diff --git a/dump.rdb b/dump.rdb deleted file mode 100644 index 6be976d..0000000 Binary files a/dump.rdb and /dev/null differ diff --git a/example.env b/example.env index d62dc7a..73ef17e 100644 --- a/example.env +++ b/example.env @@ -4,3 +4,4 @@ POSTGRES_USER=testuser POSTGRES_PASSWORD=test POSTGRES_DB=fastfood_db POSTGRES_DB_TEST=testdb +REDIS_DB=redis://localhost diff --git a/fastfood/app.py b/fastfood/app.py index e4037b8..092928b 100644 --- a/fastfood/app.py +++ b/fastfood/app.py @@ -1,5 +1,4 @@ -from fastapi import FastAPI, Request -from starlette.responses import JSONResponse +from fastapi import FastAPI from fastfood.routers.dish import router as dish_router from fastfood.routers.menu import router as menu_router diff --git a/fastfood/config.py b/fastfood/config.py index 90394d3..58e5577 100644 --- a/fastfood/config.py +++ b/fastfood/config.py @@ -8,6 +8,7 @@ class Settings(BaseSettings): POSTGRES_PASSWORD: str = "" POSTGRES_USER: str = "" POSTGRES_DB_TEST: str = "" + REDIS_DB: str = "" @property def DATABASE_URL_asyncpg(self): diff --git a/fastfood/cruds/dish.py b/fastfood/cruds/dish.py deleted file mode 100644 index 18cb8c4..0000000 --- a/fastfood/cruds/dish.py +++ /dev/null @@ -1,64 +0,0 @@ -from uuid import UUID - -from sqlalchemy import delete, select, update -from sqlalchemy.ext.asyncio import AsyncSession - -from fastfood import models, schemas - - -class DishCrud: - @staticmethod - async def get_dishes(submenu_id: UUID, session: AsyncSession): - async with session: - query = select(models.Dish).where(models.Dish.parent_submenu == submenu_id) - dishes = await session.execute(query) - return dishes.scalars().all() - - @staticmethod - async def create_dish_item( - submenu_id: UUID, - dish: schemas.DishBase, - session: AsyncSession, - ): - async with session: - new_dish = models.Dish(**dish.model_dump()) - new_dish.parent_submenu = submenu_id - session.add(new_dish) - await session.flush() - await session.commit() - return new_dish - - @staticmethod - async def get_dish_item( - dish_id: UUID, - session: AsyncSession, - ): - async with session: - query = select(models.Dish).where(models.Dish.id == dish_id) - submenu = await session.execute(query) - return submenu.scalars().one_or_none() - - @staticmethod - async def update_dish_item( - dish_id: UUID, - dish: schemas.DishBase, - session: AsyncSession, - ): - async with session: - query = ( - update(models.Dish) - .where(models.Dish.id == dish_id) - .values(**dish.model_dump()) - ) - await session.execute(query) - await session.commit() - qr = select(models.Dish).where(models.Dish.id == dish_id) - updated_submenu = await session.execute(qr) - return updated_submenu.scalars().one() - - @staticmethod - async def delete_dish_item(dish_id: UUID, session: AsyncSession): - async with session: - query = delete(models.Dish).where(models.Dish.id == dish_id) - await session.execute(query) - await session.commit() diff --git a/fastfood/cruds/redis_cache.py b/fastfood/cruds/redis_cache.py deleted file mode 100644 index 55b01e2..0000000 --- a/fastfood/cruds/redis_cache.py +++ /dev/null @@ -1,59 +0,0 @@ -import pickle -from typing import Any - -import redis.asyncio as redis -from fastapi import BackgroundTasks, Depends - - -def get_async_redis_pool(): - redis_url = "redis://localhost" - return redis.from_url(redis_url, decode_responses=False) - - -async def get_async_redis_client( - redis_pool: redis.Redis = Depends(get_async_redis_pool), -): - return redis_pool - - -class AsyncRedisCache: - def __init__(self, redis_pool: redis.Redis = Depends(get_async_redis_pool)) -> None: - self.redis_pool = redis_pool - self.ttl = 1800 - - async def get(self, key: str) -> Any | None: - data = await self.redis_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: - data = pickle.dumps(value) - background_tasks.add_task(self._set_cache, key, data) - - async def _set_cache(self, key: str, data: Any) -> None: - await self.redis_pool.setex(key, self.ttl, data) - - async def delete(self, key: str, background_tasks: BackgroundTasks) -> None: - background_tasks.add_task(self._delete_cache, key) - - async def _delete_cache(self, key: str) -> None: - await self.redis_pool.delete(key) - - async def clear_cache( - self, pattern: str, background_tasks: BackgroundTasks - ) -> None: - keys = [key async for key in self.redis_pool.scan_iter(pattern)] - if keys: - background_tasks.add_task(self._clear_keys, keys) - - async def _clear_keys(self, keys: list[str]) -> None: - await self.redis_pool.delete(*keys) - - async def clear_after_change( - self, menu_id: int | str, background_tasks: BackgroundTasks - ) -> None: - await self.clear_cache(f"{str(menu_id)}*", background_tasks) - await self.clear_cache("all*", background_tasks) diff --git a/fastfood/cruds/submenu.py b/fastfood/cruds/submenu.py deleted file mode 100644 index db562d9..0000000 --- a/fastfood/cruds/submenu.py +++ /dev/null @@ -1,80 +0,0 @@ -from uuid import UUID - -from sqlalchemy import delete, distinct, func, select, update -from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.orm import aliased - -from fastfood import models, schemas - - -class SubMenuCrud: - @staticmethod - async def get_submenus(menu_id: UUID, session: AsyncSession): - async with session: - query = select(models.SubMenu).where( - models.SubMenu.parent_menu == menu_id, - ) - submenus = await session.execute(query) - return submenus - - @staticmethod - async def create_submenu_item( - menu_id: UUID, - submenu: schemas.MenuBase, - session: AsyncSession, - ): - async with session: - new_submenu = models.SubMenu(**submenu.model_dump()) - new_submenu.parent_menu = menu_id - session.add(new_submenu) - await session.commit() - await session.refresh(new_submenu) - return new_submenu - - @staticmethod - async def get_submenu_item( - menu_id: UUID, - submenu_id: UUID, - session: AsyncSession, - ): - async with session: - s = aliased(models.SubMenu) - d = aliased(models.Dish) - query = ( - select(s, func.count(distinct(d.id))) - .join(d, s.id == d.parent_submenu, isouter=True) - .group_by(s.id) - .where(s.id == submenu_id) - ) - submenu = await session.execute(query) - submenu = submenu.scalars().one_or_none() - if submenu is None: - return None - return submenu - - @staticmethod - async def update_submenu_item( - submenu_id: UUID, - submenu: schemas.MenuBase, - session: AsyncSession, - ): - async with session: - query = ( - update(models.SubMenu) - .where(models.SubMenu.id == submenu_id) - .values(**submenu.model_dump()) - ) - await session.execute(query) - await session.commit() - qr = select(models.SubMenu).where(models.SubMenu.id == submenu_id) - updated_submenu = await session.execute(qr) - return updated_submenu - - @staticmethod - async def delete_submenu_item(submenu_id: UUID, session: AsyncSession): - async with session: - query = delete(models.SubMenu).where( - models.SubMenu.id == submenu_id, - ) - await session.execute(query) - await session.commit() diff --git a/fastfood/dbase.py b/fastfood/dbase.py index eb49cca..5d3e5b9 100644 --- a/fastfood/dbase.py +++ b/fastfood/dbase.py @@ -1,7 +1,8 @@ from typing import AsyncGenerator -from sqlalchemy.ext.asyncio import (AsyncSession, async_sessionmaker, - create_async_engine) +import redis.asyncio as redis +from fastapi import Depends +from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from fastfood.config import settings @@ -16,3 +17,13 @@ async_session_maker = async_sessionmaker( async def get_async_session() -> AsyncGenerator[AsyncSession, None]: async with async_session_maker() as session: yield session + + +def get_redis_pool(): + return redis.from_url(settings.REDIS_DB, decode_responses=False) + + +async def get_async_redis_client( + redis_pool: redis.Redis = Depends(get_redis_pool), +): + return redis_pool diff --git a/fastfood/cruds/__init__.py b/fastfood/repository/__init__.py similarity index 59% rename from fastfood/cruds/__init__.py rename to fastfood/repository/__init__.py index 155d4be..6b9f335 100644 --- a/fastfood/cruds/__init__.py +++ b/fastfood/repository/__init__.py @@ -1,9 +1,9 @@ from fastfood import models from fastfood.dbase import async_engine -from .dish import DishCrud -from .menu import MenuCrud -from .submenu import SubMenuCrud +from .dish import DishRepository +from .menu import MenuRepository +from .submenu import SubMenuRepository async def create_db_and_tables(): @@ -12,8 +12,8 @@ async def create_db_and_tables(): await conn.run_sync(models.Base.metadata.create_all) -class Crud(MenuCrud, SubMenuCrud, DishCrud): +class Repository(MenuRepository, SubMenuRepository, DishRepository): pass -crud = Crud() +ropo = Repository() diff --git a/fastfood/repository/dish.py b/fastfood/repository/dish.py new file mode 100644 index 0000000..19c96a1 --- /dev/null +++ b/fastfood/repository/dish.py @@ -0,0 +1,72 @@ +from uuid import UUID + +from fastapi import Depends +from sqlalchemy import delete, select, update +from sqlalchemy.ext.asyncio import AsyncSession + +from fastfood import models +from fastfood.dbase import get_async_session +from fastfood.schemas import DishBase + + +class DishRepository: + def __init__(self, session: AsyncSession = Depends(get_async_session)): + self.db = session + + async def get_dishes(self, menu_id: UUID, submenu_id: UUID): + query = select(models.Dish).where( + models.Dish.parent_submenu == submenu_id, + ) + dishes = await self.db.execute(query) + return dishes.scalars().all() + + async def create_dish_item( + self, + menu_id: UUID, + submenu_id: UUID, + dish_data: DishBase, + ): + new_dish = models.Dish(**dish_data.model_dump()) + new_dish.parent_submenu = submenu_id + self.db.add(new_dish) + await self.db.flush() + await self.db.commit() + return new_dish + + async def get_dish_item( + self, + menu_id: UUID, + submenu_id: UUID, + dish_id: UUID, + ): + query = select(models.Dish).where(models.Dish.id == dish_id) + submenu = await self.db.execute(query) + return submenu.scalars().one_or_none() + + async def update_dish_item( + self, + menu_id: UUID, + submenu_id: UUID, + dish_id: UUID, + dish_data: DishBase, + ): + query = ( + update(models.Dish) + .where(models.Dish.id == dish_id) + .values(**dish_data.model_dump()) + ) + await self.db.execute(query) + await self.db.commit() + qr = select(models.Dish).where(models.Dish.id == dish_id) + updated_submenu = await self.db.execute(qr) + return updated_submenu.scalars().one() + + async def delete_dish_item( + self, + menu_id: UUID, + submenu_id: UUID, + dish_id: UUID, + ): + query = delete(models.Dish).where(models.Dish.id == dish_id) + await self.db.execute(query) + await self.db.commit() diff --git a/fastfood/cruds/menu.py b/fastfood/repository/menu.py similarity index 97% rename from fastfood/cruds/menu.py rename to fastfood/repository/menu.py index ea6b9bd..818947a 100644 --- a/fastfood/cruds/menu.py +++ b/fastfood/repository/menu.py @@ -1,4 +1,3 @@ -from typing import Annotated from uuid import UUID from fastapi import Depends @@ -10,7 +9,7 @@ from fastfood import models, schemas from fastfood.dbase import get_async_session -class MenuCrud: +class MenuRepository: def __init__(self, session: AsyncSession = Depends(get_async_session)): self.db = session diff --git a/fastfood/repository/redis.py b/fastfood/repository/redis.py new file mode 100644 index 0000000..1e1a08b --- /dev/null +++ b/fastfood/repository/redis.py @@ -0,0 +1,24 @@ +from typing import Any + +import redis.asyncio as redis +from fastapi import BackgroundTasks, Depends + +from fastfood.dbase import get_redis_pool + + +class RedisRepository: + def __init__( + self, + redis_pool: redis.Redis = Depends(get_redis_pool), + ) -> None: + self.redis_pool = redis_pool + self.ttl = 1800 + + async def get(self, key: str) -> Any | None: + + return None + + async def set( + self, key: str, value: Any, background_tasks: BackgroundTasks + ) -> None: + pass diff --git a/fastfood/repository/submenu.py b/fastfood/repository/submenu.py new file mode 100644 index 0000000..083757e --- /dev/null +++ b/fastfood/repository/submenu.py @@ -0,0 +1,76 @@ +from uuid import UUID + +from fastapi import Depends +from sqlalchemy import delete, distinct, func, select, update +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import aliased + +from fastfood import models, schemas +from fastfood.dbase import get_async_session + + +class SubMenuRepository: + def __init__(self, session: AsyncSession = Depends(get_async_session)): + self.db = session + + async def get_submenus(self, menu_id: UUID): + query = select(models.SubMenu).where( + models.SubMenu.parent_menu == menu_id, + ) + submenus = await self.db.execute(query) + return submenus + + async def create_submenu_item( + self, + menu_id: UUID, + submenu: schemas.MenuBase, + ): + new_submenu = models.SubMenu(**submenu.model_dump()) + new_submenu.parent_menu = menu_id + self.db.add(new_submenu) + await self.db.commit() + await self.db.refresh(new_submenu) + return new_submenu + + async def get_submenu_item( + self, + menu_id: UUID, + submenu_id: UUID, + ): + s = aliased(models.SubMenu) + d = aliased(models.Dish) + query = ( + select(s, func.count(distinct(d.id)).label("dishes_count")) + .join(d, s.id == d.parent_submenu, isouter=True) + .group_by(s.id) + .where(s.id == submenu_id) + ) + submenu = await self.db.execute(query) + submenu = submenu.scalars().one_or_none() + if submenu is None: + return None + return submenu + + async def update_submenu_item( + self, + menu_id: UUID, + submenu_id: UUID, + submenu_data: schemas.MenuBase, + ): + query = ( + update(models.SubMenu) + .where(models.SubMenu.id == submenu_id) + .values(**submenu_data.model_dump()) + ) + await self.db.execute(query) + await self.db.commit() + qr = select(models.SubMenu).where(models.SubMenu.id == submenu_id) + updated_submenu = await self.db.execute(qr) + return updated_submenu + + async def delete_submenu_item(self, menu_id: UUID, submenu_id: UUID): + query = delete(models.SubMenu).where( + models.SubMenu.id == submenu_id, + ) + await self.db.execute(query) + await self.db.commit() diff --git a/fastfood/routers/dish.py b/fastfood/routers/dish.py index 9007cef..05b9dd4 100644 --- a/fastfood/routers/dish.py +++ b/fastfood/routers/dish.py @@ -1,12 +1,9 @@ -from typing import List, Optional from uuid import UUID -from fastapi import APIRouter, Depends, HTTPException -from sqlalchemy.ext.asyncio import AsyncSession +from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException from fastfood import schemas -from fastfood.cruds import crud -from fastfood.dbase import get_async_session +from fastfood.service.dish import DishService from fastfood.utils import price_converter router = APIRouter( @@ -17,9 +14,12 @@ router = APIRouter( @router.get("/") async def get_dishes( - menu_id: UUID, submenu_id: UUID, session: AsyncSession = Depends(get_async_session) + menu_id: UUID, + submenu_id: UUID, + dish: DishService = Depends(), + background_tasks: BackgroundTasks = BackgroundTasks(), ): - result = await crud.get_dishes(submenu_id=submenu_id, session=session) + result = await dish.read_dishes(menu_id, submenu_id) return result @@ -27,13 +27,14 @@ async def get_dishes( async def create_dish( menu_id: UUID, submenu_id: UUID, - dish: schemas.DishBase, - session: AsyncSession = Depends(get_async_session), + dish_data: schemas.DishBase, + dish: DishService = Depends(), + background_tasks: BackgroundTasks = BackgroundTasks(), ): - result = await crud.create_dish_item( - submenu_id=submenu_id, - dish=dish, - session=session, + result = await dish.create_dish( + menu_id, + submenu_id, + dish_data, ) return price_converter(result) @@ -43,11 +44,13 @@ async def get_dish( menu_id: UUID, submenu_id: UUID, dish_id: UUID, - session: AsyncSession = Depends(get_async_session), + dish: DishService = Depends(), + background_tasks: BackgroundTasks = BackgroundTasks(), ): - result = await crud.get_dish_item( - dish_id=dish_id, - session=session, + result = await dish.read_dish( + menu_id, + submenu_id, + dish_id, ) if not result: raise HTTPException(status_code=404, detail="dish not found") @@ -59,13 +62,15 @@ async def update_dish( menu_id: UUID, submenu_id: UUID, dish_id: UUID, - dish: schemas.DishBase, - session: AsyncSession = Depends(get_async_session), + dish_data: schemas.DishBase, + dish: DishService = Depends(), + background_tasks: BackgroundTasks = BackgroundTasks(), ): - result = await crud.update_dish_item( - dish_id=dish_id, - dish=dish, - session=session, + result = await dish.update_dish( + menu_id, + submenu_id, + dish_id, + dish_data, ) return price_converter(result) @@ -75,6 +80,7 @@ async def delete_dish( menu_id: UUID, submenu_id: UUID, dish_id: UUID, - session: AsyncSession = Depends(get_async_session), + dish: DishService = Depends(), + background_tasks: BackgroundTasks = BackgroundTasks(), ): - await crud.delete_dish_item(dish_id=dish_id, session=session) + await dish.del_dish(menu_id, submenu_id, dish_id) diff --git a/fastfood/routers/menu.py b/fastfood/routers/menu.py index 71d10ae..bb7045d 100644 --- a/fastfood/routers/menu.py +++ b/fastfood/routers/menu.py @@ -2,11 +2,8 @@ from typing import List, Optional from uuid import UUID from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException -from sqlalchemy.ext.asyncio import AsyncSession -from fastfood import schemas -from fastfood.cruds import crud -from fastfood.dbase import get_async_session +from fastfood.schemas import Menu, MenuBase, MenuRead from fastfood.service.menu import MenuService router = APIRouter( @@ -15,7 +12,7 @@ router = APIRouter( ) -@router.get("/", response_model=Optional[List[schemas.Menu]]) +@router.get("/", response_model=Optional[List[Menu]]) async def get_menus( menu: MenuService = Depends(), background_tasks: BackgroundTasks = BackgroundTasks(), @@ -23,16 +20,16 @@ async def get_menus( return await menu.read_menus() -@router.post("/", status_code=201, response_model=schemas.Menu) +@router.post("/", status_code=201, response_model=Menu) async def add_menu( - menu: schemas.MenuBase, + menu: MenuBase, responce: MenuService = Depends(), background_tasks: BackgroundTasks = BackgroundTasks(), ): return await responce.create_menu(menu) -@router.get("/{menu_id}", response_model=schemas.MenuRead) +@router.get("/{menu_id}", response_model=MenuRead) async def get_menu( menu_id: UUID, responce: MenuService = Depends(), @@ -45,10 +42,10 @@ async def get_menu( return result -@router.patch("/{menu_id}", response_model=schemas.MenuBase) +@router.patch("/{menu_id}", response_model=MenuRead) async def update_menu( menu_id: UUID, - menu: schemas.MenuBase, + menu: MenuBase, responce: MenuService = Depends(), background_tasks: BackgroundTasks = BackgroundTasks(), ): @@ -59,10 +56,10 @@ async def update_menu( return result.scalars().one() -# -# @router.delete("/{menu_id}") -# async def delete_menu( -# menu_id: UUID, -# session: AsyncSession = Depends(get_async_session), -# ): -# await crud.delete_menu_item(menu_id=menu_id, session=session) +@router.delete("/{menu_id}") +async def delete_menu( + menu_id: UUID, + menu: MenuService = Depends(), + background_tasks: BackgroundTasks = BackgroundTasks(), +): + await menu.del_menu(menu_id) diff --git a/fastfood/routers/submenu.py b/fastfood/routers/submenu.py index b322532..2e1578d 100644 --- a/fastfood/routers/submenu.py +++ b/fastfood/routers/submenu.py @@ -1,11 +1,10 @@ +from typing import List, Optional from uuid import UUID -from fastapi import APIRouter, Depends, HTTPException -from sqlalchemy.ext.asyncio import AsyncSession +from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException -from fastfood import schemas -from fastfood.cruds import crud -from fastfood.dbase import get_async_session +from fastfood.schemas import MenuBase, SubMenuRead +from fastfood.service.submenu import SubmenuService router = APIRouter( prefix="/api/v1/menus/{menu_id}/submenus", @@ -13,38 +12,40 @@ router = APIRouter( ) -@router.get("/") +@router.get("/", response_model=Optional[List[SubMenuRead]]) async def get_submenus( - menu_id: UUID, session: AsyncSession = Depends(get_async_session) + menu_id: UUID, + submenu: SubmenuService = Depends(), + background_tasks: BackgroundTasks = BackgroundTasks(), ): - result = await crud.get_submenus(menu_id=menu_id, session=session) + result = await submenu.read_submenus(menu_id=menu_id) return result.scalars().all() -@router.post("/", status_code=201) +@router.post("/", status_code=201, response_model=SubMenuRead) async def create_submenu_item( menu_id: UUID, - submenu: schemas.MenuBase, - session: AsyncSession = Depends(get_async_session), + submenu_data: MenuBase, + submenu: SubmenuService = Depends(), + background_tasks: BackgroundTasks = BackgroundTasks(), ): - result = await crud.create_submenu_item( + result = await submenu.create_submenu( menu_id=menu_id, - submenu=submenu, - session=session, + submenu_data=submenu_data, ) return result -@router.get("/{submenu_id}", response_model=schemas.SubMenuRead) +@router.get("/{submenu_id}", response_model=SubMenuRead) async def get_submenu( menu_id: UUID, submenu_id: UUID, - session: AsyncSession = Depends(get_async_session), + submenu: SubmenuService = Depends(), + background_tasks: BackgroundTasks = BackgroundTasks(), ): - result = await crud.get_submenu_item( + result = await submenu.read_menu( menu_id=menu_id, submenu_id=submenu_id, - session=session, ) if not result: raise HTTPException(status_code=404, detail="submenu not found") @@ -53,24 +54,28 @@ async def get_submenu( @router.patch( "/{submenu_id}", - response_model=schemas.MenuBase, + response_model=MenuBase, ) async def update_submenu( menu_id: UUID, submenu_id: UUID, - submenu: schemas.MenuBase, - session: AsyncSession = Depends(get_async_session), + submenu_data: MenuBase, + submenu: SubmenuService = Depends(), + background_tasks: BackgroundTasks = BackgroundTasks(), ): - result = await crud.update_submenu_item( + result = await submenu.update_submenu( + menu_id=menu_id, submenu_id=submenu_id, - submenu=submenu, - session=session, + submenu_data=submenu_data, ) return result.scalars().one() @router.delete("/{submenu_id}") async def delete_submenu( - menu_id: UUID, submenu_id: UUID, session: AsyncSession = Depends(get_async_session) + menu_id: UUID, + submenu_id: UUID, + submenu: SubmenuService = Depends(), + background_tasks: BackgroundTasks = BackgroundTasks(), ): - await crud.delete_submenu_item(submenu_id=submenu_id, session=session) + await submenu.del_menu(menu_id=menu_id, submenu_id=submenu_id) diff --git a/fastfood/service/dish.py b/fastfood/service/dish.py new file mode 100644 index 0000000..57962be --- /dev/null +++ b/fastfood/service/dish.py @@ -0,0 +1,58 @@ +from uuid import UUID + +import redis.asyncio as redis +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.schemas import DishBase + + +class DishService: + def __init__( + self, + dish_repo: DishRepository = Depends(), + redis_client: redis.Redis = Depends(get_async_redis_client), + background_tasks: BackgroundTasks = None, + ): + self.dish_repo = dish_repo + self.cache_client = RedisRepository(redis_client) + self.background_tasks = background_tasks + + async def read_dishes(self, menu_id: UUID, submenu_id: UUID): + data = await self.dish_repo.get_dishes(menu_id, submenu_id) + return data + + async def create_dish( + self, + menu_id: UUID, + submenu_id: UUID, + dish_data: DishBase, + ): + data = await self.dish_repo.create_dish_item( + menu_id, + submenu_id, + dish_data, + ) + return data + + async def read_dish(self, menu_id: UUID, submenu_id: UUID, dish_id: UUID): + data = await self.dish_repo.get_dish_item(menu_id, submenu_id, dish_id) + return data + + async def update_dish( + self, menu_id: UUID, submenu_id: UUID, dish_id, dish_data: DishBase + ): + data = await self.dish_repo.update_dish_item( + menu_id, submenu_id, dish_id, dish_data + ) + return data + + async def del_dish(self, menu_id: UUID, submenu_id: UUID, dish_id: UUID): + data = await self.dish_repo.delete_dish_item( + menu_id, + submenu_id, + dish_id, + ) + return data diff --git a/fastfood/service/menu.py b/fastfood/service/menu.py index 871166f..f6be1b8 100644 --- a/fastfood/service/menu.py +++ b/fastfood/service/menu.py @@ -1,66 +1,41 @@ -import pickle from uuid import UUID -import redis.asyncio as redis # type: ignore +import redis.asyncio as redis from fastapi import BackgroundTasks, Depends -from fastfood.cruds.menu import MenuCrud -from fastfood.cruds.redis_cache import AsyncRedisCache, get_async_redis_client -from fastfood.schemas import MenuRead +from fastfood.dbase import get_async_redis_client +from fastfood.repository.menu import MenuRepository +from fastfood.repository.redis import RedisRepository +from fastfood.schemas import MenuBase class MenuService: def __init__( self, - menu_crud: MenuCrud = Depends(), + menu_repo: MenuRepository = Depends(), redis_client: redis.Redis = Depends(get_async_redis_client), background_tasks: BackgroundTasks = None, ): - self.menu_crud = menu_crud - self.cache_client = AsyncRedisCache(redis_client) + self.menu_repo = menu_repo + self.cache_client = RedisRepository(redis_client) self.background_tasks = background_tasks async def read_menus(self): - cached = await self.cache_client.get("all") - - if cached: - return cached - - data = await self.menu_crud.get_menus() - print("not cached", data) - await self.cache_client.set("all", data, self.background_tasks) + data = await self.menu_repo.get_menus() return data - async def create_menu(self, menu_data): - data = await self.menu_crud.create_menu_item(menu_data) - await self.cache_client.set(str(data.id), data, self.background_tasks) - await self.cache_client.clear_after_change(str(data.id), self.background_tasks) + async def create_menu(self, menu_data: MenuBase): + data = await self.menu_repo.create_menu_item(menu_data) return data async def read_menu(self, menu_id: UUID): - cached = await self.cache_client.get(str(menu_id)) - if cached is not None: - return cached - - data = await self.menu_crud.get_menu_item(menu_id) - await self.cache_client.set(str(menu_id), data, self.background_tasks) + data = await self.menu_repo.get_menu_item(menu_id) return data async def update_menu(self, menu_id: UUID, menu_data): - data = await self.menu_crud.update_menu_item(menu_id, menu_data) - await self.cache_client.set(str(menu_id), data, self.background_tasks) - await self.cache_client.clear_after_change(str(menu_id), self.background_tasks) + data = await self.menu_repo.update_menu_item(menu_id, menu_data) return data - # async def del_menu(self, menu_id: int | str): - # data = await self.menu_crud.del_menu(menu_id) - # await self.cache_client.delete(f'{menu_id}', self.background_tasks) - # await self.cache_client.clear_after_change(menu_id, self.background_tasks) - # return data - # - # async def orm_read_menu(self, menu_id: int | str): - # return await self.menu_crud.orm_read_menu(menu_id) - # - # async def read_full_menus(self): - # menu_data = await self.menu_crud.get_full_menus() - # return FullMenuListResponse(menus=menu_data) + async def del_menu(self, menu_id: UUID): + data = await self.menu_repo.delete_menu_item(menu_id) + return data diff --git a/fastfood/service/submenu.py b/fastfood/service/submenu.py new file mode 100644 index 0000000..c415469 --- /dev/null +++ b/fastfood/service/submenu.py @@ -0,0 +1,48 @@ +from uuid import UUID + +import redis.asyncio as redis +from fastapi import BackgroundTasks, Depends + +from fastfood.dbase import get_async_redis_client +from fastfood.repository.redis import RedisRepository +from fastfood.repository.submenu import SubMenuRepository +from fastfood.schemas import MenuBase + + +class SubmenuService: + def __init__( + self, + submenu_repo: SubMenuRepository = Depends(), + redis_client: redis.Redis = Depends(get_async_redis_client), + background_tasks: BackgroundTasks = None, + ): + self.submenu_repo = submenu_repo + self.cache_client = RedisRepository(redis_client) + self.background_tasks = background_tasks + + async def read_submenus(self, menu_id: UUID): + data = await self.submenu_repo.get_submenus(menu_id=menu_id) + return data + + async def create_submenu(self, menu_id: UUID, submenu_data: MenuBase): + data = await self.submenu_repo.create_submenu_item( + menu_id, + submenu_data, + ) + return data + + async def read_menu(self, menu_id: UUID, submenu_id: UUID): + data = await self.submenu_repo.get_submenu_item(menu_id, submenu_id) + return data + + async def update_submenu( + self, menu_id: UUID, submenu_id: UUID, submenu_data: MenuBase + ): + data = await self.submenu_repo.update_submenu_item( + menu_id, submenu_id, submenu_data + ) + return data + + async def del_menu(self, menu_id: UUID, submenu_id: UUID): + data = await self.submenu_repo.delete_submenu_item(menu_id, submenu_id) + return data diff --git a/manage.py b/manage.py index 7a44d5f..b4778d1 100644 --- a/manage.py +++ b/manage.py @@ -3,7 +3,7 @@ import sys import uvicorn -from fastfood.cruds import create_db_and_tables +from fastfood.repository import create_db_and_tables def run_app(): diff --git a/tests/conftest.py b/tests/conftest.py index bfc702e..4ade2b0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,12 +1,11 @@ import asyncio -from typing import AsyncGenerator, Generator +from typing import AsyncGenerator, Dict, Generator import pytest import pytest_asyncio from fastapi import FastAPI from httpx import AsyncClient -from sqlalchemy.ext.asyncio import (AsyncSession, async_sessionmaker, - create_async_engine) +from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from fastfood.app import create_app from fastfood.config import settings @@ -31,7 +30,7 @@ def event_loop(): loop.close() -@pytest_asyncio.fixture(scope="function", autouse=True) +@pytest_asyncio.fixture(scope="session", autouse=True) async def db_init(event_loop): async with async_engine.begin() as conn: await conn.run_sync(Base.metadata.drop_all) @@ -60,3 +59,8 @@ async def client(app) -> AsyncGenerator[AsyncClient, None]: base_url="http://localhost:8000/api/v1/menus", ) as async_client: yield async_client + + +@pytest.fixture(scope="session") +def session_data() -> Dict: + return {} diff --git a/tests/repository.py b/tests/repository.py new file mode 100644 index 0000000..8c1c182 --- /dev/null +++ b/tests/repository.py @@ -0,0 +1,150 @@ +from typing import Tuple + +from httpx import AsyncClient, Response + + +class Repository: + class Menu: + @staticmethod + async def read_all(ac: AsyncClient) -> Tuple[int, dict]: + """чтение всех меню""" + + response: Response = await ac.get("/") + return response.status_code, response.json() + + @staticmethod + async def get(ac: AsyncClient, data: dict) -> Tuple[int, dict]: + """Получение меню по id""" + response: Response = await ac.get(f"/{data.get('id')}") + return response.status_code, response.json() + + @staticmethod + async def write(ac: AsyncClient, data: dict) -> Tuple[int, dict]: + """создания меню""" + response: Response = await ac.post("/", json=data) + return response.status_code, response.json() + + @staticmethod + async def update(ac: AsyncClient, data: dict) -> Tuple[int, dict]: + """Обновление меню по id""" + response: Response = await ac.patch( + f"/{data.get('id')}", + json=data, + ) + return response.status_code, response.json() + + @staticmethod + async def delete(ac: AsyncClient, data: dict) -> int: + """Удаление меню по id""" + response: Response = await ac.delete(f"/{data.get('id')}") + return response.status_code + + class Submenu: + @staticmethod + async def read_all(ac: AsyncClient, menu: dict) -> Tuple[int, dict]: + """чтение всех меню""" + response: Response = await ac.get(f"/{menu.get('id')}/submenus/") + return response.status_code, response.json() + + @staticmethod + async def get( + ac: AsyncClient, + menu: dict, + submenu: dict, + ) -> Tuple[int, dict]: + """Получение меню по id""" + response: Response = await ac.get( + f"/{menu.get('id')}/submenus/{submenu.get('id')}", + ) + return response.status_code, response.json() + + @staticmethod + async def write( + ac: AsyncClient, + menu: dict, + submenu: dict, + ) -> Tuple[int, dict]: + """создания меню""" + response: Response = await ac.post( + f"/{menu.get('id')}/submenus/", + json=submenu, + ) + return response.status_code, response.json() + + @staticmethod + async def update( + ac: AsyncClient, menu: dict, submenu: dict + ) -> Tuple[int, dict]: + """Обновление меню по id""" + response: Response = await ac.patch( + f"/{menu.get('id')}/submenus/{submenu.get('id')}", + json=submenu, + ) + return response.status_code, response.json() + + @staticmethod + async def delete(ac: AsyncClient, menu: dict, submenu: dict) -> int: + """Удаление меню по id""" + response: Response = await ac.delete( + f"/{menu.get('id')}/submenus/{submenu.get('id')}" + ) + return response.status_code + + class Dish: + @staticmethod + async def read_all( + ac: AsyncClient, menu: dict, submenu: dict + ) -> Tuple[int, dict]: + """чтение всех блюд""" + response: Response = await ac.get( + f"/{menu.get('id')}/submenus/{submenu.get('id')}/dishes/", + ) + return response.status_code, response.json() + + @staticmethod + async def get( + ac: AsyncClient, menu: dict, submenu: dict, dish: dict + ) -> Tuple[int, dict]: + """Получение блюда по id""" + response: Response = await ac.get( + f"/{menu.get('id')}/submenus/{submenu.get('id')}" + f"/dishes/{dish.get('id')}", + ) + return response.status_code, response.json() + + @staticmethod + async def write( + ac: AsyncClient, menu: dict, submenu: dict, dish: dict + ) -> Tuple[int, dict]: + """создания блюда""" + response: Response = await ac.post( + f"/{menu.get('id')}/submenus/{submenu.get('id')}/dishes/", + json=dish, + ) + return response.status_code, response.json() + + @staticmethod + async def update( + ac: AsyncClient, menu: dict, submenu: dict, dish: dict + ) -> Tuple[int, dict]: + """Обновление блюда по id""" + response: Response = await ac.patch( + f"/{menu.get('id')}/submenus/{submenu.get('id')}" + f"/dishes/{dish.get('id')}", + json=dish, + ) + return response.status_code, response.json() + + @staticmethod + async def delete( + ac: AsyncClient, + menu: dict, + submenu: dict, + dish: dict, + ) -> int: + """Удаление блюда по id""" + response: Response = await ac.delete( + f"/{menu.get('id')}/submenus/{submenu.get('id')}" + f"/dishes/{dish.get('id')}" + ) + return response.status_code diff --git a/tests/test_api.py b/tests/test_api.py index 6aedb6b..a13c20e 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,414 +1,360 @@ -from typing import Tuple - import pytest -from httpx import AsyncClient, Response +from httpx import AsyncClient - -class TestBaseCrud: - class Menu: - @staticmethod - async def read_all(ac: AsyncClient) -> Tuple[int, dict]: - """чтение всех меню""" - - response: Response = await ac.get("/") - return response.status_code, response.json() - - @staticmethod - async def get(ac: AsyncClient, data: dict) -> Tuple[int, dict]: - """Получение меню по id""" - response: Response = await ac.get(f"/{data.get('id')}") - return response.status_code, response.json() - - @staticmethod - async def write(ac: AsyncClient, data: dict) -> Tuple[int, dict]: - """создания меню""" - response: Response = await ac.post("/", json=data) - return response.status_code, response.json() - - @staticmethod - async def update(ac: AsyncClient, data: dict) -> Tuple[int, dict]: - """Обновление меню по id""" - response: Response = await ac.patch( - f"/{data.get('id')}", - json=data, - ) - return response.status_code, response.json() - - @staticmethod - async def delete(ac: AsyncClient, data: dict) -> int: - """Удаление меню по id""" - response: Response = await ac.delete(f"/{data.get('id')}") - return response.status_code - - class Submenu: - @staticmethod - async def read_all(ac: AsyncClient, menu: dict) -> Tuple[int, dict]: - """чтение всех меню""" - response: Response = await ac.get(f"/{menu.get('id')}/submenus/") - return response.status_code, response.json() - - @staticmethod - async def get( - ac: AsyncClient, - menu: dict, - submenu: dict, - ) -> Tuple[int, dict]: - """Получение меню по id""" - response: Response = await ac.get( - f"/{menu.get('id')}/submenus/{submenu.get('id')}", - ) - return response.status_code, response.json() - - @staticmethod - async def write( - ac: AsyncClient, - menu: dict, - submenu: dict, - ) -> Tuple[int, dict]: - """создания меню""" - response: Response = await ac.post( - f"/{menu.get('id')}/submenus/", - json=submenu, - ) - return response.status_code, response.json() - - @staticmethod - async def update( - ac: AsyncClient, menu: dict, submenu: dict - ) -> Tuple[int, dict]: - """Обновление меню по id""" - response: Response = await ac.patch( - f"/{menu.get('id')}/submenus/{submenu.get('id')}", - json=submenu, - ) - return response.status_code, response.json() - - @staticmethod - async def delete(ac: AsyncClient, menu: dict, submenu: dict) -> int: - """Удаление меню по id""" - response: Response = await ac.delete( - f"/{menu.get('id')}/submenus/{submenu.get('id')}" - ) - return response.status_code - - class Dish: - @staticmethod - async def read_all( - ac: AsyncClient, menu: dict, submenu: dict - ) -> Tuple[int, dict]: - """чтение всех блюд""" - response: Response = await ac.get( - f"/{menu.get('id')}/submenus/{submenu.get('id')}/dishes/", - ) - return response.status_code, response.json() - - @staticmethod - async def get( - ac: AsyncClient, menu: dict, submenu: dict, dish: dict - ) -> Tuple[int, dict]: - """Получение блюда по id""" - response: Response = await ac.get( - f"/{menu.get('id')}/submenus/{submenu.get('id')}" - f"/dishes/{dish.get('id')}", - ) - return response.status_code, response.json() - - @staticmethod - async def write( - ac: AsyncClient, menu: dict, submenu: dict, dish: dict - ) -> Tuple[int, dict]: - """создания блюда""" - response: Response = await ac.post( - f"/{menu.get('id')}/submenus/{submenu.get('id')}/dishes/", - json=dish, - ) - return response.status_code, response.json() - - @staticmethod - async def update( - ac: AsyncClient, menu: dict, submenu: dict, dish: dict - ) -> Tuple[int, dict]: - """Обновление блюда по id""" - response: Response = await ac.patch( - f"/{menu.get('id')}/submenus/{submenu.get('id')}" - f"/dishes/{dish.get('id')}", - json=dish, - ) - return response.status_code, response.json() - - @staticmethod - async def delete(ac: AsyncClient, menu: dict, submenu: dict, dish: dict) -> int: - """Удаление блюда по id""" - response: Response = await ac.delete( - f"/{menu.get('id')}/submenus/{submenu.get('id')}" - f"/dishes/{dish.get('id')}" - ) - return response.status_code +from .repository import Repository as Repo @pytest.mark.asyncio -async def test_menu_crud(client) -> None: +async def test_menu_crud_empty(client: AsyncClient) -> None: """Тестирование функций меню""" - code, menus = await TestBaseCrud.Menu.read_all(client) + code, rspn = await Repo.Menu.read_all(client) assert code == 200 - assert menus == [] + assert rspn == [] + +@pytest.mark.asyncio +async def test_menu_crud_add(client: AsyncClient) -> None: + """Тестирование функций меню""" data = {"title": "Menu", "description": None} - code, menu = await TestBaseCrud.Menu.write(client, data) + code, rspn = await Repo.Menu.write(client, data) assert code == 201 - assert menu["title"] == "Menu" - assert menu["description"] is None - code, menus = await TestBaseCrud.Menu.read_all(client) - assert len(menus) == 1 + assert rspn["title"] == "Menu" + assert rspn["description"] is None + await Repo.Menu.delete(client, rspn) - code, menu = await TestBaseCrud.Menu.get(client, {"id": menu.get("id")}) + +@pytest.mark.asyncio +async def test_menu_crud_get(client: AsyncClient) -> None: + """Тестирование функций меню""" + data = {"title": "Menu", "description": None} + code, rspn = await Repo.Menu.write(client, data) + code, menu = await Repo.Menu.get(client, {"id": rspn.get("id")}) assert code == 200 - assert menu["title"] == data["title"] + assert menu["title"] == rspn["title"] + await Repo.Menu.delete(client, menu) - upd_menu = { - "id": menu.get("id"), + +@pytest.mark.asyncio +async def test_menu_crud_update(client: AsyncClient) -> None: + """Тестирование функций меню""" + data = {"title": "Menu", "description": None} + code, rspn = await Repo.Menu.write(client, data) + + upd_data = { + "id": rspn.get("id"), "title": "upd Menu", "description": "", } - code, menu = await TestBaseCrud.Menu.update(client, upd_menu) + code, upd_rspn = await Repo.Menu.update(client, upd_data) assert code == 200 - print(menu) - # assert menu["title"] == "upd Menu" + assert upd_rspn["title"] == "upd Menu" + await Repo.Menu.delete(client, upd_rspn) -# code = await self.Menu.delete(client, rspn) -# assert code == 200 -# -# code, menu = await self.Menu.get(client, {"id": rspn.get("id")}) -# assert code == 404 -# -# @pytest.mark.asyncio -# async def test_submenus(self, client) -> None: -# # Создаем меню и проверяем ответ -# menu = {"title": "Menu", "description": "main menu"} -# code, rspn = await self.Menu.write(client, menu) -# assert code == 201 -# menu.update(rspn) -# -# # Проверяем наличие подменю -# code, rspn = await self.Submenu.read_all(client, menu) -# assert code == 200 -# assert rspn == [] -# -# # Создаем и проверяем подменю -# submenu = { -# "title": "Submenu", -# "description": "submenu", -# "parent_menu": menu["id"], -# } -# code, rspn = await self.Submenu.write(client, menu, submenu) -# assert code == 201 -# submenu.update(rspn) -# -# # Проверяем меню на наличие подменю -# code, rspn = await self.Menu.get(client, menu) -# assert code == 200 -# assert rspn["submenus_count"] == 1 -# -# # Обновляем подменю и проверяем -# submenu["title"] = "updated_submenu" -# code, rspn = await self.Submenu.update(client, menu, submenu) -# assert code == 200 -# assert submenu["title"] == rspn["title"] -# submenu.update(rspn) -# -# # Удаляем подменю -# code = await self.Submenu.delete(client, menu, submenu) -# assert code == 200 -# -# # Проверяем меню -# code, rspn = await self.Menu.get(client, menu) -# assert code == 200 -# assert rspn["submenus_count"] == 0 -# -# # Проверяем удаленное подменю -# code, rspn = await self.Submenu.get(client, menu, submenu) -# assert code == 404 -# -# # удаляем сопутствующее -# await self.Menu.delete(client, menu) -# -# @pytest.mark.asyncio -# async def test_dishes(self, client: AsyncClient) -> None: -# # Создаем меню и проверяем ответ -# menu = { -# "title": "Menu", -# "description": "main menu", -# } -# code, rspn = await self.Menu.write(client, menu) -# assert code == 201 -# menu.update(rspn) -# -# # Создаем и проверяем подменю -# submenu = { -# "title": "Submenu", -# "description": "submenu", -# "parent_menu": menu["id"], -# } -# code, rspn = await self.Submenu.write(client, menu, submenu) -# assert code == 201 -# submenu.update(rspn) -# -# # Проверяем все блюда в подменю -# code, rspn = await self.Dish.read_all(client, menu, submenu) -# assert code == 200 -# assert rspn == [] -# -# # Добавляем блюдо -# dish = { -# "title": "dish", -# "description": "some dish", -# "price": "12.5", -# "parent_submenu": submenu["id"], -# } -# code, rspn = await self.Dish.write(client, menu, submenu, dish) -# assert code == 201 -# dish.update(rspn) -# -# # Получаем блюдо -# code, rspn = await self.Dish.get(client, menu, submenu, dish) -# assert code == 200 -# assert rspn["title"] == dish["title"] -# -# # Проверяем меню на количество блюд -# code, rspn = await self.Menu.get(client, menu) -# assert code == 200 -# assert rspn["dishes_count"] == 1 -# -# # Проверяем подменю на наличие блюд -# code, rspn = await self.Submenu.get(client, menu, submenu) -# assert code == 200 -# assert rspn["dishes_count"] == 1 -# -# # Обновляем блюдо и проверяем -# dish["title"] = "updated_dish" -# code, rspn = await self.Dish.update(client, menu, submenu, dish) -# assert code == 200 -# assert dish["title"] == rspn["title"] -# dish.update(rspn) -# -# # Удаляем подменю -# code = await self.Dish.delete(client, menu, submenu, dish) -# assert code == 200 -# -# # Проверяем меню -# code, rspn = await self.Menu.get(client, menu) -# assert code == 200 -# assert rspn["dishes_count"] == 0 -# -# # Проверяем подменю на наличие блюд -# code, rspn = await self.Submenu.get(client, menu, submenu) -# assert code == 200 -# assert rspn["dishes_count"] == 0 -# -# # Проверяем удаленное блюдо -# code, rspn = await self.Dish.get(client, menu, submenu, dish) -# assert code == 404 -# -# # удаляем сопутствующее -# await self.Submenu.delete(client, menu, submenu) -# await self.Menu.delete(client, menu) -# -# -# class TestСontinuity: -# @pytest.mark.asyncio -# async def test_postman_continuity(self, client): -# # Создаем меню -# menu = { -# "title": "Menu", -# "description": "main menu", -# } -# code, rspn = await TestBaseCrud.Menu.write(client, menu) -# assert code == 201 -# assert "id" in rspn.keys() -# menu.update(rspn) -# -# # Создаем подменю -# submenu = { -# "title": "Submenu", -# "description": "submenu", -# "parent_menu": menu["id"], -# } -# code, rspn = await TestBaseCrud.Submenu.write(client, menu, submenu) -# assert code == 201 -# assert "id" in rspn.keys() -# submenu.update(rspn) -# -# # Добавляем блюдо1 -# dish = { -# "title": "dish1", -# "description": "some dish1", -# "price": "13.50", -# "parent_submenu": submenu["id"], -# } -# code, rspn = await TestBaseCrud.Dish.write(client, menu, submenu, dish) -# assert code == 201 -# assert "id" in rspn.keys() -# dish.update(rspn) -# -# # Добавляем блюдо2 -# dish = { -# "title": "dish2", -# "description": "some dish2", -# "price": "12.50", -# "parent_submenu": submenu["id"], -# } -# code, rspn = await TestBaseCrud.Dish.write(client, menu, submenu, dish) -# assert code == 201 -# assert "id" in rspn.keys() -# dish.update(rspn) -# -# # Просматриваем конкретное меню -# code, rspn = await TestBaseCrud.Menu.get(client, menu) -# assert code == 200 -# assert "id" in rspn.keys() -# assert menu["id"] == rspn["id"] -# assert "submenus_count" in rspn.keys() -# assert rspn["submenus_count"] == 1 -# assert "dishes_count" in rspn.keys() -# assert rspn["dishes_count"] == 2 -# -# # Просматриваем конкретное подменю -# code, rspn = await TestBaseCrud.Submenu.get(client, menu, submenu) -# assert code == 200 -# assert "id" in rspn.keys() -# assert "dishes_count" in rspn.keys() -# assert rspn["dishes_count"] == 2 -# -# # Удаляем подменю -# code = await TestBaseCrud.Submenu.delete(client, menu, submenu) -# assert code == 200 -# -# # Просматриваем список подменю -# code, rspn = await TestBaseCrud.Submenu.read_all(client, menu) -# assert code == 200 -# assert rspn == [] -# -# # Просматриваем список блюд -# code, rspn = await TestBaseCrud.Dish.read_all(client, menu, submenu) -# assert code == 200 -# assert rspn == [] -# -# # Просматриваем конкретное меню -# code, rspn = await TestBaseCrud.Menu.get(client, menu) -# assert code == 200 -# assert "id" in rspn.keys() -# assert menu["id"] == rspn["id"] -# assert "submenus_count" in rspn.keys() -# assert rspn["submenus_count"] == 0 -# assert "dishes_count" in rspn.keys() -# assert rspn["dishes_count"] == 0 -# -# # Удаляем меню -# code = await TestBaseCrud.Menu.delete(client, menu) -# assert code == 200 -# -# # Просматриваем все меню -# code, rspn = await TestBaseCrud.Menu.read_all(client) -# assert code == 200 -# assert rspn == [] +@pytest.mark.asyncio +async def test_menu_crud_delete(client: AsyncClient) -> None: + """Тестирование функций меню""" + data = {"title": "Menu", "description": None} + code, rspn = await Repo.Menu.write(client, data) + + code = await Repo.Menu.delete(client, rspn) + assert code == 200 + + code, rspn = await Repo.Menu.get(client, {"id": rspn.get("id")}) + assert code == 404 + + +@pytest.mark.asyncio +async def test_menu_crud_get_all(client: AsyncClient) -> None: + """Тестирование функций меню""" + code, rspn = await Repo.Menu.read_all(client) + assert code == 200 + assert rspn == [] + + data = {"title": "Menu", "description": None} + code, rspn = await Repo.Menu.write(client, data) + + code, upd_rspn = await Repo.Menu.read_all(client) + assert code == 200 + assert upd_rspn == [rspn] + await Repo.Menu.delete(client, rspn) + + +@pytest.mark.asyncio +async def test_submenus_get_all(client) -> None: + # Создаем меню и проверяем ответ + menu = {"title": "Menu", "description": "main menu"} + code, rspn = await Repo.Menu.write(client, menu) + assert code == 201 + menu.update(rspn) + + # Проверяем наличие подменю + code, rspn = await Repo.Submenu.read_all(client, menu) + assert code == 200 + assert rspn == [] + + # Создаем и проверяем подменю + submenu = { + "title": "Submenu", + "description": "submenu", + "parent_menu": menu["id"], + } + code, rspn = await Repo.Submenu.write(client, menu, submenu) + submenu.update(rspn) + + # Проверяем наличие подменю + code, upd_rspn = await Repo.Submenu.read_all(client, menu) + assert code == 200 + assert upd_rspn == [rspn] + + # удаляем сопутствующее + await Repo.Submenu.delete(client, menu, submenu) + await Repo.Menu.delete(client, menu) + + +@pytest.mark.asyncio +async def test_submenus_add(client) -> None: + # Создаем меню и проверяем ответ + menu = {"title": "Menu", "description": "main menu"} + code, rspn = await Repo.Menu.write(client, menu) + menu.update(rspn) + + # Создаем и проверяем подменю + submenu = { + "title": "Submenu", + "description": "submenu", + "parent_menu": menu["id"], + } + code, rspn = await Repo.Submenu.write(client, menu, submenu) + assert code == 201 + submenu.update(rspn) + + # удаляем сопутствующее + await Repo.Submenu.delete(client, menu, submenu) + await Repo.Menu.delete(client, menu) + + +@pytest.mark.asyncio +async def test_submenus_update(client) -> None: + # Создаем меню и проверяем ответ + menu = {"title": "Menu", "description": "main menu"} + code, rspn = await Repo.Menu.write(client, menu) + menu.update(rspn) + + # Создаем и проверяем подменю + submenu = { + "title": "Submenu", + "description": "submenu", + "parent_menu": menu["id"], + } + code, rspn = await Repo.Submenu.write(client, menu, submenu) + submenu.update(rspn) + + # Обновляем подменю и проверяем + submenu["title"] = "updated_submenu" + code, rspn = await Repo.Submenu.update(client, menu, submenu) + assert code == 200 + assert submenu["title"] == rspn["title"] + submenu.update(rspn) + + # удаляем сопутствующее + await Repo.Submenu.delete(client, menu, submenu) + await Repo.Menu.delete(client, menu) + + +@pytest.mark.asyncio +async def test_submenus_delete(client) -> None: + # Создаем меню и проверяем ответ + menu = {"title": "Menu", "description": "main menu"} + code, rspn = await Repo.Menu.write(client, menu) + menu.update(rspn) + + # Создаем и проверяем подменю + submenu = { + "title": "Submenu", + "description": "submenu", + "parent_menu": menu["id"], + } + code, rspn = await Repo.Submenu.write(client, menu, submenu) + submenu.update(rspn) + + # Удаляем подменю + code = await Repo.Submenu.delete(client, menu, submenu) + assert code == 200 + + # Проверяем удаленное подменю + code, rspn = await Repo.Submenu.get(client, menu, submenu) + assert code == 404 + + # удаляем сопутствующее + await Repo.Menu.delete(client, menu) + + +@pytest.mark.asyncio +async def test_dishes_get_all(client: AsyncClient) -> None: + # Создаем меню и проверяем ответ + menu = { + "title": "Menu", + "description": "main menu", + } + code, rspn = await Repo.Menu.write(client, menu) + menu.update(rspn) + + # Создаем и проверяем подменю + submenu = { + "title": "Submenu", + "description": "submenu", + "parent_menu": menu["id"], + } + code, rspn = await Repo.Submenu.write(client, menu, submenu) + submenu.update(rspn) + + # Проверяем все блюда в подменю + code, rspn = await Repo.Dish.read_all(client, menu, submenu) + assert code == 200 + assert rspn == [] + + # Добавляем блюдо + dish = { + "title": "dish", + "description": "some dish", + "price": "12.5", + "parent_submenu": submenu["id"], + } + code, rspn = await Repo.Dish.write(client, menu, submenu, dish) + assert code == 201 + dish.update(rspn) + + code, upd_rspn = await Repo.Dish.read_all(client, menu, submenu) + + assert code == 200 + + # удаляем сопутствующее + await Repo.Dish.delete(client, menu, submenu, dish) + await Repo.Submenu.delete(client, menu, submenu) + await Repo.Menu.delete(client, menu) + + +@pytest.mark.asyncio +async def test_dishes_add(client: AsyncClient) -> None: + # Создаем меню и проверяем ответ + menu = { + "title": "Menu", + "description": "main menu", + } + code, rspn = await Repo.Menu.write(client, menu) + menu.update(rspn) + + # Создаем и проверяем подменю + submenu = { + "title": "Submenu", + "description": "submenu", + "parent_menu": menu["id"], + } + code, rspn = await Repo.Submenu.write(client, menu, submenu) + submenu.update(rspn) + + # Добавляем блюдо + dish = { + "title": "dish", + "description": "some dish", + "price": "12.5", + "parent_submenu": submenu["id"], + } + code, rspn = await Repo.Dish.write(client, menu, submenu, dish) + assert code == 201 + dish.update(rspn) + + # Получаем блюдо + code, rspn = await Repo.Dish.get(client, menu, submenu, dish) + assert code == 200 + assert rspn["title"] == dish["title"] + + # удаляем сопутствующее + await Repo.Dish.delete(client, menu, submenu, dish) + await Repo.Submenu.delete(client, menu, submenu) + await Repo.Menu.delete(client, menu) + + +@pytest.mark.asyncio +async def test_dishes_update(client: AsyncClient) -> None: + # Создаем меню и проверяем ответ + menu = { + "title": "Menu", + "description": "main menu", + } + code, rspn = await Repo.Menu.write(client, menu) + menu.update(rspn) + + # Создаем и проверяем подменю + submenu = { + "title": "Submenu", + "description": "submenu", + "parent_menu": menu["id"], + } + code, rspn = await Repo.Submenu.write(client, menu, submenu) + submenu.update(rspn) + + # Добавляем блюдо + dish = { + "title": "dish", + "description": "some dish", + "price": "12.5", + "parent_submenu": submenu["id"], + } + code, rspn = await Repo.Dish.write(client, menu, submenu, dish) + dish.update(rspn) + + # Обновляем блюдо и проверяем + dish["title"] = "updated_dish" + code, rspn = await Repo.Dish.update(client, menu, submenu, dish) + assert code == 200 + assert dish["title"] == rspn["title"] + dish.update(rspn) + + # удаляем сопутствующее + await Repo.Dish.delete(client, menu, submenu, dish) + await Repo.Submenu.delete(client, menu, submenu) + await Repo.Menu.delete(client, menu) + + +@pytest.mark.asyncio +async def test_dishes_delete(client: AsyncClient) -> None: + # Создаем меню и проверяем ответ + menu = { + "title": "Menu", + "description": "main menu", + } + code, rspn = await Repo.Menu.write(client, menu) + menu.update(rspn) + + # Создаем и проверяем подменю + submenu = { + "title": "Submenu", + "description": "submenu", + "parent_menu": menu["id"], + } + code, rspn = await Repo.Submenu.write(client, menu, submenu) + submenu.update(rspn) + + # Добавляем блюдо + dish = { + "title": "dish", + "description": "some dish", + "price": "12.5", + "parent_submenu": submenu["id"], + } + code, rspn = await Repo.Dish.write(client, menu, submenu, dish) + dish.update(rspn) + + # Удаляем подменю + code = await Repo.Dish.delete(client, menu, submenu, dish) + assert code == 200 + + # Проверяем удаленное блюдо + code, rspn = await Repo.Dish.get(client, menu, submenu, dish) + assert code == 404 + + # удаляем сопутствующее + await Repo.Submenu.delete(client, menu, submenu) + await Repo.Menu.delete(client, menu) diff --git a/tests/test_postman.py b/tests/test_postman.py new file mode 100644 index 0000000..f42380e --- /dev/null +++ b/tests/test_postman.py @@ -0,0 +1,235 @@ +from typing import Dict + +import pytest +from httpx import AsyncClient + +from .repository import Repository as Repo + + +@pytest.mark.asyncio +async def test_01(client: AsyncClient, session_data: Dict): + """Проверяет создание меню""" + menu = {"title": "Menu", "description": "some_menu_desc"} + code, rspn = await Repo.Menu.write(client, menu) + + assert code == 201 + code, rspn = await Repo.Menu.get(client, rspn) + session_data["target_menu_id"] = rspn.get("id") + session_data["target_menu_title"] = rspn.get("title") + session_data["target_menu_description"] = rspn.get("description") + + assert code == 200 + assert "id" in rspn + assert "title" in rspn + assert "description" in rspn + assert "submenus_count" in rspn + assert "dishes_count" in rspn + assert rspn["title"] == menu.get("title") + assert rspn.get("description") == menu.get("description") + + +@pytest.mark.asyncio +async def test_02(client: AsyncClient, session_data: Dict): + submenu = {"title": "Submenu", "description": "submenu_descr"} + menu = { + "id": session_data.get("target_menu_id"), + "title": session_data.get("target_menu_title"), + "description": session_data.get("target_menu_description"), + } + + code, rspn = await Repo.Submenu.write(client, menu, submenu) + + assert code == 201 + assert "id" in rspn + assert "title" in rspn + assert "description" in rspn + assert "dishes_count" in rspn + assert rspn["title"] == submenu.get("title") + assert rspn.get("description") == submenu.get("description") + + session_data["target_submenu_id"] = rspn.get("id") + session_data["target_submenu_title"] = rspn.get("title") + session_data["target_submenu_description"] = rspn.get("description") + + +@pytest.mark.asyncio +async def test_03_dish1(client: AsyncClient, session_data: Dict): + menu = { + "id": session_data.get("target_menu_id"), + "title": session_data.get("target_menu_title"), + "description": session_data.get("target_menu_description"), + } + submenu = { + "id": session_data.get("target_submenu_id"), + "title": session_data.get("target_submenu_title"), + "description": session_data.get("target_submenu_description"), + } + dish = {"title": "dish_1", "description": "dish 1 descr", "price": "12.5"} + code, rspn = await Repo.Dish.write(client, menu, submenu, dish) + + assert code == 201 + assert "id" in rspn + assert "title" in rspn + assert "description" in rspn + assert "price" in rspn + assert rspn["title"] == dish.get("title") + assert rspn.get("description") == dish.get("description") + assert rspn.get("price") == dish.get("price") + + session_data["target_dish_id"] = rspn.get("id") + session_data["target_dish_title"] = rspn.get("title") + session_data["target_dish_description"] = rspn.get("description") + session_data["target_dish_price"] = rspn.get("price") + + +@pytest.mark.asyncio +async def test_04_dish2(client: AsyncClient, session_data: Dict): + menu = { + "id": session_data.get("target_menu_id"), + "title": session_data.get("target_menu_title"), + "description": session_data.get("target_menu_description"), + } + submenu = { + "id": session_data.get("target_submenu_id"), + "title": session_data.get("target_submenu_title"), + "description": session_data.get("target_submenu_description"), + } + dish = {"title": "dish_2", "description": "dish 2 descr", "price": "13.5"} + code, rspn = await Repo.Dish.write(client, menu, submenu, dish) + + assert code == 201 + assert "id" in rspn + assert "title" in rspn + assert "description" in rspn + assert "price" in rspn + assert rspn["title"] == dish.get("title") + assert rspn.get("description") == dish.get("description") + assert rspn.get("price") == dish.get("price") + + session_data["target_dish1_id"] = rspn.get("id") + session_data["target_dish1_title"] = rspn.get("title") + session_data["target_dish1_description"] = rspn.get("description") + session_data["target_dish1_price"] = rspn.get("price") + + +@pytest.mark.asyncio +async def test_05_check_menu(client: AsyncClient, session_data: Dict): + menu = { + "id": session_data.get("target_menu_id"), + "title": session_data.get("target_menu_title"), + "description": session_data.get("target_menu_description"), + } + code, rspn = await Repo.Menu.get(client, menu) + + assert code == 200 + assert "id" in rspn + assert "title" in rspn + assert "description" in rspn + assert rspn.get("submenus_count") == 1 + assert rspn.get("dishes_count") == 2 + + +@pytest.mark.asyncio +async def test_06_check_submenu(client: AsyncClient, session_data: Dict): + menu = { + "id": session_data.get("target_menu_id"), + "title": session_data.get("target_menu_title"), + "description": session_data.get("target_menu_description"), + } + submenu = { + "id": session_data.get("target_submenu_id"), + "title": session_data.get("target_submenu_title"), + "description": session_data.get("target_submenu_description"), + } + code, rspn = await Repo.Submenu.get(client, menu, submenu) + + assert code == 200 + assert "id" in rspn + assert "title" in rspn + assert "description" in rspn + assert rspn.get("dishes_count") == 2 + + +@pytest.mark.asyncio +async def test_07_del_submenu(client: AsyncClient, session_data: Dict): + menu = { + "id": session_data.get("target_menu_id"), + "title": session_data.get("target_menu_title"), + "description": session_data.get("target_menu_description"), + } + submenu = { + "id": session_data.get("target_submenu_id"), + "title": session_data.get("target_submenu_title"), + "description": session_data.get("target_submenu_description"), + } + code = await Repo.Submenu.delete(client, menu, submenu) + + assert code == 200 + + +@pytest.mark.asyncio +async def test_07_check_submenus(client: AsyncClient, session_data: Dict): + menu = { + "id": session_data.get("target_menu_id"), + "title": session_data.get("target_menu_title"), + "description": session_data.get("target_menu_description"), + } + code, rspn = await Repo.Submenu.read_all(client, menu) + + assert code == 200 + assert rspn == [] + + +@pytest.mark.asyncio +async def test_08_check_dishes(client: AsyncClient, session_data: Dict): + menu = { + "id": session_data.get("target_menu_id"), + "title": session_data.get("target_menu_title"), + "description": session_data.get("target_menu_description"), + } + submenu = { + "id": session_data.get("target_submenu_id"), + "title": session_data.get("target_submenu_title"), + "description": session_data.get("target_submenu_description"), + } + code, rspn = await Repo.Dish.read_all(client, menu, submenu) + + assert code == 200 + assert rspn == [] + + +@pytest.mark.asyncio +async def test_09_check_menu(client: AsyncClient, session_data: Dict): + menu = { + "id": session_data.get("target_menu_id"), + "title": session_data.get("target_menu_title"), + "description": session_data.get("target_menu_description"), + } + code, rspn = await Repo.Menu.get(client, menu) + + assert code == 200 + assert "id" in rspn + assert "title" in rspn + assert "description" in rspn + assert rspn.get("submenus_count") == 0 + assert rspn.get("dishes_count") == 0 + + +@pytest.mark.asyncio +async def test_10_del_menu(client: AsyncClient, session_data: Dict): + menu = { + "id": session_data.get("target_menu_id"), + "title": session_data.get("target_menu_title"), + "description": session_data.get("target_menu_description"), + } + code = await Repo.Menu.delete(client, menu) + + assert code == 200 + + +@pytest.mark.asyncio +async def test_11_check_menus(client: AsyncClient, session_data: Dict): + code, rspn = await Repo.Menu.read_all(client) + + assert code == 200 + assert rspn == []