diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..dd0953e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.10-slim + +RUN mkdir /fastfood + +WORKDIR /fastfood + +COPY . . + +RUN pip install poetry + +RUN poetry config virtualenvs.create false + +RUN poetry install + +RUN chmod a+x scripts/*.sh diff --git a/README.md b/README.md index 2483dc3..ad95d7d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,13 @@ # fastfood Fastapi веб приложение реализующее api для общепита. -## Описание +## Оглавление + + + +## Описание Данный проект, это результат выполнения практического домашнего задания интенсива от YLAB Development. Проект реализован на фреймворке fastapi, с использованием sqlalchemy. В качестве базы данных используется postgresql. ### Техническое задание diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2e8e8e2 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,44 @@ +version: "3.8" +services: + + db: + image: postgres:15.1-alpine + env_file: + - .env + container_name: pgdatabase + ports: + - 6432:5432 + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"] + interval: 10s + timeout: 5s + retries: 5 + volumes: + - ./scripts/db_prepare.sql:/docker-entrypoint-initdb.d/db_prepare.sql + + app: + build: + context: . + container_name: fastfood_app + env_file: + - .env + command: ["/fastfood/scripts/migrate_and_run.sh"] + ports: + - 8000:8000 + depends_on: + db: + condition: service_healthy + restart: always + + tests: + build: + context: . + container_name: tests + env_file: + - .env + depends_on: + db: + condition: service_healthy + app: + condition: service_started + command: ["/fastfood/scripts/testing.sh"] diff --git a/fastfood/config.py b/fastfood/config.py index 65ece35..0f7eefa 100644 --- a/fastfood/config.py +++ b/fastfood/config.py @@ -8,6 +8,10 @@ class Settings(BaseSettings): DB_PASS: str = "postgres" DB_NAME: str = "postgres" + POSTGRES_DB: str = "fastfod_db" + POSTGRES_PASSWORD: str = "postgres" + POSTGRES_USER: str = "postgres" + @property def DATABASE_URL_asyncpg(self): """ diff --git a/fastfood/cruds/menu.py b/fastfood/cruds/menu.py index 55561df..5e70d17 100644 --- a/fastfood/cruds/menu.py +++ b/fastfood/cruds/menu.py @@ -2,6 +2,7 @@ from uuid import UUID from sqlalchemy import delete, func, select, update from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import Query, aliased from fastfood import models, schemas @@ -26,21 +27,51 @@ class MenuCrud: @staticmethod async def get_menu_item(menu_id: UUID, session: AsyncSession): async with session: - query = select(models.Menu).where(models.Menu.id == menu_id) + """ Комментарий для проверяющего + То что было, оставил закоментированным, удалю в следующей части + в pgadmin набросал следующий запрос + WITH subq as ( + SELECT + s.id, + s.title, + s.description, + s.parent_menu, + count(d.id) as dishes_count + FROM submenu s + JOIN dish d ON s.id = d.parent_submenu + GROUP BY s.id + ) + SELECT + m.id, + m.title, + m.description, + count(q.id) AS submenus_count, + SUM(q.dishes_count) AS dishes_count + FROM menu m + JOIN subq q ON m.id = q.parent_menu + GROUP BY m.id + """ + m = aliased(models.Menu) + s = aliased(models.SubMenu) + d = aliased(models.Dish) + + query = select(m).where(m.id == menu_id) menu = await session.execute(query) menu = menu.scalars().one_or_none() + if menu is None: return None + submenu_query = select( - func.count(models.SubMenu.id).label("counter") - ).filter(models.SubMenu.parent_menu == menu_id) + func.count(s.id).label("counter") + ).filter(s.parent_menu == menu_id) counter = await session.execute(submenu_query) dish_query = ( - select(func.count(models.Dish.id)) - .join(models.SubMenu) - .filter(models.Dish.parent_submenu == models.SubMenu.id) - .filter(models.SubMenu.parent_menu == menu_id) + select(func.count(d.id)) + .join(s) + .filter(d.parent_submenu == s.id) + .filter(s.parent_menu == menu_id) ) dishes = await session.execute(dish_query) menu.submenus_count = counter.scalars().one_or_none() diff --git a/manage.py b/manage.py index fabcab9..7a44d5f 100644 --- a/manage.py +++ b/manage.py @@ -12,8 +12,11 @@ def run_app(): """ uvicorn.run( app="fastfood.app:create_app", + host="0.0.0.0", + port=8000, reload=True, factory=True, + workers=1, ) diff --git a/poetry.lock b/poetry.lock index 5178d81..d07c37a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,24 @@ # This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +[[package]] +name = "alembic" +version = "1.13.1" +description = "A database migration tool for SQLAlchemy." +optional = false +python-versions = ">=3.8" +files = [ + {file = "alembic-1.13.1-py3-none-any.whl", hash = "sha256:2edcc97bed0bd3272611ce3a98d98279e9c209e7186e43e75bbb1b2bdfdbcc43"}, + {file = "alembic-1.13.1.tar.gz", hash = "sha256:4932c8558bf68f2ee92b9bbcb8218671c627064d5b08939437af6d77dc05e595"}, +] + +[package.dependencies] +Mako = "*" +SQLAlchemy = ">=1.3.0" +typing-extensions = ">=4" + +[package.extras] +tz = ["backports.zoneinfo"] + [[package]] name = "annotated-types" version = "0.6.0" @@ -137,6 +156,73 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "coverage" +version = "7.4.1" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7"}, + {file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b"}, + {file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016"}, + {file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018"}, + {file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295"}, + {file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6"}, + {file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5"}, + {file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968"}, + {file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581"}, + {file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc"}, + {file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74"}, + {file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448"}, + {file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218"}, + {file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad"}, + {file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042"}, + {file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d"}, + {file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54"}, + {file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35"}, + {file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c"}, + {file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a"}, + {file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166"}, + {file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + [[package]] name = "dnspython" version = "2.5.0" @@ -354,6 +440,94 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "mako" +version = "1.3.0" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Mako-1.3.0-py3-none-any.whl", hash = "sha256:57d4e997349f1a92035aa25c17ace371a4213f2ca42f99bee9a602500cfd54d9"}, + {file = "Mako-1.3.0.tar.gz", hash = "sha256:e3a9d388fd00e87043edbe8792f45880ac0114e9c4adc69f6e9bfb2c55e3b11b"}, +] + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["Babel"] +lingua = ["lingua"] +testing = ["pytest"] + +[[package]] +name = "markupsafe" +version = "2.1.4" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de8153a7aae3835484ac168a9a9bdaa0c5eee4e0bc595503c95d53b942879c84"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e888ff76ceb39601c59e219f281466c6d7e66bd375b4ec1ce83bcdc68306796b"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b838c37ba596fcbfca71651a104a611543077156cb0a26fe0c475e1f152ee8"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac1ebf6983148b45b5fa48593950f90ed6d1d26300604f321c74a9ca1609f8e"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbad3d346df8f9d72622ac71b69565e621ada2ce6572f37c2eae8dacd60385d"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5291d98cd3ad9a562883468c690a2a238c4a6388ab3bd155b0c75dd55ece858"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a7cc49ef48a3c7a0005a949f3c04f8baa5409d3f663a1b36f0eba9bfe2a0396e"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83041cda633871572f0d3c41dddd5582ad7d22f65a72eacd8d3d6d00291df26"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-win32.whl", hash = "sha256:0c26f67b3fe27302d3a412b85ef696792c4a2386293c53ba683a89562f9399b0"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:a76055d5cb1c23485d7ddae533229039b850db711c554a12ea64a0fd8a0129e2"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e9e3c4020aa2dc62d5dd6743a69e399ce3de58320522948af6140ac959ab863"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0042d6a9880b38e1dd9ff83146cc3c9c18a059b9360ceae207805567aacccc69"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d03fea4c4e9fd0ad75dc2e7e2b6757b80c152c032ea1d1de487461d8140efc"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ab3a886a237f6e9c9f4f7d272067e712cdb4efa774bef494dccad08f39d8ae6"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf5ebbec056817057bfafc0445916bb688a255a5146f900445d081db08cbabb"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e1a0d1924a5013d4f294087e00024ad25668234569289650929ab871231668e7"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e7902211afd0af05fbadcc9a312e4cf10f27b779cf1323e78d52377ae4b72bea"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c669391319973e49a7c6230c218a1e3044710bc1ce4c8e6eb71f7e6d43a2c131"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-win32.whl", hash = "sha256:31f57d64c336b8ccb1966d156932f3daa4fee74176b0fdc48ef580be774aae74"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:54a7e1380dfece8847c71bf7e33da5d084e9b889c75eca19100ef98027bd9f56"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a76cd37d229fc385738bd1ce4cba2a121cf26b53864c1772694ad0ad348e509e"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:987d13fe1d23e12a66ca2073b8d2e2a75cec2ecb8eab43ff5624ba0ad42764bc"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5244324676254697fe5c181fc762284e2c5fceeb1c4e3e7f6aca2b6f107e60dc"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78bc995e004681246e85e28e068111a4c3f35f34e6c62da1471e844ee1446250"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4d176cfdfde84f732c4a53109b293d05883e952bbba68b857ae446fa3119b4f"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f9917691f410a2e0897d1ef99619fd3f7dd503647c8ff2475bf90c3cf222ad74"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f06e5a9e99b7df44640767842f414ed5d7bedaaa78cd817ce04bbd6fd86e2dd6"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396549cea79e8ca4ba65525470d534e8a41070e6b3500ce2414921099cb73e8d"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-win32.whl", hash = "sha256:f6be2d708a9d0e9b0054856f07ac7070fbe1754be40ca8525d5adccdbda8f475"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:5045e892cfdaecc5b4c01822f353cf2c8feb88a6ec1c0adef2a2e705eef0f656"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a07f40ef8f0fbc5ef1000d0c78771f4d5ca03b4953fc162749772916b298fc4"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d18b66fe626ac412d96c2ab536306c736c66cf2a31c243a45025156cc190dc8a"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:698e84142f3f884114ea8cf83e7a67ca8f4ace8454e78fe960646c6c91c63bfa"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a3b78a5af63ec10d8604180380c13dcd870aba7928c1fe04e881d5c792dc4e"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:15866d7f2dc60cfdde12ebb4e75e41be862348b4728300c36cdf405e258415ec"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6aa5e2e7fc9bc042ae82d8b79d795b9a62bd8f15ba1e7594e3db243f158b5565"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:54635102ba3cf5da26eb6f96c4b8c53af8a9c0d97b64bdcb592596a6255d8518"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-win32.whl", hash = "sha256:3583a3a3ab7958e354dc1d25be74aee6228938312ee875a22330c4dc2e41beb0"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-win_amd64.whl", hash = "sha256:d6e427c7378c7f1b2bef6a344c925b8b63623d3321c09a237b7cc0e77dd98ceb"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bf1196dcc239e608605b716e7b166eb5faf4bc192f8a44b81e85251e62584bd2"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4df98d4a9cd6a88d6a585852f56f2155c9cdb6aec78361a19f938810aa020954"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b835aba863195269ea358cecc21b400276747cc977492319fd7682b8cd2c253d"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23984d1bdae01bee794267424af55eef4dfc038dc5d1272860669b2aa025c9e3"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c98c33ffe20e9a489145d97070a435ea0679fddaabcafe19982fe9c971987d5"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9896fca4a8eb246defc8b2a7ac77ef7553b638e04fbf170bff78a40fa8a91474"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b0fe73bac2fed83839dbdbe6da84ae2a31c11cfc1c777a40dbd8ac8a6ed1560f"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c7556bafeaa0a50e2fe7dc86e0382dea349ebcad8f010d5a7dc6ba568eaaa789"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-win32.whl", hash = "sha256:fc1a75aa8f11b87910ffd98de62b29d6520b6d6e8a3de69a70ca34dea85d2a8a"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-win_amd64.whl", hash = "sha256:3a66c36a3864df95e4f62f9167c734b3b1192cb0851b43d7cc08040c074c6279"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:765f036a3d00395a326df2835d8f86b637dbaf9832f90f5d196c3b8a7a5080cb"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:21e7af8091007bf4bebf4521184f4880a6acab8df0df52ef9e513d8e5db23411"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5c31fe855c77cad679b302aabc42d724ed87c043b1432d457f4976add1c2c3e"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653fa39578957bc42e5ebc15cf4361d9e0ee4b702d7d5ec96cdac860953c5b4"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47bb5f0142b8b64ed1399b6b60f700a580335c8e1c57f2f15587bd072012decc"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fe8512ed897d5daf089e5bd010c3dc03bb1bdae00b35588c49b98268d4a01e00"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:36d7626a8cca4d34216875aee5a1d3d654bb3dac201c1c003d182283e3205949"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b6f14a9cd50c3cb100eb94b3273131c80d102e19bb20253ac7bd7336118a673a"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-win32.whl", hash = "sha256:c8f253a84dbd2c63c19590fa86a032ef3d8cc18923b8049d91bcdeeb2581fbf6"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:8b570a1537367b52396e53325769608f2a687ec9a4363647af1cded8928af959"}, + {file = "MarkupSafe-2.1.4.tar.gz", hash = "sha256:3aae9af4cac263007fd6309c64c6ab4506dd2b79382d9d19a1994f9240b8db4f"}, +] + [[package]] name = "packaging" version = "23.2" @@ -652,6 +826,24 @@ pytest = ">=7.0.0" docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] +[[package]] +name = "pytest-cov" +version = "4.1.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + [[package]] name = "python-dotenv" version = "1.0.0" @@ -825,4 +1017,4 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "65d9e5359044da0053c55ad7cd84666c115837d7e6da4f72d41fde0ad349c16b" +content-hash = "21d68c7a50ac5fb0af89a33a484094ea6a69f1e9a03d0f8cb5c6dc35400c760a" diff --git a/pyproject.toml b/pyproject.toml index 53b471c..604b59b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,8 @@ psycopg2-binary = "^2.9.9" email-validator = "^2.1.0.post1" pytest-asyncio = "^0.23.3" httpx = "^0.26.0" +pytest-cov = "^4.1.0" +alembic = "^1.13.1" [tool.poetry.group.dev.dependencies] diff --git a/scripts/db_prepare.sql b/scripts/db_prepare.sql new file mode 100644 index 0000000..fa300f9 --- /dev/null +++ b/scripts/db_prepare.sql @@ -0,0 +1 @@ +CREATE DATABASE fastfood_db_test WITH OWNER postgres; diff --git a/scripts/migrate_and_run.sh b/scripts/migrate_and_run.sh new file mode 100644 index 0000000..531afac --- /dev/null +++ b/scripts/migrate_and_run.sh @@ -0,0 +1,2 @@ +#!/bin/bash +poetry run python manage.py --run-test-server diff --git a/scripts/testing.sh b/scripts/testing.sh new file mode 100644 index 0000000..d075b46 --- /dev/null +++ b/scripts/testing.sh @@ -0,0 +1,2 @@ +#!/bin/bash +poetry run pytest -vv diff --git a/tests/test_api.py b/tests/test_api.py index 6b012c6..dc3b941 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,7 +1,7 @@ import pytest -class TestCrud: +class TestBaseCrud: class Menu: @staticmethod async def read_all(ac): @@ -31,6 +31,7 @@ class TestCrud: async def delete(ac, data): """Удаление меню по id""" response = await ac.delete(f"/{data.get('id')}") + return response.status_code class Submenu: @staticmethod @@ -71,6 +72,50 @@ class TestCrud: response = await ac.delete( f"/{menu.get('id')}/submenus/{submenu.get('id')}" ) + return response.status_code + + class Dish: + @staticmethod + async def read_all(ac, menu, submenu): + """чтение всех блюд""" + response = await ac.get( + f"/{menu.get('id')}/submenus/{submenu.get('id')}/dishes/", + ) + return response.status_code, response.json() + + @staticmethod + async def get(ac, menu, submenu, dish): + """Получение блюда по id""" + response = await ac.get( + f"/{menu.get('id')}/submenus/{submenu.get('id')}/dishes/{dish.get('id')}", + ) + return response.status_code, response.json() + + @staticmethod + async def write(ac, menu, submenu, dish): + """создания блюда""" + 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, menu, submenu, dish): + """Обновление блюда по id""" + response = await ac.patch( + f"/{menu.get('id')}/submenus/{submenu.get('id')}/dishes/{dish.get('id')}", + json=dish, + ) + return response.status_code, response.json() + + @staticmethod + async def delete(ac, menu, submenu, dish): + """Удаление блюда по id""" + response = await ac.delete( + f"/{menu.get('id')}/submenus/{submenu.get('id')}/dishes/{dish.get('id')}" + ) + return response.status_code @pytest.mark.asyncio async def test_menu_crud(self, client): @@ -98,12 +143,13 @@ class TestCrud: assert upd_rspn["title"] == "upd Menu" 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_submenu(self, client): + async def test_submenus(self, client): # Создаем меню и проверяем ответ menu = {"title": "Menu", "description": "main menu"} code, rspn = await self.Menu.write(client, menu) @@ -138,7 +184,8 @@ class TestCrud: submenu.update(rspn) # Удаляем подменю - await self.Submenu.delete(client, menu, submenu) + code = await self.Submenu.delete(client, menu, submenu) + assert code == 200 # Проверяем меню code, rspn = await self.Menu.get(client, menu) @@ -152,10 +199,182 @@ class TestCrud: # удаляем сопутствующее await self.Menu.delete(client, menu) + @pytest.mark.asyncio + async def test_dishes(self, client): + # Создаем меню и проверяем ответ + menu = { + "title": "Menu", + "description": "main menu", + } + code, rspn = await self.Menu.write(client, menu) + assert code == 201 + menu.update(rspn) -# -# class TestСontinuity: -# @pytest.mark.asyncio -# async def test_postman_continuity(self, app): -# async with AsyncClient(app=app, base_url=url) as ac: -# pass + # Создаем и проверяем подменю + 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 == []