Skip to content

Commit e9a3481

Browse files
committed
first working basic expires decorator
Signed-off-by: Grant Ramsay <seapagan@gmail.com>
1 parent d15e3fb commit e9a3481

File tree

6 files changed

+55
-22
lines changed

6 files changed

+55
-22
lines changed

fastapi_redis_cache/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
cache_one_month,
99
cache_one_week,
1010
cache_one_year,
11+
expires,
1112
)
1213
from fastapi_redis_cache.client import FastApiRedisCache
1314

@@ -19,5 +20,6 @@
1920
"cache_one_month",
2021
"cache_one_week",
2122
"cache_one_year",
23+
"expires",
2224
"FastApiRedisCache",
2325
]

fastapi_redis_cache/cache.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,19 @@ async def inner_wrapper(
127127
return outer_wrapper
128128

129129

130-
def expires(tag: str | None = None) -> Callable[..., Any]:
130+
def expires(
131+
tag: str | None = None,
132+
arg: str | None = None, # noqa: ARG001
133+
) -> Callable[..., Any]:
131134
"""Invalidate all cached responses with the same tag.
132135
133136
Args:
134-
tag (str, optional): A tag to associate with the cached response. This
135-
can later be used to invalidate all cached responses with the same
136-
tag, or for further fine-grained cache expiry. Defaults to None.
137+
tag (str, optional): The tag to search for keys to expire.
138+
Defaults to None.
139+
arg: (str, optional): The function arguement to filter for expiry. This
140+
would generally be the varying arguement suppplied to the route.
141+
Defaults to None. If not specified, the kwargs for the route will
142+
be used to search for the key to expire.
137143
"""
138144

139145
def outer_wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
@@ -144,14 +150,26 @@ async def inner_wrapper(
144150
) -> Any: # noqa: ANN401
145151
"""Invalidate all cached responses with the same tag."""
146152
redis_cache = FastApiRedisCache()
147-
if redis_cache.not_connected:
148-
return await get_api_response_async(func, *args, **kwargs)
149-
if tag:
150-
# expire all keys with the same tag. This is a test we will
151-
# later only expire keys that have the search argument in the
152-
# key.
153+
orig_response = await get_api_response_async(func, *args, **kwargs)
154+
155+
if not redis_cache.redis or not redis_cache.connected or not tag:
156+
# we only want to invalidate the cache if the redis client is
157+
# connected and a tag is provided.
158+
return orig_response
159+
if kwargs:
160+
search = "".join(
161+
[f"({key}={value})" for key, value in kwargs.items()]
162+
)
163+
tag_keys = redis_cache.get_tagged_keys(tag)
164+
found_keys = [key for key in tag_keys if search.encode() in key]
165+
for key in found_keys:
166+
redis_cache.redis.delete(key)
167+
redis_cache.redis.srem(tag, key)
168+
else:
169+
# will fill this later, what to do if no kwargs are provided
153170
pass
154-
return await get_api_response_async(func, *args, **kwargs)
171+
172+
return orig_response
155173

156174
return inner_wrapper
157175

fastapi_redis_cache/client.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
from fastapi_redis_cache.util import serialize_json
2121

2222
if TYPE_CHECKING: # pragma: no cover
23+
from collections.abc import ByteString
24+
2325
from fastapi import Request, Response
2426
from redis import client
2527

@@ -158,7 +160,7 @@ def add_key_to_tag_set(self, tag: str, key: str) -> None:
158160
if self.redis:
159161
self.redis.sadd(tag, key)
160162

161-
def get_tagged_keys(self, tag: str) -> set[str]:
163+
def get_tagged_keys(self, tag: str) -> set[ByteString]:
162164
"""Return a set of keys associated with a tag."""
163165
return self.redis.smembers(tag) if self.redis else set()
164166

requirements-dev.txt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ distlib==0.3.8 ; python_version >= "3.9" and python_version < "4.0"
1616
dnspython==2.6.1 ; python_version >= "3.9" and python_version < "4.0"
1717
email-validator==2.1.1 ; python_version >= "3.9" and python_version < "4.0"
1818
exceptiongroup==1.2.0 ; python_version >= "3.9" and python_version < "3.11"
19-
faker==24.3.0 ; python_version >= "3.9" and python_version < "4.0"
19+
faker==24.4.0 ; python_version >= "3.9" and python_version < "4.0"
2020
fakeredis==2.21.3 ; python_version >= "3.9" and python_version < "4.0"
21-
fastapi[all]==0.110.0 ; python_version >= "3.9" and python_version < "4.0"
21+
fastapi[all]==0.110.1 ; python_version >= "3.9" and python_version < "4.0"
2222
filelock==3.13.1 ; python_version >= "3.9" and python_version < "4.0"
2323
github-changelog-md==0.9.2 ; python_version >= "3.9" and python_version < "4.0"
2424
h11==0.14.0 ; python_version >= "3.9" and python_version < "4.0"
@@ -44,7 +44,7 @@ pastel==0.2.1 ; python_version >= "3.9" and python_version < "4.0"
4444
platformdirs==4.2.0 ; python_version >= "3.9" and python_version < "4.0"
4545
pluggy==1.4.0 ; python_version >= "3.9" and python_version < "4.0"
4646
poethepoet==0.25.0 ; python_version >= "3.9" and python_version < "4.0"
47-
pre-commit==3.6.2 ; python_version >= "3.9" and python_version < "4.0"
47+
pre-commit==3.7.0 ; python_version >= "3.9" and python_version < "4.0"
4848
pycparser==2.21 ; python_version >= "3.9" and python_version < "4.0"
4949
pydantic-core==2.16.3 ; python_version >= "3.9" and python_version < "4.0"
5050
pydantic-extra-types==2.6.0 ; python_version >= "3.9" and python_version < "4.0"
@@ -57,14 +57,14 @@ pyjwt[crypto]==2.8.0 ; python_version >= "3.9" and python_version < "4.0"
5757
pymarkdownlnt==0.9.18 ; python_version >= "3.9" and python_version < "4.0"
5858
pynacl==1.5.0 ; python_version >= "3.9" and python_version < "4.0"
5959
pytest-asyncio==0.21.1 ; python_version >= "3.9" and python_version < "4.0"
60-
pytest-cov==4.1.0 ; python_version >= "3.9" and python_version < "4.0"
60+
pytest-cov==5.0.0 ; python_version >= "3.9" and python_version < "4.0"
6161
pytest-env==1.1.3 ; python_version >= "3.9" and python_version < "4.0"
6262
pytest-mock==3.14.0 ; python_version >= "3.9" and python_version < "4.0"
63-
pytest-order==1.2.0 ; python_version >= "3.9" and python_version < "4.0"
63+
pytest-order==1.2.1 ; python_version >= "3.9" and python_version < "4.0"
6464
pytest-randomly==3.15.0 ; python_version >= "3.9" and python_version < "4.0"
6565
pytest-reverse==1.7.0 ; python_version >= "3.9" and python_version < "4.0"
6666
pytest-sugar==1.0.0 ; python_version >= "3.9" and python_version < "4.0"
67-
pytest-watcher==0.4.1 ; python_version >= "3.9" and python_version < "4.0"
67+
pytest-watcher==0.4.2 ; python_version >= "3.9" and python_version < "4.0"
6868
pytest==8.1.1 ; python_version >= "3.9" and python_version < "4.0"
6969
python-dateutil==2.9.0.post0 ; python_version >= "3.9" and python_version < "4.0"
7070
python-dotenv==1.0.1 ; python_version >= "3.9" and python_version < "4.0"
@@ -74,13 +74,13 @@ redis==5.0.3 ; python_version >= "3.9" and python_version < "4.0"
7474
requests==2.31.0 ; python_version >= "3.9" and python_version < "4.0"
7575
rich==13.7.1 ; python_version >= "3.9" and python_version < "4.0"
7676
rtoml==0.9.0 ; python_version >= "3.9" and python_version < "4.0"
77-
ruff==0.3.4 ; python_version >= "3.9" and python_version < "4.0"
77+
ruff==0.3.5 ; python_version >= "3.9" and python_version < "4.0"
7878
setuptools==69.2.0 ; python_version >= "3.9" and python_version < "4.0"
7979
simple-toml-settings==0.6.0 ; python_version >= "3.9" and python_version < "4.0"
8080
six==1.16.0 ; python_version >= "3.9" and python_version < "4.0"
8181
sniffio==1.3.1 ; python_version >= "3.9" and python_version < "4.0"
8282
sortedcontainers==2.4.0 ; python_version >= "3.9" and python_version < "4.0"
83-
starlette==0.36.3 ; python_version >= "3.9" and python_version < "4.0"
83+
starlette==0.37.2 ; python_version >= "3.9" and python_version < "4.0"
8484
termcolor==2.4.0 ; python_version >= "3.9" and python_version < "4.0"
8585
tomli==2.0.1 ; python_version >= "3.9" and python_version < "4.0"
8686
toolz==0.12.1 ; python_version >= "3.9" and python_version < "4.0"

requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ colorama==0.4.6 ; python_version >= "3.9" and python_version < "4.0" and (sys_pl
77
dnspython==2.6.1 ; python_version >= "3.9" and python_version < "4.0"
88
email-validator==2.1.1 ; python_version >= "3.9" and python_version < "4.0"
99
exceptiongroup==1.2.0 ; python_version >= "3.9" and python_version < "3.11"
10-
fastapi[all]==0.110.0 ; python_version >= "3.9" and python_version < "4.0"
10+
fastapi[all]==0.110.1 ; python_version >= "3.9" and python_version < "4.0"
1111
h11==0.14.0 ; python_version >= "3.9" and python_version < "4.0"
1212
httpcore==1.0.4 ; python_version >= "3.9" and python_version < "4.0"
1313
httptools==0.6.1 ; python_version >= "3.9" and python_version < "4.0"
@@ -26,7 +26,7 @@ python-multipart==0.0.9 ; python_version >= "3.9" and python_version < "4.0"
2626
pyyaml==6.0.1 ; python_version >= "3.9" and python_version < "4.0"
2727
redis==5.0.3 ; python_version >= "3.9" and python_version < "4.0"
2828
sniffio==1.3.1 ; python_version >= "3.9" and python_version < "4.0"
29-
starlette==0.36.3 ; python_version >= "3.9" and python_version < "4.0"
29+
starlette==0.37.2 ; python_version >= "3.9" and python_version < "4.0"
3030
typing-extensions==4.10.0 ; python_version >= "3.9" and python_version < "4.0"
3131
ujson==5.9.0 ; python_version >= "3.9" and python_version < "4.0"
3232
uvicorn[standard]==0.28.0 ; python_version >= "3.9" and python_version < "4.0"

tests/live_test.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
cache,
2020
cache_one_hour,
2121
cache_one_minute,
22+
expires,
2223
)
2324

2425
REDIS_SERVER_URL = "redis://127.0.0.1:"
@@ -104,3 +105,13 @@ def cache_with_args(user: int) -> dict[str, Union[bool, str]]:
104105
"success": True,
105106
"message": f"this data is for user {user}",
106107
}
108+
109+
110+
@app.put("/cache_with_args/{user}")
111+
@expires(tag="user_tag")
112+
def put_cache_with_args(user: int) -> dict[str, Union[bool, str]]:
113+
"""Put request to change data for a specific user."""
114+
return {
115+
"success": True,
116+
"message": f"New data for User {user}",
117+
}

0 commit comments

Comments
 (0)