REDIS кэширование, на локалке

develop
Сергей Ванюшкин 2024-02-05 03:40:12 +03:00
parent 291c61f873
commit 43eca19d91
5 changed files with 234 additions and 53 deletions

View File

@ -60,8 +60,7 @@ class MenuRepository:
updated_menu = await self.db.execute(qr)
return updated_menu.scalar_one()
async def delete_menu_item(self, menu_id: UUID) -> int:
async def delete_menu_item(self, menu_id: UUID):
query = delete(Menu).where(Menu.id == menu_id)
await self.db.execute(query)
await self.db.commit()
return 200

View File

@ -1,3 +1,4 @@
import pickle
from typing import Any
import redis.asyncio as redis # type: ignore
@ -6,19 +7,59 @@ from fastapi import BackgroundTasks, Depends
from fastfood.dbase import get_redis_pool
def get_key(level: str, **kwargs) -> str:
match level:
case 'menus':
return 'MENUS'
case 'menu':
return f"{kwargs.get('menu_id')}"
case 'submenus':
return f"{kwargs.get('menu_id')}:SUBMENUS"
case 'submenu':
return f"{kwargs.get('menu_id')}:{kwargs.get('submenu_id')}"
case 'dishes':
return f"{kwargs.get('menu_id')}:{kwargs.get('submenu_id')}:DISHES"
case 'dish':
return f"{kwargs.get('menu_id')}:{kwargs.get('submenu_id')}:{kwargs.get('dish_id')}"
return 'abracadabra'
class RedisRepository:
def __init__(
self,
redis_pool: redis.Redis = Depends(get_redis_pool),
pool: redis.Redis = Depends(get_redis_pool),
) -> None:
self.redis_pool = redis_pool
self.pool = pool
self.ttl = 1800
async def get(self, key: str) -> Any | None:
data = await self.pool.get(key)
if data is not None:
return pickle.loads(data)
return None
async def set(
self, key: str, value: Any, background_tasks: BackgroundTasks
) -> None:
pass
async def set(self, key: str, value: Any, bg_task: BackgroundTasks) -> None:
data = pickle.dumps(value)
bg_task.add_task(self._set_cache, key, data)
async def _set_cache(self, key: str, data: Any) -> None:
await self.pool.setex(key, self.ttl, data)
async def delete(self, key: str, bg_task: BackgroundTasks) -> None:
bg_task.add_task(self._delete_cache, key)
async def _delete_cache(self, key: str) -> None:
await self.pool.delete(key)
async def clear_cache(self, pattern: str, bg_task: BackgroundTasks) -> None:
keys = [key async for key in self.pool.scan_iter(pattern)]
if keys:
bg_task.add_task(self._clear_keys, keys)
async def _clear_keys(self, keys: list[str]) -> None:
await self.pool.delete(*keys)
async def invalidate(self, key: str, bg_task: BackgroundTasks) -> None:
await self.clear_cache(f'{key}*', bg_task)
await self.clear_cache(f'{get_key("menus")}*', bg_task)

View File

@ -5,7 +5,7 @@ from fastapi import BackgroundTasks, Depends
from fastfood.dbase import get_async_redis_client
from fastfood.repository.dish import DishRepository
from fastfood.repository.redis import RedisRepository
from fastfood.repository.redis import RedisRepository, get_key
from fastfood.schemas import Dish, Dish_db, DishBase
@ -17,16 +17,32 @@ class DishService:
background_tasks: BackgroundTasks = None,
) -> None:
self.dish_repo = dish_repo
self.cache_client = RedisRepository(redis_client)
self.background_tasks = background_tasks
self.cache = RedisRepository(redis_client)
self.bg_tasks = background_tasks
self.key = get_key
async def read_dishes(self, menu_id: UUID, submenu_id: UUID) -> list[Dish]:
cached_dishes = await self.cache.get(
self.key('dishes', menu_id=str(menu_id), submenu_id=str(submenu_id))
)
if cached_dishes is not None:
return cached_dishes
data = await self.dish_repo.get_dishes(menu_id, submenu_id)
response = []
for row in data:
dish = row.__dict__
dish['price'] = str(dish['price'])
response.append(Dish(**dish))
await self.cache.set(
self.key(
'dishes',
menu_id=str(menu_id),
submenu_id=str(submenu_id),
),
response,
self.bg_tasks,
)
return response
async def create_dish(
@ -35,34 +51,79 @@ class DishService:
submenu_id: UUID,
dish_data: DishBase,
) -> Dish:
dish = Dish_db(**dish_data.model_dump())
dish_db = Dish_db(**dish_data.model_dump())
data = await self.dish_repo.create_dish_item(
menu_id,
submenu_id,
dish,
dish_db,
)
response = data.__dict__
response['price'] = str(response['price'])
return Dish(**response)
dish = data.__dict__
dish['price'] = str(dish['price'])
dish = Dish(**dish)
await self.cache.set(
self.key('dish', menu_id=str(menu_id), submenu_id=str(submenu_id)),
dish,
self.bg_tasks,
)
await self.cache.invalidate(key=str(menu_id), bg_task=self.bg_tasks)
return dish
async def read_dish(
self, menu_id: UUID, submenu_id: UUID, dish_id: UUID
) -> Dish | None:
cached_dish = await self.cache.get(
self.key(
'dish',
menu_id=str(menu_id),
submenu_id=str(submenu_id),
dish_id=str(dish_id),
)
)
if cached_dish is not None:
return cached_dish
data = await self.dish_repo.get_dish_item(menu_id, submenu_id, dish_id)
if data is None:
return None
response = data.__dict__
response['price'] = str(response['price'])
return Dish(**response)
dish = data.__dict__
dish['price'] = str(dish['price'])
dish = Dish(**dish)
await self.cache.set(
self.key(
'dish',
menu_id=str(menu_id),
submenu_id=str(submenu_id),
dish_id=str(dish_id),
),
dish,
self.bg_tasks,
)
return dish
async def update_dish(
self, menu_id: UUID, submenu_id: UUID, dish_id, dish_data: DishBase
) -> Dish:
dish = Dish_db(**dish_data.model_dump())
data = await self.dish_repo.update_dish_item(menu_id, submenu_id, dish_id, dish)
response = data.__dict__
response['price'] = str(response['price'])
return Dish(**response)
dish_db = Dish_db(**dish_data.model_dump())
data = await self.dish_repo.update_dish_item(
menu_id, submenu_id, dish_id, dish_db
)
dish = data.__dict__
dish['price'] = str(dish['price'])
dish = Dish(**dish)
await self.cache.set(
self.key(
'dish',
menu_id=str(menu_id),
submenu_id=str(submenu_id),
dish_id=str(dish_id),
),
dish,
self.bg_tasks,
)
await self.cache.invalidate(key=str(menu_id), bg_task=self.bg_tasks)
return dish
async def del_dish(self, menu_id: UUID, submenu_id: UUID, dish_id: UUID) -> int:
response = await self.dish_repo.delete_dish_item(
@ -70,4 +131,7 @@ class DishService:
submenu_id,
dish_id,
)
await self.cache.delete(key=str(menu_id), bg_task=self.bg_tasks)
await self.cache.invalidate(key=str(menu_id), bg_task=self.bg_tasks)
return response

View File

@ -5,7 +5,7 @@ from fastapi import BackgroundTasks, Depends
from fastfood.dbase import get_async_redis_client
from fastfood.repository.menu import MenuRepository
from fastfood.repository.redis import RedisRepository
from fastfood.repository.redis import RedisRepository, get_key
from fastfood.schemas import MenuBase, MenuRead
@ -17,10 +17,15 @@ class MenuService:
background_tasks: BackgroundTasks = None,
) -> None:
self.menu_repo = menu_repo
self.cache_client = RedisRepository(redis_client)
self.background_tasks = background_tasks
self.cache = RedisRepository(redis_client)
self.key = get_key
self.bg_tasks = background_tasks
async def read_menus(self) -> list[MenuRead]:
cached_menus = await self.cache.get(self.key('menus'))
if cached_menus is not None:
return cached_menus
data = await self.menu_repo.get_menus()
menus = []
for r in data:
@ -34,6 +39,8 @@ class MenuService:
menu['dishes_count'] = dishes_conter
menu = MenuRead(**menu)
menus.append(menu)
await self.cache.set(self.key('menus'), menus, self.bg_tasks)
return menus
async def create_menu(self, menu_data: MenuBase) -> MenuRead:
@ -46,10 +53,23 @@ class MenuService:
dishes_conter += len(sub.dishes)
menu['submenus_count'] = len(menu.pop('submenus'))
menu['dishes_count'] = dishes_conter
return MenuRead(**menu)
await self.cache.set(
key=get_key('menu', menu_id=str(menu.get('id'))),
value=menu,
bg_task=self.bg_tasks,
)
menu = MenuRead(**menu)
await self.cache.set(
self.key('menu', menu_id=str(menu.id)), menu, self.bg_tasks
)
await self.cache.invalidate(key=str(menu.id), bg_task=self.bg_tasks)
return menu
async def read_menu(self, menu_id: UUID) -> MenuRead | None:
cached_menu = await self.cache.get(self.key('menu', menu_id=str(menu_id)))
if cached_menu is not None:
return cached_menu
data = await self.menu_repo.get_menu_item(menu_id)
if data is None:
return None
@ -61,8 +81,11 @@ class MenuService:
dishes_conter += len(sub.dishes)
menu['submenus_count'] = len(menu.pop('submenus'))
menu['dishes_count'] = dishes_conter
return MenuRead(**menu)
menu = MenuRead(**menu)
await self.cache.set(
self.key('menu', menu_id=str(menu.id)), menu, self.bg_tasks
)
return menu
async def update_menu(self, menu_id: UUID, menu_data) -> MenuRead:
data = await self.menu_repo.update_menu_item(menu_id, menu_data)
@ -74,9 +97,15 @@ class MenuService:
dishes_conter += len(sub.dishes)
menu['submenus_count'] = len(menu.pop('submenus'))
menu['dishes_count'] = dishes_conter
menu = MenuRead(**menu)
await self.cache.set(
self.key('menu', menu_id=str(menu.id)), menu, self.bg_tasks
)
await self.cache.invalidate(key=str(menu_id), bg_task=self.bg_tasks)
return menu
return MenuRead(**menu)
async def del_menu(self, menu_id: UUID) -> int:
async def del_menu(self, menu_id: UUID):
data = await self.menu_repo.delete_menu_item(menu_id)
await self.cache.delete(key=str(menu_id), bg_task=self.bg_tasks)
await self.cache.invalidate(key=str(menu_id), bg_task=self.bg_tasks)
return data

View File

@ -4,7 +4,7 @@ import redis.asyncio as redis # type: ignore
from fastapi import BackgroundTasks, Depends
from fastfood.dbase import get_async_redis_client
from fastfood.repository.redis import RedisRepository
from fastfood.repository.redis import RedisRepository, get_key
from fastfood.repository.submenu import SubMenuRepository
from fastfood.schemas import MenuBase, SubMenuRead
@ -18,10 +18,17 @@ class SubmenuService:
) -> None:
self.submenu_repo = submenu_repo
self.cache_client = RedisRepository(redis_client)
self.background_tasks = background_tasks
self.cache = RedisRepository(redis_client)
self.bg_tasks = background_tasks
self.key = get_key
async def read_submenus(self, menu_id: UUID) -> list[SubMenuRead]:
cached_submenus = await self.cache.get(
self.key('submenus', menu_id=str(menu_id))
)
if cached_submenus is not None:
return cached_submenus
data = await self.submenu_repo.get_submenus(menu_id=menu_id)
submenus = []
for r in data:
@ -31,6 +38,10 @@ class SubmenuService:
submenu['dishes_count'] = len(subq.dishes)
submenu = SubMenuRead(**submenu)
submenus.append(submenu)
await self.cache.set(
self.key('submenus', menu_id=str(menu_id)), submenus, self.bg_tasks
)
return submenus
async def create_submenu(
@ -40,20 +51,40 @@ class SubmenuService:
menu_id,
submenu_data,
)
menu = data.__dict__
menu = {k: v for k, v in menu.items() if not k.startswith('_')}
menu['dishes_count'] = len(menu.pop('dishes'))
menu = SubMenuRead(**menu)
return menu
submenu = data.__dict__
submenu = {k: v for k, v in submenu.items() if not k.startswith('_')}
submenu['dishes_count'] = len(submenu.pop('dishes'))
submenu = SubMenuRead(**submenu)
await self.cache.set(
self.key('submenu', menu_id=str(menu_id)), submenu, self.bg_tasks
)
await self.cache.invalidate(key=str(menu_id), bg_task=self.bg_tasks)
return submenu
async def read_menu(self, menu_id: UUID, submenu_id: UUID) -> SubMenuRead | None:
cached_submenu = await self.cache.get(
self.key(
'submenu',
menu_id=str(menu_id),
submenu_id=str(submenu_id),
)
)
if cached_submenu is not None:
return cached_submenu
data = await self.submenu_repo.get_submenu_item(menu_id, submenu_id)
if data is None:
return None
menu = data.__dict__
menu = {k: v for k, v in menu.items() if not k.startswith('_')}
menu['dishes_count'] = len(menu.pop('dishes'))
menu = SubMenuRead(**menu)
submenu = data.__dict__
submenu = {k: v for k, v in submenu.items() if not k.startswith('_')}
submenu['dishes_count'] = len(submenu.pop('dishes'))
menu = SubMenuRead(**submenu)
await self.cache.set(
self.key('submenu', menu_id=str(menu_id), submenu_id=str(submenu_id)),
submenu,
self.bg_tasks,
)
return menu
async def update_submenu(
@ -62,11 +93,28 @@ class SubmenuService:
data = await self.submenu_repo.update_submenu_item(
menu_id, submenu_id, submenu_data
)
menu = data.__dict__
menu = {k: v for k, v in menu.items() if not k.startswith('_')}
menu['dishes_count'] = len(menu.pop('dishes'))
menu = SubMenuRead(**menu)
return menu
submenu = data.__dict__
submenu = {k: v for k, v in submenu.items() if not k.startswith('_')}
submenu['dishes_count'] = len(submenu.pop('dishes'))
submenu = SubMenuRead(**submenu)
await self.cache.set(
self.key('submenu', menu_id=str(menu_id), submenu_id=str(submenu_id)),
submenu,
self.bg_tasks,
)
await self.cache.invalidate(key=str(menu_id), bg_task=self.bg_tasks)
return submenu
async def del_menu(self, menu_id: UUID, submenu_id: UUID) -> int:
return await self.submenu_repo.delete_submenu_item(menu_id, submenu_id)
code = await self.submenu_repo.delete_submenu_item(menu_id, submenu_id)
await self.cache.delete(
key=self.key(
'submenu',
menu_id=str(menu_id),
submenu_id=str(submenu_id),
),
bg_task=self.bg_tasks,
)
await self.cache.invalidate(key=str(menu_id), bg_task=self.bg_tasks)
return code