Skip to content

Commit 3b56ca4

Browse files
authored
update README (#26)
1 parent 945381b commit 3b56ca4

1 file changed

Lines changed: 51 additions & 11 deletions

File tree

README.md

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,59 @@ INFO: 127.0.0.1:61779 - "GET /immutable_data HTTP/1.1" 200 OK
8484

8585
The log messages show two successful (**`200 OK`**) responses to the same request (**`GET /immutable_data`**). The first request executed the `get_immutable_data` function and stored the result in Redis under key `api.get_immutable_data()`. The second request _**did not**_ execute the `get_immutable_data` function, instead the cached result was retrieved and sent as the response.
8686

87-
If data for an API endpoint needs to expire, you can specify the number of seconds before it is deleted by Redis using the `expire_after_seconds` parameter:
87+
In most situations, response data must expire in a much shorter period of time than one year. Using the `expire` parameter, You can specify the number of seconds before data is deleted:
8888

8989
```python
9090
# Will be cached for thirty seconds
9191
@app.get("/dynamic_data")
92-
@cache(expire_after_seconds=30)
92+
@cache(expire=30)
9393
def get_dynamic_data(request: Request, response: Response):
9494
return {"success": True, "message": "this data should only be cached temporarily"}
9595
```
9696

97+
> **NOTE!** `expire` can be either an `int` value or `timedelta` object. When the TTL is very short (like the example above) this results in a decorator that is expressive and requires minimal effort to parse visually. For durations an hour or longer (e.g., `@cache(expire=86400)`), IMHO, using a `timedelta` object is much easier to grok (`@cache(expire=timedelta(days=1))`).
98+
99+
Additionally, the decorators listed below define several common durations and can be used in place of the `@cache` decorator:
100+
101+
- `@cache_one_minute`
102+
- `@cache_one_hour`
103+
- `@cache_one_day`
104+
- `@cache_one_week`
105+
- `@cache_one_month`
106+
- `@cache_one_year`
107+
108+
For example, instead of `@cache(expire=timedelta(days=1))`, you could use:
109+
110+
```python
111+
from fastapi_redis_cache import cache_one_day
112+
113+
@app.get("/cache_one_day")
114+
@cache_one_day()
115+
def partial_cache_one_day(response: Response):
116+
return {"success": True, "message": "this data should be cached for 24 hours"}
117+
```
118+
119+
If a duration that you would like to use throughout your project is missing from the list, you can easily create your own:
120+
121+
```python
122+
from functools import partial, update_wrapper
123+
from fastapi_redis_cache import cache
124+
125+
ONE_HOUR_IN_SECONDS = 3600
126+
127+
cache_two_hours = partial(cache, expire=ONE_HOUR_IN_SECONDS * 2)
128+
update_wrapper(cache_two_hours, cache)
129+
```
130+
131+
Then, simply import `cache_two_hours` and use it to decorate your API endpoint path functions:
132+
133+
```python
134+
@app.get("/cache_two_hours")
135+
@cache_two_hours()
136+
def partial_cache_two_hours(response: Response):
137+
return {"success": True, "message": "this data should be cached for two hours"}
138+
```
139+
97140
#### Response Headers
98141

99142
Below is an example HTTP response for the `/dynamic_data` endpoint. The `cache-control`, `etag`, `expires`, and `x-fastapi-cache` headers are added because of the `@cache` decorator:
@@ -117,7 +160,7 @@ $ http "http://127.0.0.1:8000/dynamic_data"
117160
```
118161

119162
- The `x-fastapi-cache` header field indicates that this response was found in the Redis cache (a.k.a. a `Hit`). The only other possible value for this field is `Miss`.
120-
- The `expires` field and `max-age` value in the `cache-control` field indicate that this response will be considered fresh for 29 seconds. This is expected since `expire_after_seconds=30` was specified in the `@cache` decorator.
163+
- The `expires` field and `max-age` value in the `cache-control` field indicate that this response will be considered fresh for 29 seconds. This is expected since `expire=30` was specified in the `@cache` decorator.
121164
- The `etag` field is an identifier that is created by converting the response data to a string and applying a hash function. If a request containing the `if-none-match` header is received, the `etag` value will be used to determine if the requested resource has been modified.
122165

123166
If this request was made from a web browser, and a request for the same resource was sent before the cached response expires, the browser would automatically serve the cached version and the request would never even be sent to the FastAPI server.
@@ -126,27 +169,25 @@ Similarly, if a request is sent with the `cache-control` header containing `no-c
126169

127170
#### Cache Keys
128171

129-
Consider the `/get_user` API route defined below. This is the first path function we have seen where the response depends on the value of an argument (`user_id: int`). This is a typical CRUD operation where `user_id` is used to retrieve a `User` record from a SQLAlchemy database.
172+
Consider the `/get_user` API route defined below. This is the first path function we have seen where the response depends on the value of an argument (`user_id: int`). This is a typical CRUD operation where `user_id` is used to retrieve a `User` record from a database. The API route also includes a dependency that injects a `Session` object (`db`) into the function, [per the instructions from the FastAPI docs](https://fastapi.tiangolo.com/tutorial/sql-databases/#create-a-dependency):
130173

131174
```python
132175
@app.get("/get_user", response_model=schemas.User)
133-
@cache(expire_after_seconds=3600)
176+
@cache(expire=3600)
134177
def get_item(user_id: int, db: Session = Depends(get_db)):
135178
return db.query(models.User).filter(models.User.id == user_id).first()
136179
```
137180

138-
The API route also includes a dependency that injects a Session object (`db`) into the function, [per the instructions from the FastAPI docs](https://fastapi.tiangolo.com/tutorial/sql-databases/#create-a-dependency). This is used to query the database for the `User` corresponding to the `user_id` value.
139-
140181
In the [Initialize Redis](#initialize-redis) section of this document, the `FastApiRedisCache.init` method was called with `ignore_arg_types=[Request, Response, Session]`. Why is it necessary to include `Session` in this list?
141182

142183
Before we can answer that question, we must understand how a cache key is created. In order to create a unique identifier for the data sent in response to an API request, the following values are combined:
143184

144185
1) The optional `prefix` value provided as an argument to the `FastApiRedisCache.init` method (`"myapi-cache"`).
145186
2) The module containing the path function (`"api"`).
146187
3) The name of the path function (`"get_user"`).
147-
4) The name and value of all arguments to the path function **EXCEPT for arguments with a type that exists in** `ignore_arg_types` (`"user_id=?"`).
188+
4) The name and value of all arguments to the path function **EXCEPT for arguments with a type that exists in** `ignore_arg_types` (`"user_id=1"`).
148189

149-
Therefore, all response data for the `/get_user` endpoint will have a cache key equal to `"myapi-cache:api.get_user(user_id=?)"` (e.g., for `user_id=1`, the cache key will be `"myapi-cache:api.get_user(user_id=1)"`).
190+
Therefore, the cache key in this example will be `"myapi-cache:api.get_user(user_id=1)"`).
150191

151192
Even though `db` is an argument to the path function, it is not included in the cache key because it is a `Session` type. If `Session` had not been included in the `ignore_arg_types` list, caching would be completely broken.
152193

@@ -203,7 +244,6 @@ def get_scoreboard_for_date(
203244

204245
The `game_date` argument is a `MLBGameDate` type. This is a custom type that parses the value from the querystring to a date, and determines if the parsed date is valid by checking if it is within a certain range. The implementation for `MLBGameDate` is given below:
205246

206-
207247
```python
208248
class MLBGameDate:
209249
def __init__(
@@ -225,7 +265,7 @@ class MLBGameDate:
225265
return self.date.strftime("%Y-%m-%d")
226266
```
227267

228-
Please note the custom `__str__` method that overrides the default behavior. This way, instead of `<MLBGameDate object at 0x11c7e35e0>`, the value will be formatted as, for example, `2019-05-09`. You can use this strategy whenever you have an argument that has en effect on the response data but converting that argument to a string results in a value containing the object's memory location.
268+
Please note the `__str__` method that overrides the default behavior. This way, instead of `<MLBGameDate object at 0x11c7e35e0>`, the value will be formatted as, for example, `2019-05-09`. You can use this strategy whenever you have an argument that has en effect on the response data but converting that argument to a string results in a value containing the object's memory location.
229269

230270
### Questions/Contributions
231271

0 commit comments

Comments
 (0)