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) updated_menu = await self.db.execute(qr)
return updated_menu.scalar_one() 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) query = delete(Menu).where(Menu.id == menu_id)
await self.db.execute(query) await self.db.execute(query)
await self.db.commit() await self.db.commit()
return 200

View File

@ -1,3 +1,4 @@
import pickle
from typing import Any from typing import Any
import redis.asyncio as redis # type: ignore import redis.asyncio as redis # type: ignore
@ -6,19 +7,59 @@ from fastapi import BackgroundTasks, Depends
from fastfood.dbase import get_redis_pool 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: class RedisRepository:
def __init__( def __init__(
self, self,
redis_pool: redis.Redis = Depends(get_redis_pool), pool: redis.Redis = Depends(get_redis_pool),
) -> None: ) -> None:
self.redis_pool = redis_pool self.pool = pool
self.ttl = 1800 self.ttl = 1800
async def get(self, key: str) -> Any | None: 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 return None
async def set( async def set(self, key: str, value: Any, bg_task: BackgroundTasks) -> None:
self, key: str, value: Any, background_tasks: BackgroundTasks data = pickle.dumps(value)
) -> None: bg_task.add_task(self._set_cache, key, data)
pass
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.dbase import get_async_redis_client
from fastfood.repository.dish import DishRepository 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 from fastfood.schemas import Dish, Dish_db, DishBase
@ -17,16 +17,32 @@ class DishService:
background_tasks: BackgroundTasks = None, background_tasks: BackgroundTasks = None,
) -> None: ) -> None:
self.dish_repo = dish_repo self.dish_repo = dish_repo
self.cache_client = RedisRepository(redis_client) self.cache = RedisRepository(redis_client)
self.background_tasks = background_tasks self.bg_tasks = background_tasks
self.key = get_key
async def read_dishes(self, menu_id: UUID, submenu_id: UUID) -> list[Dish]: 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) data = await self.dish_repo.get_dishes(menu_id, submenu_id)
response = [] response = []
for row in data: for row in data:
dish = row.__dict__ dish = row.__dict__
dish['price'] = str(dish['price']) dish['price'] = str(dish['price'])
response.append(Dish(**dish)) 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 return response
async def create_dish( async def create_dish(
@ -35,34 +51,79 @@ class DishService:
submenu_id: UUID, submenu_id: UUID,
dish_data: DishBase, dish_data: DishBase,
) -> Dish: ) -> Dish:
dish = Dish_db(**dish_data.model_dump()) dish_db = Dish_db(**dish_data.model_dump())
data = await self.dish_repo.create_dish_item( data = await self.dish_repo.create_dish_item(
menu_id, menu_id,
submenu_id, submenu_id,
dish, dish_db,
) )
response = data.__dict__ dish = data.__dict__
response['price'] = str(response['price']) dish['price'] = str(dish['price'])
return Dish(**response) 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( async def read_dish(
self, menu_id: UUID, submenu_id: UUID, dish_id: UUID self, menu_id: UUID, submenu_id: UUID, dish_id: UUID
) -> Dish | None: ) -> 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) data = await self.dish_repo.get_dish_item(menu_id, submenu_id, dish_id)
if data is None: if data is None:
return None return None
response = data.__dict__ dish = data.__dict__
response['price'] = str(response['price']) dish['price'] = str(dish['price'])
return Dish(**response) 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( async def update_dish(
self, menu_id: UUID, submenu_id: UUID, dish_id, dish_data: DishBase self, menu_id: UUID, submenu_id: UUID, dish_id, dish_data: DishBase
) -> Dish: ) -> Dish:
dish = Dish_db(**dish_data.model_dump()) dish_db = Dish_db(**dish_data.model_dump())
data = await self.dish_repo.update_dish_item(menu_id, submenu_id, dish_id, dish) data = await self.dish_repo.update_dish_item(
response = data.__dict__ menu_id, submenu_id, dish_id, dish_db
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,
)
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: async def del_dish(self, menu_id: UUID, submenu_id: UUID, dish_id: UUID) -> int:
response = await self.dish_repo.delete_dish_item( response = await self.dish_repo.delete_dish_item(
@ -70,4 +131,7 @@ class DishService:
submenu_id, submenu_id,
dish_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 return response

View File

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

View File

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