Compare commits

..

23 Commits

Author SHA1 Message Date
Сергей Ванюшкин 8189aaedd4 fix: Поправил TypeHints и убраз неиспользуемые сущности 2024-02-14 15:34:24 +03:00
Сергей Ванюшкин 5ef6aaeb6f маленькие правки 2024-02-13 13:09:09 +03:00
Сергей Ванюшкин f75415d9d9 Удаление блюда 2024-02-13 12:38:13 +03:00
Сергей Ванюшкин 4c3779776d Readme 2024-02-13 02:44:24 +03:00
Сергей Ванюшкин d54e704dfb fix: volumes не примонтировал 2024-02-13 00:02:22 +03:00
Сергей Ванюшкин 68594eb7f0 слияние веток 2024-02-12 23:52:21 +03:00
Сергей Ванюшкин 8bfa166987 слияние веток 2024-02-12 23:09:50 +03:00
Сергей Ванюшкин e0a81cf126 google sheets docker образ 2024-02-12 23:09:01 +03:00
Сергей Ванюшкин a4f8bce657 google синхронизация 2024-02-12 23:09:01 +03:00
Сергей Ванюшкин 9ba42aae9f upd фоновая задача теперь не дропает базу 2024-02-12 23:09:01 +03:00
Сергей Ванюшкин afdf1c5e2b fix 2024-02-12 23:09:01 +03:00
Сергей Ванюшкин 74c0ccae2a fix 2024-02-12 23:09:01 +03:00
Сергей Ванюшкин 2c48529a02 fix 2024-02-12 23:09:01 +03:00
Сергей Ванюшкин cedf27a04d fix 2024-02-12 23:09:01 +03:00
Сергей Ванюшкин e0798de713 fix 2024-02-12 23:09:01 +03:00
Сергей Ванюшкин 5a133a05e1 fix 2024-02-12 23:09:01 +03:00
Сергей Ванюшкин 3df3c67e7c fix: правка урла кролика 2024-02-12 23:09:01 +03:00
Сергей Ванюшкин a0ebe9bdb9 upd: Контейнеры для celery & rabbitmq 2024-02-12 23:09:01 +03:00
Сергей Ванюшкин ed3d7d9352 upd Разнес тесты, уменьшив портянку
upd Тест для summary роута
2024-02-12 23:09:01 +03:00
Сергей Ванюшкин 3dbefda936 upd: Применение скидки в выводе API 2024-02-12 23:09:01 +03:00
Сергей Ванюшкин 5a95b06300 upd: Добавил bg_task xlsx>>DBase 2024-02-12 23:09:01 +03:00
Сергей Ванюшкин ebe75b6dc3 upd: Добавил роут summary с выводом вмего меню со вложением 2024-02-12 23:09:01 +03:00
Сергей Ванюшкин 3120910552 Fix .env для локального запуска 2024-02-07 12:44:42 +03:00
17 changed files with 97 additions and 111 deletions

View File

View File

@ -5,29 +5,50 @@ Fastapi веб приложение реализующее api для общеп
Данный проект, это результат выполнения практических домашних заданий интенсива от YLAB Development. Проект реализован на фреймворке fastapi, с использованием sqlalchemy. В качестве базы данных используется postgresql. Данный проект, это результат выполнения практических домашних заданий интенсива от YLAB Development. Проект реализован на фреймворке fastapi, с использованием sqlalchemy. В качестве базы данных используется postgresql.
## Техническое задание ## Техническое задание
### Спринт 3 - Паттерны и принципы разработки ### Спринт 4 - Многопроцессорность, асинхронность
В этом домашнем задании необходимо:
1.Переписать текущее FastAPI приложение на асинхронное выполнение
2.Добавить в проект фоновую задачу с помощью Celery + RabbitMQ.
3.Добавить эндпоинт (GET) для вывода всех меню со всеми связанными подменю и со всеми связанными блюдами.
4.Реализовать инвалидация кэша в background task (встроено в FastAPI)
5.* Обновление меню из google sheets раз в 15 сек.
6.** Блюда по акции. Размер скидки (%) указывается в столбце G файла Menu.xlsx
1.Вынести бизнес логику и запросы в БД в отдельные слои приложения. Фоновая задача: синхронизация Excel документа и БД.
В проекте создаем папку admin. В эту папку кладем файл Menu.xlsx (будет прикреплен к ДЗ). Не забываем запушить в гит.
При внесении изменений в файл все изменения должны отображаться в БД. Периодичность обновления 15 сек. Удалять БД при каждом обновлении нельзя.
2.Добавить кэширование запросов к API с использованием Redis. Не забыть про инвалидацию кэша.
3.Добавить pre-commit хуки в проект. Файл yaml будет прикреплен к ДЗ.
4.Покрыть проект type hints (тайпхинтами)
5.* Описать ручки API в соответствий c OpenAPI
6.** Реализовать в тестах аналог Django reverse() для FastAPI
Требования: Требования:
●Код должен проходить все линтеры. ●Данные меню, подменю, блюд для нового эндпоинта должны доставаться одним ORM-запросом в БД (использовать подзапросы и агрегирующие функций SQL).
●Код должен соответствовать принципам SOLID, DRY, KISS. ●Проект должен запускаться одной командой
●Проект должен запускаться по одной команде (докер). ●Проект должен соответствовать требованиям всех предыдущих вебинаров. (Не забыть добавить тесты для нового API эндпоинта)
●Проект должен проходить все Postman тесты (коллекция с Вебинара №1).
●Тесты написанные вами после Вебинара №2, должны быть актуальны, запускать и успешно проходить ### Выполненные доп задания со *
Спринт 2
3.* Реализовать вывод количества подменю и блюд для Меню через один (сложный) ORM запрос.
`./fastfood/repository/menu.py` Метод `get_menu_item`
4.** Реализовать тестовый сценарий «Проверка кол-ва блюд и подменю в меню» из Postman с помощью pytest
`./tests/test_postman.py`
Спринт 3
5.* Описать ручки API в соответствий c OpenAPI
'./openapi.json'
6.** Реализовать в тестах аналог Django reverse() для FastAPI
'./tests/urls.py'
Спринт 4
5.* Обновление меню из google sheets раз в 15 сек.
`./bg_tasks/` Реализовано чтение как локальной, так и удаленной таблицы.
В зависимости какой compose поднять, тот и будет использоваться
6.** Блюда по акции. Размер скидки (%) указывается в столбце G файла Menu.xlsx
`./fastfood/service/dish.py`, метод _get_discont, подменяет сумму в выдаче,
скидка хранится в REDIS под ключами вида DISCONT:{UUID блюда}
Дополнительно:
Контейнеры с проектом и с тестами запускаются разными командами.
## Зависимости ## Зависимости
- docker - docker
@ -43,8 +64,12 @@ Fastapi веб приложение реализующее api для общеп
Запуск/остановка образов: Запуск/остановка образов:
- Запуск FAstAPI приложения - Запуск FAstAPI приложения c локальным файлом для фоновой задачи
> `$ docker-compose -f compose_app.yml up ` > `$ docker-compose -f compose_app.yml up`
- Запуск FAstAPI приложения c Google Sheets для фоновой задачи
> `$ docker-compose -f compose_google.yml up`
(ЧИТАЙТЕ СООБЩЕНИЕ В ЧАТЕ)
После успешного запуска образов документация по API будет доступна по адресу <a href="http://localhost:8000/docs">http://localhost:8000</a> После успешного запуска образов документация по API будет доступна по адресу <a href="http://localhost:8000/docs">http://localhost:8000</a>

Binary file not shown.

View File

@ -1,4 +1,5 @@
import os import os
from typing import Any
import gspread import gspread
import openpyxl import openpyxl
@ -43,7 +44,9 @@ async def local_xlsx_to_rows() -> list[list[str | int | float]]:
return data return data
async def rows_to_dict(rows: list[list]) -> tuple: async def rows_to_dict(
rows: list[list],
) -> tuple[dict[int, Any], dict[Any, Any], dict[Any, Any]]:
"""Парсит строки полученные и источников в словарь""" """Парсит строки полученные и источников в словарь"""
menus = {} menus = {}

View File

@ -1,5 +1,6 @@
import os import os
import pickle import pickle
from typing import Any
import redis.asyncio as redis # type: ignore import redis.asyncio as redis # type: ignore
from sqlalchemy import delete, update from sqlalchemy import delete, update
@ -45,7 +46,8 @@ async def is_changed_xls() -> bool:
async def on_menu_change( async def on_menu_change(
new_menu: dict, old_menu: dict, session: AsyncSession new_menu: dict, old_menu: dict, session: AsyncSession
) -> dict | None: ) -> dict[str, Any]:
"""Изменение, удаление или создание меню"""
if new_menu and not old_menu: if new_menu and not old_menu:
# Создаем меню # Создаем меню
menu = Menu( menu = Menu(
@ -55,6 +57,7 @@ async def on_menu_change(
session.add(menu) session.add(menu)
await session.flush() await session.flush()
new_menu['id'] = str(menu.id) new_menu['id'] = str(menu.id)
elif new_menu and old_menu: elif new_menu and old_menu:
# Обновляем меню # Обновляем меню
await session.execute( await session.execute(
@ -67,10 +70,6 @@ async def on_menu_change(
await session.execute(delete(Menu).where(Menu.id == old_menu['id'])) await session.execute(delete(Menu).where(Menu.id == old_menu['id']))
await session.commit() await session.commit()
# Чистим кэш
await clear_cache('MENUS*')
await clear_cache('summary')
return new_menu return new_menu
@ -108,9 +107,9 @@ async def menus_updater(menus: dict, session: AsyncSession) -> None:
async def on_submenu_change( async def on_submenu_change(
new_sub: dict, old_sub: dict, session: AsyncSession new_sub: dict, old_sub: dict, session: AsyncSession
) -> dict: ) -> dict[str, Any]:
if new_sub and not old_sub: if new_sub and not old_sub:
# Создаем меню # Создаем подменю
submenu = SubMenu( submenu = SubMenu(
title=new_sub['data']['title'], title=new_sub['data']['title'],
description=new_sub['data']['description'], description=new_sub['data']['description'],
@ -121,8 +120,9 @@ async def on_submenu_change(
await session.flush() await session.flush()
new_sub['id'] = str(submenu.id) new_sub['id'] = str(submenu.id)
new_sub['parent_menu'] = str(submenu.parent_menu) new_sub['parent_menu'] = str(submenu.parent_menu)
elif new_sub and old_sub: elif new_sub and old_sub:
# Обновляем меню # Обновляем подменю
await session.execute( await session.execute(
update(SubMenu) update(SubMenu)
.where(SubMenu.id == old_sub['id']) .where(SubMenu.id == old_sub['id'])
@ -132,12 +132,9 @@ async def on_submenu_change(
new_sub['parent_menu'] = old_sub['parent_menu'] new_sub['parent_menu'] = old_sub['parent_menu']
else: else:
# Удаляем меню # Удаляем подменю
await session.execute(delete(SubMenu).where(SubMenu.id == old_sub['id'])) await session.execute(delete(SubMenu).where(SubMenu.id == old_sub['id']))
await clear_cache('MENUS*')
await clear_cache('summary')
await session.commit() await session.commit()
return new_sub return new_sub
@ -146,7 +143,7 @@ async def submenus_updater(submenus: dict, session: AsyncSession) -> None:
"""Проверяет пункты подменю на изменения """Проверяет пункты подменю на изменения
При необходимости запускае обновление БД При необходимости запускае обновление БД
""" """
# Получаем Меню из кэша для получения их ID по померу в таблице # Получаем меню из кэша для получения их ID по померу в таблице
cached_menus = await redis.get('ALL_MENUS') cached_menus = await redis.get('ALL_MENUS')
if cached_menus is not None: if cached_menus is not None:
cached_menus = pickle.loads(cached_menus) cached_menus = pickle.loads(cached_menus)
@ -189,7 +186,9 @@ async def submenus_updater(submenus: dict, session: AsyncSession) -> None:
await redis.set('ALL_SUBMENUS', pickle.dumps(submenus)) await redis.set('ALL_SUBMENUS', pickle.dumps(submenus))
async def on_dish_change(new_dish: dict, old_dish, session: AsyncSession) -> dict: async def on_dish_change(
new_dish: dict, old_dish, session: AsyncSession
) -> dict[str, Any]:
if new_dish and not old_dish: if new_dish and not old_dish:
dish = Dish( dish = Dish(
title=new_dish['data']['title'], title=new_dish['data']['title'],
@ -204,7 +203,7 @@ async def on_dish_change(new_dish: dict, old_dish, session: AsyncSession) -> dic
new_dish['parent_submenu'] = str(dish.parent_submenu) new_dish['parent_submenu'] = str(dish.parent_submenu)
new_dish['data']['price'] = str(dish.price) new_dish['data']['price'] = str(dish.price)
elif new_dish and old_dish: elif new_dish and old_dish:
# Обновляем меню # Обновляем блюдо
await session.execute( await session.execute(
update(Dish).where(Dish.id == old_dish['id']).values(**(new_dish['data'])) update(Dish).where(Dish.id == old_dish['id']).values(**(new_dish['data']))
) )
@ -213,12 +212,9 @@ async def on_dish_change(new_dish: dict, old_dish, session: AsyncSession) -> dic
new_dish['data']['price'] = old_dish['data']['price'] new_dish['data']['price'] = old_dish['data']['price']
else: else:
# Удаляем меню # Удаляем блюдо
await session.execute(delete(Dish).where(Dish.id == old_dish['id'])) await session.execute(delete(Dish).where(Dish.id == old_dish['id']))
await clear_cache('MENUS*')
await clear_cache('summary')
await session.commit() await session.commit()
return new_dish return new_dish
@ -233,7 +229,7 @@ async def dishes_updater(dishes: dict, session: AsyncSession) -> None:
else: else:
cached_submenus = {} cached_submenus = {}
# Получаем подмен из кэша # Получаем блюда из кэша
cached_dishes = await redis.get('ALL_DISHES') cached_dishes = await redis.get('ALL_DISHES')
if cached_dishes is not None: if cached_dishes is not None:
@ -244,21 +240,21 @@ async def dishes_updater(dishes: dict, session: AsyncSession) -> None:
await clear_cache('DISCONT*') await clear_cache('DISCONT*')
for key in {k: cached_dishes[k] for k in set(cached_dishes) - set(dishes)}: for key in {k: cached_dishes[k] for k in set(cached_dishes) - set(dishes)}:
# Проверяем на удаленные меню # Проверяем на удаленные блюда и обновляемся
await on_submenu_change({}, cached_dishes.pop(key), session) await on_dish_change({}, cached_dishes.pop(key), session)
for key in dishes.keys(): for key in dishes.keys():
parent = cached_submenus[dishes[key]['parent_num']]['id'] parent = cached_submenus[dishes[key]['parent_num']]['id']
dishes[key]['parent_submenu'] = parent dishes[key]['parent_submenu'] = parent
if key not in cached_dishes.keys(): if key not in cached_dishes.keys():
# Получаем и ставим UUID parent_menu # Получаем и ставим UUID parent_submenu
dishes[key]['parent_submenu'] = parent dishes[key]['parent_submenu'] = parent
dish = await on_dish_change(dishes[key], {}, session) dish = await on_dish_change(dishes[key], {}, session)
dishes[key] = dish dishes[key] = dish
elif key in cached_dishes.keys(): elif key in cached_dishes.keys():
# Обновление меню # Обновление блюда
if dishes[key].get('data') != cached_dishes[key].get('data'): if dishes[key].get('data') != cached_dishes[key].get('data'):
dish = await on_dish_change(dishes[key], cached_dishes[key], session) dish = await on_dish_change(dishes[key], cached_dishes[key], session)
dishes[key] = dish dishes[key] = dish
@ -274,12 +270,15 @@ async def dishes_updater(dishes: dict, session: AsyncSession) -> None:
await redis.set('ALL_DISHES', pickle.dumps(dishes)) await redis.set('ALL_DISHES', pickle.dumps(dishes))
async def updater(rows): async def updater(rows) -> None:
menus, submenus, dishes = await rows_to_dict(rows) menus, submenus, dishes = await rows_to_dict(rows)
async with async_session_maker() as session: async with async_session_maker() as session:
await menus_updater(menus, session) await menus_updater(menus, session)
await submenus_updater(submenus, session) await submenus_updater(submenus, session)
await dishes_updater(dishes, session) await dishes_updater(dishes, session)
# Чистим кэш
await clear_cache('MENUS*')
await clear_cache('summary')
async def main() -> None: async def main() -> None:
@ -291,5 +290,6 @@ async def main() -> None:
async def main_gsheets() -> None: async def main_gsheets() -> None:
"""Главная функция фоновой задачи для работы с Google"""
rows = await gsheets_to_rows() rows = await gsheets_to_rows()
await updater(rows) await updater(rows)

View File

@ -55,6 +55,7 @@ services:
redis: redis:
condition: service_healthy condition: service_healthy
restart: always volumes:
- .:/usr/src/fastfood
command: /bin/bash -c 'poetry run pytest -vv' command: /bin/bash -c 'poetry run pytest -vv'

View File

@ -1,13 +0,0 @@
{
"type": "service_account",
"project_id": "psyched-ceiling-413920",
"private_key_id": "d19492eea6a030092cf8ad767b62d7909734ccb1",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDPP2UrfDE+UOlD\nA761Jemy1IKKdIanRKXSziGvDYJ7zbY5gPFxY8Vo+9fsh1oumvROXqEH4+1LiR0J\nnUiy33R1KDOHETeP4H/FJ3u8+gHoL8wsA7SN5pIX9AR8AyBZCBjgSSp+KJchfrp/\nWN2qnFAbgp248QPvmG7/wLzBNnVsAQhQULLKqSc46hbiZ8Jt7t0ajJgOFRJSp2wP\nT5VE5k737w6b4OH8mUnhw7VK04Wk6DBmQhN1jrnxmMxmdG2hSM2zR824RIMBBs/O\n1dF+5Vkav0tgja/tVqm41Aaa2vgPRACP6bpF13YS+8C1lzw6s+7M/VdE5TH5NXRU\nvChuRknBAgMBAAECggEABmuckna0krVsawaXhLaQ30DsLf5w9hdLTvDy6CCuO9Aw\nPKb//9UNNmjMKD4rlQNY1YFS6jbxZNZRrIC7aftwQOGE2mKuIMBl6+tinuy0tLr/\nl3baS+22VZyyG36ILNrqZJ8epGm08CEsNVYRKKwS0x3aXZKFnnlnqaeYn2CUzdqa\na9iNZqrdXdRt4O7KVP7IfdNi11WuOL4epmHwBBYmCxiN0Z2KAIYvS6AcflYWtYTZ\npsBFjCQexqS37PdUyyQX9E/gKwqNZmahYwIC3vsCMCLdQQ93iODYni7LKsG0vvls\nwz03TtlMmZpMJJQGkALeqlv7jeyj+oRuqg6gjs2moQKBgQDxuDt3u1rDWhTJ50bD\nAp5T1LaiV0/+lu29ElTmYpa0RF1tlHvrndFm/MrdUjpzP4/VISmRkP3bmAgwPP6p\nYeALqQXCCGJtl44LG6D9VIOCOZxntytjLHogY8S3BLpwzKC+VMFsd56ay6wCl03S\nJEnvG10FQX8sFd+6j5qMy73OoQKBgQDbfc7hV4/r7PMaUVWFRqWjLry3dtTErxnM\nTdX30BDtuqMrm+hx0zC85ePcsbx+Zhwneyaxw2ICN5F954mJurBqs9cVaxitNSv1\nX5XjAoZqf3TevufkmSBXog6t/p4FHqAHftHYzwQvQXIINFrmT15PJkbx0lMYEYzw\nPyB7doBHIQKBgGiJi7ZpYYRw1eLH0fOOk1if+uhUqHTrYx/M6MjGRHTryBgXCkzI\n8QIAO9/hqwOirpq2/9pDgXZR1uC90EkC2jlQvPvAUokg7T5ikYpd3Y4ZSkoUjoAS\ngTK20yFvuw4DgVUvJIO7a+14PgjU1MQYC52MEPuv6sbvItX1Oxq/FnRhAoGAHWYK\ncbBSvJzuKtY+CC3gPa0i5cfq07VIVU8Pm7OosM7Q0CR/y88ntgVsscC0qJFwr/EU\ny7aJyBY9TInYqDPzMTeJVXsUwQ5gJut4ngFWk6kitDsJwFqqNFKmeLOj4repY5ee\n79U6kEHJzkOE8VgsH5nW4sjzDEQ9hmhOJ3tFz0ECgYEA8N+7yq1tK/99S8ThYW1J\n9mvUXRhAcFamBYp+8bIBdnQlrM9bGd9j8gYzQj+RBcvfCpVHFM20z8CC8oN0bitk\nh5MEjLBkw1vaywFlA/hcnA8A3g+5/IgHl03Y1tPWnyAtB77vE2M2ThklZ5l4E8eT\nP1vYw9RUSAPjtd43XDTqPNQ=\n-----END PRIVATE KEY-----\n",
"client_email": "tester@psyched-ceiling-413920.iam.gserviceaccount.com",
"client_id": "100697987276606879445",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/tester%40psyched-ceiling-413920.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}

View File

@ -13,7 +13,7 @@ class DishRepository:
def __init__(self, session: AsyncSession = Depends(get_async_session)) -> None: def __init__(self, session: AsyncSession = Depends(get_async_session)) -> None:
self.db = session self.db = session
async def get_dishes(self, menu_id: UUID, submenu_id: UUID) -> list[Dish]: async def get_dishes(self, submenu_id: UUID) -> list[Dish]:
query = select(Dish).where( query = select(Dish).where(
Dish.parent_submenu == submenu_id, Dish.parent_submenu == submenu_id,
) )
@ -22,7 +22,6 @@ class DishRepository:
async def create_dish_item( async def create_dish_item(
self, self,
menu_id: UUID,
submenu_id: UUID, submenu_id: UUID,
dish_data: Dish_db, dish_data: Dish_db,
) -> Dish: ) -> Dish:
@ -35,8 +34,6 @@ class DishRepository:
async def get_dish_item( async def get_dish_item(
self, self,
menu_id: UUID,
submenu_id: UUID,
dish_id: UUID, dish_id: UUID,
) -> Dish | None: ) -> Dish | None:
query = select(Dish).where(Dish.id == dish_id) query = select(Dish).where(Dish.id == dish_id)
@ -45,8 +42,6 @@ class DishRepository:
async def update_dish_item( async def update_dish_item(
self, self,
menu_id: UUID,
submenu_id: UUID,
dish_id: UUID, dish_id: UUID,
dish_data: Dish_db, dish_data: Dish_db,
) -> Dish | None: ) -> Dish | None:
@ -59,8 +54,6 @@ class DishRepository:
async def delete_dish_item( async def delete_dish_item(
self, self,
menu_id: UUID,
submenu_id: UUID,
dish_id: UUID, dish_id: UUID,
) -> None: ) -> None:
query = delete(Dish).where(Dish.id == dish_id) query = delete(Dish).where(Dish.id == dish_id)

View File

@ -32,14 +32,13 @@ class SubMenuRepository:
await self.db.commit() await self.db.commit()
await self.db.refresh(new_submenu) await self.db.refresh(new_submenu)
full_sub = await self.get_submenu_item(menu_id, new_submenu.id) full_sub = await self.get_submenu_item(new_submenu.id)
if full_sub is None: if full_sub is None:
raise TypeError raise TypeError
return full_sub return full_sub
async def get_submenu_item( async def get_submenu_item(
self, self,
menu_id: UUID,
submenu_id: UUID, submenu_id: UUID,
) -> SubMenu | None: ) -> SubMenu | None:
s = aliased(SubMenu) s = aliased(SubMenu)
@ -56,7 +55,6 @@ class SubMenuRepository:
async def update_submenu_item( async def update_submenu_item(
self, self,
menu_id: UUID,
submenu_id: UUID, submenu_id: UUID,
submenu_data: MenuBase, submenu_data: MenuBase,
) -> SubMenu | None: ) -> SubMenu | None:
@ -71,7 +69,7 @@ class SubMenuRepository:
updated_submenu = await self.db.execute(qr) updated_submenu = await self.db.execute(qr)
return updated_submenu.scalar_one_or_none() return updated_submenu.scalar_one_or_none()
async def delete_submenu_item(self, menu_id: UUID, submenu_id: UUID) -> None: async def delete_submenu_item(self, submenu_id: UUID) -> None:
query = delete(SubMenu).where( query = delete(SubMenu).where(
SubMenu.id == submenu_id, SubMenu.id == submenu_id,
) )

View File

@ -1,3 +1,5 @@
from typing import Any
from fastapi import Depends from fastapi import Depends
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
@ -11,7 +13,7 @@ class SummaryRepository:
def __init__(self, session: AsyncSession = Depends(get_async_session)) -> None: def __init__(self, session: AsyncSession = Depends(get_async_session)) -> None:
self.db = session self.db = session
async def get_data(self): async def get_data(self) -> list[Any]:
query = select(Menu).options( query = select(Menu).options(
selectinload(Menu.submenus).selectinload(SubMenu.dishes) selectinload(Menu.submenus).selectinload(SubMenu.dishes)
) )

View File

@ -1,6 +1,6 @@
from uuid import UUID from uuid import UUID
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from fastfood.schemas import Dish, DishBase from fastfood.schemas import Dish, DishBase
from fastfood.service.dish import DishService from fastfood.service.dish import DishService
@ -19,7 +19,6 @@ async def get_dishes(
menu_id: UUID, menu_id: UUID,
submenu_id: UUID, submenu_id: UUID,
dish: DishService = Depends(), dish: DishService = Depends(),
background_tasks: BackgroundTasks = BackgroundTasks(),
) -> list[Dish]: ) -> list[Dish]:
result = await dish.read_dishes(menu_id, submenu_id) result = await dish.read_dishes(menu_id, submenu_id)
return result return result
@ -35,7 +34,6 @@ async def create_dish(
submenu_id: UUID, submenu_id: UUID,
dish_data: DishBase, dish_data: DishBase,
dish: DishService = Depends(), dish: DishService = Depends(),
background_tasks: BackgroundTasks = BackgroundTasks(),
) -> Dish: ) -> Dish:
return await dish.create_dish( return await dish.create_dish(
menu_id, menu_id,
@ -53,7 +51,6 @@ async def get_dish(
submenu_id: UUID, submenu_id: UUID,
dish_id: UUID, dish_id: UUID,
dish: DishService = Depends(), dish: DishService = Depends(),
background_tasks: BackgroundTasks = BackgroundTasks(),
) -> Dish | None: ) -> Dish | None:
result = await dish.read_dish( result = await dish.read_dish(
menu_id, menu_id,
@ -78,7 +75,6 @@ async def update_dish(
dish_id: UUID, dish_id: UUID,
dish_data: DishBase, dish_data: DishBase,
dish: DishService = Depends(), dish: DishService = Depends(),
background_tasks: BackgroundTasks = BackgroundTasks(),
) -> Dish: ) -> Dish:
result = await dish.update_dish( result = await dish.update_dish(
menu_id, menu_id,
@ -102,6 +98,5 @@ async def delete_dish(
submenu_id: UUID, submenu_id: UUID,
dish_id: UUID, dish_id: UUID,
dish: DishService = Depends(), dish: DishService = Depends(),
background_tasks: BackgroundTasks = BackgroundTasks(),
) -> None: ) -> None:
await dish.del_dish(menu_id, submenu_id, dish_id) await dish.del_dish(menu_id, dish_id)

View File

@ -1,6 +1,6 @@
from uuid import UUID from uuid import UUID
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from fastfood.schemas import MenuBase, MenuRead from fastfood.schemas import MenuBase, MenuRead
from fastfood.service.menu import MenuService from fastfood.service.menu import MenuService
@ -18,7 +18,6 @@ router = APIRouter(
) )
async def get_menus( async def get_menus(
menu: MenuService = Depends(), menu: MenuService = Depends(),
background_tasks: BackgroundTasks = BackgroundTasks(),
) -> list[MenuRead]: ) -> list[MenuRead]:
return await menu.read_menus() return await menu.read_menus()
@ -31,7 +30,6 @@ async def get_menus(
async def add_menu( async def add_menu(
menu: MenuBase, menu: MenuBase,
responce: MenuService = Depends(), responce: MenuService = Depends(),
background_tasks: BackgroundTasks = BackgroundTasks(),
) -> MenuRead: ) -> MenuRead:
return await responce.create_menu(menu) return await responce.create_menu(menu)
@ -43,7 +41,6 @@ async def add_menu(
async def get_menu( async def get_menu(
menu_id: UUID, menu_id: UUID,
responce: MenuService = Depends(), responce: MenuService = Depends(),
background_tasks: BackgroundTasks = BackgroundTasks(),
) -> MenuRead: ) -> MenuRead:
result = await responce.read_menu(menu_id=menu_id) result = await responce.read_menu(menu_id=menu_id)
@ -63,7 +60,6 @@ async def update_menu(
menu_id: UUID, menu_id: UUID,
menu: MenuBase, menu: MenuBase,
responce: MenuService = Depends(), responce: MenuService = Depends(),
background_tasks: BackgroundTasks = BackgroundTasks(),
) -> MenuRead: ) -> MenuRead:
result = await responce.update_menu( result = await responce.update_menu(
menu_id=menu_id, menu_id=menu_id,
@ -85,6 +81,5 @@ async def update_menu(
async def delete_menu( async def delete_menu(
menu_id: UUID, menu_id: UUID,
menu: MenuService = Depends(), menu: MenuService = Depends(),
background_tasks: BackgroundTasks = BackgroundTasks(),
) -> None: ) -> None:
await menu.del_menu(menu_id) await menu.del_menu(menu_id)

View File

@ -1,6 +1,6 @@
from uuid import UUID from uuid import UUID
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from fastfood.schemas import MenuBase, SubMenuRead from fastfood.schemas import MenuBase, SubMenuRead
from fastfood.service.submenu import SubmenuService from fastfood.service.submenu import SubmenuService
@ -18,7 +18,6 @@ router = APIRouter(
async def get_submenus( async def get_submenus(
menu_id: UUID, menu_id: UUID,
submenu: SubmenuService = Depends(), submenu: SubmenuService = Depends(),
background_tasks: BackgroundTasks = BackgroundTasks(),
) -> list[SubMenuRead]: ) -> list[SubMenuRead]:
result = await submenu.read_submenus(menu_id=menu_id) result = await submenu.read_submenus(menu_id=menu_id)
return result return result
@ -33,7 +32,6 @@ async def create_submenu_item(
menu_id: UUID, menu_id: UUID,
submenu_data: MenuBase, submenu_data: MenuBase,
submenu: SubmenuService = Depends(), submenu: SubmenuService = Depends(),
background_tasks: BackgroundTasks = BackgroundTasks(),
) -> SubMenuRead: ) -> SubMenuRead:
result = await submenu.create_submenu( result = await submenu.create_submenu(
menu_id=menu_id, menu_id=menu_id,
@ -50,7 +48,6 @@ async def get_submenu(
menu_id: UUID, menu_id: UUID,
submenu_id: UUID, submenu_id: UUID,
submenu: SubmenuService = Depends(), submenu: SubmenuService = Depends(),
background_tasks: BackgroundTasks = BackgroundTasks(),
) -> SubMenuRead: ) -> SubMenuRead:
result = await submenu.read_menu( result = await submenu.read_menu(
menu_id=menu_id, menu_id=menu_id,
@ -73,7 +70,6 @@ async def update_submenu(
submenu_id: UUID, submenu_id: UUID,
submenu_data: MenuBase, submenu_data: MenuBase,
submenu: SubmenuService = Depends(), submenu: SubmenuService = Depends(),
background_tasks: BackgroundTasks = BackgroundTasks(),
) -> SubMenuRead: ) -> SubMenuRead:
result = await submenu.update_submenu( result = await submenu.update_submenu(
menu_id=menu_id, menu_id=menu_id,
@ -96,6 +92,5 @@ async def delete_submenu(
menu_id: UUID, menu_id: UUID,
submenu_id: UUID, submenu_id: UUID,
submenu: SubmenuService = Depends(), submenu: SubmenuService = Depends(),
background_tasks: BackgroundTasks = BackgroundTasks(),
) -> None: ) -> None:
await submenu.del_menu(menu_id=menu_id, submenu_id=submenu_id) await submenu.del_menu(menu_id=menu_id, submenu_id=submenu_id)

View File

@ -1,4 +1,4 @@
from fastapi import APIRouter, BackgroundTasks, Depends from fastapi import APIRouter, Depends
from fastfood.schemas import MenuSummary from fastfood.schemas import MenuSummary
from fastfood.service.summary import SummaryService from fastfood.service.summary import SummaryService
@ -12,6 +12,5 @@ router = APIRouter(
@router.get('/', response_model=list[MenuSummary]) @router.get('/', response_model=list[MenuSummary])
async def get_summary( async def get_summary(
sum: SummaryService = Depends(), sum: SummaryService = Depends(),
background_tasks: BackgroundTasks = BackgroundTasks(),
) -> list[MenuSummary]: ) -> list[MenuSummary]:
return await sum.read_data() return await sum.read_data()

View File

@ -42,7 +42,7 @@ class DishService:
if cached_dishes is not None: if cached_dishes is not None:
return cached_dishes return cached_dishes
data = await self.dish_repo.get_dishes(menu_id, submenu_id) data = await self.dish_repo.get_dishes(submenu_id)
response = [] response = []
for row in data: for row in data:
dish = await self._convert_dish_to_dict(row) dish = await self._convert_dish_to_dict(row)
@ -67,7 +67,6 @@ class DishService:
) -> Dish: ) -> Dish:
dish_db = 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,
submenu_id, submenu_id,
dish_db, dish_db,
) )
@ -95,7 +94,7 @@ class DishService:
if cached_dish is not None: if cached_dish is not None:
return cached_dish 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(dish_id)
if data is None: if data is None:
return None return None
dish = await self._convert_dish_to_dict(data) dish = await self._convert_dish_to_dict(data)
@ -116,9 +115,7 @@ class DishService:
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 | None: ) -> Dish | None:
dish_db = Dish_db(**dish_data.model_dump()) dish_db = Dish_db(**dish_data.model_dump())
data = await self.dish_repo.update_dish_item( data = await self.dish_repo.update_dish_item(dish_id, dish_db)
menu_id, submenu_id, dish_id, dish_db
)
if data is None: if data is None:
return None return None
@ -139,10 +136,8 @@ class DishService:
return dish return dish
async def del_dish(self, menu_id: UUID, submenu_id: UUID, dish_id: UUID) -> None: async def del_dish(self, menu_id: UUID, dish_id: UUID) -> None:
await self.dish_repo.delete_dish_item( await self.dish_repo.delete_dish_item(
menu_id,
submenu_id,
dish_id, dish_id,
) )
await self.cache.delete(key=str(menu_id), bg_task=self.bg_tasks) await self.cache.delete(key=str(menu_id), bg_task=self.bg_tasks)

View File

@ -33,7 +33,7 @@ class SubmenuService:
submenus = [] submenus = []
for r in data: for r in data:
submenu = r.__dict__ submenu = r.__dict__
subq = await self.submenu_repo.get_submenu_item(menu_id, r.id) subq = await self.submenu_repo.get_submenu_item(r.id)
if subq is not None: if subq is not None:
submenu['dishes_count'] = len(subq.dishes) submenu['dishes_count'] = len(subq.dishes)
submenu = SubMenuRead(**submenu) submenu = SubMenuRead(**submenu)
@ -73,7 +73,7 @@ class SubmenuService:
if cached_submenu is not None: if cached_submenu is not None:
return cached_submenu return cached_submenu
data = await self.submenu_repo.get_submenu_item(menu_id, submenu_id) data = await self.submenu_repo.get_submenu_item(submenu_id)
if data is None: if data is None:
return None return None
submenu = data.__dict__ submenu = data.__dict__
@ -90,9 +90,7 @@ class SubmenuService:
async def update_submenu( async def update_submenu(
self, menu_id: UUID, submenu_id: UUID, submenu_data: MenuBase self, menu_id: UUID, submenu_id: UUID, submenu_data: MenuBase
) -> SubMenuRead | None: ) -> SubMenuRead | None:
data = await self.submenu_repo.update_submenu_item( data = await self.submenu_repo.update_submenu_item(submenu_id, submenu_data)
menu_id, submenu_id, submenu_data
)
if data is None: if data is None:
return None return None
@ -111,7 +109,7 @@ class SubmenuService:
return submenu return submenu
async def del_menu(self, menu_id: UUID, submenu_id: UUID) -> None: async def del_menu(self, menu_id: UUID, submenu_id: UUID) -> None:
await self.submenu_repo.delete_submenu_item(menu_id, submenu_id) await self.submenu_repo.delete_submenu_item(submenu_id)
await self.cache.delete( await self.cache.delete(
key=self.key( key=self.key(
'submenu', 'submenu',

View File

@ -19,7 +19,7 @@ class SummaryService:
self.key = get_key self.key = get_key
self.bg_tasks = background_tasks self.bg_tasks = background_tasks
async def read_data(self): async def read_data(self) -> list[MenuSummary]:
result = [] result = []