Skip to content

Commit c320fd8

Browse files
committed
cache decorator fixed to replace variable names in key_prefix, now possible to invalidate extra cache passing as an argument
1 parent 37e585b commit c320fd8

File tree

3 files changed

+90
-6
lines changed

3 files changed

+90
-6
lines changed

src/app/api/v1/posts.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ async def read_posts(
5252

5353

5454
@router.get("/{username}/post/{id}", response_model=PostRead)
55-
@cache(key_prefix="{username}_post_cache")
55+
@cache(key_prefix="{username}_post_cache", resource_id_name="id")
5656
async def read_post(
5757
request: Request,
5858
username: str,
@@ -71,7 +71,11 @@ async def read_post(
7171

7272

7373
@router.patch("/{username}/post/{id}", response_model=PostRead)
74-
@cache("{username}_post_cache", resource_id_name="id")
74+
@cache(
75+
"{username}_post_cache",
76+
resource_id_name="id",
77+
to_invalidate_extra={"{username}_posts": "{username}"}
78+
)
7579
async def patch_post(
7680
request: Request,
7781
username: str,
@@ -95,6 +99,11 @@ async def patch_post(
9599

96100

97101
@router.delete("/{username}/post/{id}")
102+
@cache(
103+
"{username}_post_cache",
104+
resource_id_name="id",
105+
to_invalidate_extra={"{username}_posts": "{username}"}
106+
)
98107
async def erase_post(
99108
request: Request,
100109
username: str,
@@ -123,6 +132,11 @@ async def erase_post(
123132

124133

125134
@router.delete("/{username}/db_post/{id}", dependencies=[Depends(get_current_superuser)])
135+
@cache(
136+
"{username}_post_cache",
137+
resource_id_name="id",
138+
to_invalidate_extra={"{username}_posts": "{username}"}
139+
)
126140
async def erase_db_post(
127141
request: Request,
128142
username: str,

src/app/api/v1/users.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ async def read_users_me(
4848

4949

5050
@router.get("/user/{username}", response_model=UserRead)
51-
@cache("user_cache", resource_id_type=str)
5251
async def read_user(request: Request, username: str, db: Annotated[AsyncSession, Depends(async_get_db)]):
5352
db_user = await crud_users.get(db=db, username=username, is_deleted=False)
5453
if db_user is None:

src/app/core/cache.py

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import json
44
from uuid import UUID
55
from datetime import datetime
6+
import re
67

78
from fastapi import Request, Response
89
from redis.asyncio import Redis, ConnectionPool
@@ -86,11 +87,75 @@ def _infer_resource_id(kwargs: Dict[str, Any], resource_id_type: Union[type, str
8687
return resource_id
8788

8889

89-
def cache(key_prefix: str, resource_id_name: Any = None, expiration: int = 3600, resource_id_type: Union[type, List[type]] = int) -> Callable:
90+
def _extract_data_inside_brackets(input_string: str) -> List[str]:
91+
# Use regular expressions to find data inside brackets
92+
data_inside_brackets = re.findall(r'{(.*?)}', input_string)
93+
return data_inside_brackets
94+
95+
96+
def _format_prefix(prefix: str, kwargs: Dict[str, Any]) -> str:
97+
"""
98+
Format a prefix using keyword arguments.
99+
100+
Parameters
101+
----------
102+
prefix: str
103+
The prefix template to be formatted.
104+
kwargs: Dict[str, Any]
105+
A dictionary of keyword arguments.
106+
107+
Returns
108+
-------
109+
str: The formatted prefix.
110+
"""
111+
data_inside_brackets = _extract_data_inside_brackets(prefix)
112+
formatted_prefix = prefix.format(**{key: kwargs[key] for key in data_inside_brackets})
113+
return formatted_prefix
114+
115+
116+
def _format_extra_data(
117+
to_invalidate_extra: Dict[str, str],
118+
kwargs: Dict[str, Any]
119+
) -> Dict[str, Any]:
120+
"""
121+
Format extra data based on provided templates and keyword arguments.
122+
123+
This function takes a dictionary of templates and their associated values and a dictionary of keyword arguments.
124+
It formats the templates with the corresponding values from the keyword arguments and returns a dictionary
125+
where keys are the formatted templates and values are the associated keyword argument values.
126+
127+
Parameters
128+
----------
129+
to_invalidate_extra: Dict[str, str]
130+
A dictionary where keys are templates and values are the associated values.
131+
kwargs: Dict[str, Any]
132+
A dictionary of keyword arguments.
133+
134+
Returns
135+
-------
136+
Dict[str, Any]: A dictionary where keys are formatted templates and values are associated keyword argument values.
137+
"""
138+
formatted_extra = {}
139+
for prefix, id_template in to_invalidate_extra.items():
140+
formatted_prefix = _format_prefix(prefix, kwargs)
141+
id = _extract_data_inside_brackets(id_template)[0]
142+
formatted_extra[formatted_prefix] = kwargs[id]
143+
144+
return formatted_extra
145+
146+
147+
def cache(
148+
key_prefix: str,
149+
resource_id_name: Any = None,
150+
expiration: int = 3600,
151+
resource_id_type: Union[type, List[type]] = int,
152+
to_invalidate_extra: Dict[str, Any] | None = None
153+
) -> Callable:
90154
"""
91155
Cache decorator for FastAPI endpoints.
92156
93-
This decorator allows you to cache the results of FastAPI endpoint functions, improving response times and reducing the load on the application by storing and retrieving data in a cache.
157+
This decorator allows you to cache the results of FastAPI endpoint functions, improving response times and
158+
reducing the load on the application by storing and retrieving data in a cache.
94159
95160
Parameters
96161
----------
@@ -140,7 +205,8 @@ async def inner(request: Request, *args, **kwargs) -> Response:
140205
else:
141206
resource_id = _infer_resource_id(kwargs=kwargs, resource_id_type=resource_id_type)
142207

143-
cache_key = f"{key_prefix}:{resource_id}"
208+
formatted_key_prefix = _format_prefix(key_prefix, kwargs)
209+
cache_key = f"{formatted_key_prefix}:{resource_id}"
144210

145211
if request.method == "GET":
146212
cached_data = await client.get(cache_key)
@@ -163,6 +229,11 @@ async def inner(request: Request, *args, **kwargs) -> Response:
163229
await client.expire(cache_key, expiration)
164230
else:
165231
await client.delete(cache_key)
232+
if to_invalidate_extra:
233+
formatted_extra = _format_extra_data(to_invalidate_extra, kwargs)
234+
for prefix, id in formatted_extra.items():
235+
extra_cache_key = f"{prefix}:{id}"
236+
await client.delete(extra_cache_key)
166237

167238
return result
168239

0 commit comments

Comments
 (0)