Skip to content

Commit

Permalink
first working basic expires decorator
Browse files Browse the repository at this point in the history
Signed-off-by: Grant Ramsay <[email protected]>
  • Loading branch information
seapagan committed Apr 3, 2024
1 parent d15e3fb commit e9a3481
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 22 deletions.
2 changes: 2 additions & 0 deletions fastapi_redis_cache/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
cache_one_month,
cache_one_week,
cache_one_year,
expires,
)
from fastapi_redis_cache.client import FastApiRedisCache

Expand All @@ -19,5 +20,6 @@
"cache_one_month",
"cache_one_week",
"cache_one_year",
"expires",
"FastApiRedisCache",
]
40 changes: 29 additions & 11 deletions fastapi_redis_cache/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,19 @@ async def inner_wrapper(
return outer_wrapper


def expires(tag: str | None = None) -> Callable[..., Any]:
def expires(
tag: str | None = None,
arg: str | None = None, # noqa: ARG001
) -> Callable[..., Any]:
"""Invalidate all cached responses with the same tag.
Args:
tag (str, optional): A tag to associate with the cached response. This
can later be used to invalidate all cached responses with the same
tag, or for further fine-grained cache expiry. Defaults to None.
tag (str, optional): The tag to search for keys to expire.
Defaults to None.
arg: (str, optional): The function arguement to filter for expiry. This
would generally be the varying arguement suppplied to the route.
Defaults to None. If not specified, the kwargs for the route will
be used to search for the key to expire.
"""

def outer_wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
Expand All @@ -144,14 +150,26 @@ async def inner_wrapper(
) -> Any: # noqa: ANN401
"""Invalidate all cached responses with the same tag."""
redis_cache = FastApiRedisCache()
if redis_cache.not_connected:
return await get_api_response_async(func, *args, **kwargs)
if tag:
# expire all keys with the same tag. This is a test we will
# later only expire keys that have the search argument in the
# key.
orig_response = await get_api_response_async(func, *args, **kwargs)

if not redis_cache.redis or not redis_cache.connected or not tag:
# we only want to invalidate the cache if the redis client is
# connected and a tag is provided.
return orig_response
if kwargs:
search = "".join(
[f"({key}={value})" for key, value in kwargs.items()]
)
tag_keys = redis_cache.get_tagged_keys(tag)
found_keys = [key for key in tag_keys if search.encode() in key]
for key in found_keys:
redis_cache.redis.delete(key)
redis_cache.redis.srem(tag, key)
else:
# will fill this later, what to do if no kwargs are provided
pass
return await get_api_response_async(func, *args, **kwargs)

return orig_response

return inner_wrapper

Expand Down
4 changes: 3 additions & 1 deletion fastapi_redis_cache/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
from fastapi_redis_cache.util import serialize_json

if TYPE_CHECKING: # pragma: no cover
from collections.abc import ByteString

from fastapi import Request, Response
from redis import client

Expand Down Expand Up @@ -158,7 +160,7 @@ def add_key_to_tag_set(self, tag: str, key: str) -> None:
if self.redis:
self.redis.sadd(tag, key)

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

Expand Down
16 changes: 8 additions & 8 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ distlib==0.3.8 ; python_version >= "3.9" and python_version < "4.0"
dnspython==2.6.1 ; python_version >= "3.9" and python_version < "4.0"
email-validator==2.1.1 ; python_version >= "3.9" and python_version < "4.0"
exceptiongroup==1.2.0 ; python_version >= "3.9" and python_version < "3.11"
faker==24.3.0 ; python_version >= "3.9" and python_version < "4.0"
faker==24.4.0 ; python_version >= "3.9" and python_version < "4.0"
fakeredis==2.21.3 ; python_version >= "3.9" and python_version < "4.0"
fastapi[all]==0.110.0 ; python_version >= "3.9" and python_version < "4.0"
fastapi[all]==0.110.1 ; python_version >= "3.9" and python_version < "4.0"
filelock==3.13.1 ; python_version >= "3.9" and python_version < "4.0"
github-changelog-md==0.9.2 ; python_version >= "3.9" and python_version < "4.0"
h11==0.14.0 ; python_version >= "3.9" and python_version < "4.0"
Expand All @@ -44,7 +44,7 @@ pastel==0.2.1 ; python_version >= "3.9" and python_version < "4.0"
platformdirs==4.2.0 ; python_version >= "3.9" and python_version < "4.0"
pluggy==1.4.0 ; python_version >= "3.9" and python_version < "4.0"
poethepoet==0.25.0 ; python_version >= "3.9" and python_version < "4.0"
pre-commit==3.6.2 ; python_version >= "3.9" and python_version < "4.0"
pre-commit==3.7.0 ; python_version >= "3.9" and python_version < "4.0"
pycparser==2.21 ; python_version >= "3.9" and python_version < "4.0"
pydantic-core==2.16.3 ; python_version >= "3.9" and python_version < "4.0"
pydantic-extra-types==2.6.0 ; python_version >= "3.9" and python_version < "4.0"
Expand All @@ -57,14 +57,14 @@ pyjwt[crypto]==2.8.0 ; python_version >= "3.9" and python_version < "4.0"
pymarkdownlnt==0.9.18 ; python_version >= "3.9" and python_version < "4.0"
pynacl==1.5.0 ; python_version >= "3.9" and python_version < "4.0"
pytest-asyncio==0.21.1 ; python_version >= "3.9" and python_version < "4.0"
pytest-cov==4.1.0 ; python_version >= "3.9" and python_version < "4.0"
pytest-cov==5.0.0 ; python_version >= "3.9" and python_version < "4.0"
pytest-env==1.1.3 ; python_version >= "3.9" and python_version < "4.0"
pytest-mock==3.14.0 ; python_version >= "3.9" and python_version < "4.0"
pytest-order==1.2.0 ; python_version >= "3.9" and python_version < "4.0"
pytest-order==1.2.1 ; python_version >= "3.9" and python_version < "4.0"
pytest-randomly==3.15.0 ; python_version >= "3.9" and python_version < "4.0"
pytest-reverse==1.7.0 ; python_version >= "3.9" and python_version < "4.0"
pytest-sugar==1.0.0 ; python_version >= "3.9" and python_version < "4.0"
pytest-watcher==0.4.1 ; python_version >= "3.9" and python_version < "4.0"
pytest-watcher==0.4.2 ; python_version >= "3.9" and python_version < "4.0"
pytest==8.1.1 ; python_version >= "3.9" and python_version < "4.0"
python-dateutil==2.9.0.post0 ; python_version >= "3.9" and python_version < "4.0"
python-dotenv==1.0.1 ; python_version >= "3.9" and python_version < "4.0"
Expand All @@ -74,13 +74,13 @@ redis==5.0.3 ; python_version >= "3.9" and python_version < "4.0"
requests==2.31.0 ; python_version >= "3.9" and python_version < "4.0"
rich==13.7.1 ; python_version >= "3.9" and python_version < "4.0"
rtoml==0.9.0 ; python_version >= "3.9" and python_version < "4.0"
ruff==0.3.4 ; python_version >= "3.9" and python_version < "4.0"
ruff==0.3.5 ; python_version >= "3.9" and python_version < "4.0"
setuptools==69.2.0 ; python_version >= "3.9" and python_version < "4.0"
simple-toml-settings==0.6.0 ; python_version >= "3.9" and python_version < "4.0"
six==1.16.0 ; python_version >= "3.9" and python_version < "4.0"
sniffio==1.3.1 ; python_version >= "3.9" and python_version < "4.0"
sortedcontainers==2.4.0 ; python_version >= "3.9" and python_version < "4.0"
starlette==0.36.3 ; python_version >= "3.9" and python_version < "4.0"
starlette==0.37.2 ; python_version >= "3.9" and python_version < "4.0"
termcolor==2.4.0 ; python_version >= "3.9" and python_version < "4.0"
tomli==2.0.1 ; python_version >= "3.9" and python_version < "4.0"
toolz==0.12.1 ; python_version >= "3.9" and python_version < "4.0"
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ colorama==0.4.6 ; python_version >= "3.9" and python_version < "4.0" and (sys_pl
dnspython==2.6.1 ; python_version >= "3.9" and python_version < "4.0"
email-validator==2.1.1 ; python_version >= "3.9" and python_version < "4.0"
exceptiongroup==1.2.0 ; python_version >= "3.9" and python_version < "3.11"
fastapi[all]==0.110.0 ; python_version >= "3.9" and python_version < "4.0"
fastapi[all]==0.110.1 ; python_version >= "3.9" and python_version < "4.0"
h11==0.14.0 ; python_version >= "3.9" and python_version < "4.0"
httpcore==1.0.4 ; python_version >= "3.9" and python_version < "4.0"
httptools==0.6.1 ; python_version >= "3.9" and python_version < "4.0"
Expand All @@ -26,7 +26,7 @@ python-multipart==0.0.9 ; python_version >= "3.9" and python_version < "4.0"
pyyaml==6.0.1 ; python_version >= "3.9" and python_version < "4.0"
redis==5.0.3 ; python_version >= "3.9" and python_version < "4.0"
sniffio==1.3.1 ; python_version >= "3.9" and python_version < "4.0"
starlette==0.36.3 ; python_version >= "3.9" and python_version < "4.0"
starlette==0.37.2 ; python_version >= "3.9" and python_version < "4.0"
typing-extensions==4.10.0 ; python_version >= "3.9" and python_version < "4.0"
ujson==5.9.0 ; python_version >= "3.9" and python_version < "4.0"
uvicorn[standard]==0.28.0 ; python_version >= "3.9" and python_version < "4.0"
Expand Down
11 changes: 11 additions & 0 deletions tests/live_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
cache,
cache_one_hour,
cache_one_minute,
expires,
)

REDIS_SERVER_URL = "redis://127.0.0.1:"
Expand Down Expand Up @@ -104,3 +105,13 @@ def cache_with_args(user: int) -> dict[str, Union[bool, str]]:
"success": True,
"message": f"this data is for user {user}",
}


@app.put("/cache_with_args/{user}")
@expires(tag="user_tag")
def put_cache_with_args(user: int) -> dict[str, Union[bool, str]]:
"""Put request to change data for a specific user."""
return {
"success": True,
"message": f"New data for User {user}",
}

0 comments on commit e9a3481

Please sign in to comment.