Compare commits

..

No commits in common. "ead24d9f284cdd7e555663b854e016f1cedabfa8" and "732bf9928c6dc8bf02f0f65aa652a5df2bf8edc9" have entirely different histories.

14 changed files with 90 additions and 142 deletions

View File

@ -1,13 +1,15 @@
FROM python:3.10-slim
RUN mkdir /fastfood
WORKDIR /fastfood
COPY . .
RUN pip install poetry
RUN poetry config virtualenvs.create false
RUN mkdir -p /usr/src/fastfood
WORKDIR /usr/src/fastfood
COPY . .
RUN poetry install
RUN chmod a+x scripts/*.sh

View File

@ -98,11 +98,7 @@ Fastapi веб приложение реализующее api для общеп
где <db_name> и <db_user> соответвтовали POSTGRES_DB и POSTGRES_USER в файле `.env`
Создайте и запустите образы
Запуск FAstAPI приложения
> `$ docker-compose -f compose_app.yml up -d`
Запуск тестов
> `$ docker-compose -f compose_test.yml up`
> `$ docker-compose up -d --build`
После успешного запуска образов документация по API будет доступна по адресу <a href="http://localhost:8000/docs">http://localhost:8000</a>

View File

@ -1,45 +0,0 @@
version: "3.8"
services:
db:
container_name: pgdb
image: postgres:15.1-alpine
env_file:
- .env
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
ports:
- 6432:5432
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
app:
container_name: fastfood_app
build:
context: .
env_file:
- .env
ports:
- 8000:8000
depends_on:
db:
condition: service_healthy
restart: always
command: /bin/bash -c 'poetry run python /usr/src/fastfood/manage.py --run-test-server'

View File

@ -1,45 +0,0 @@
version: "3.8"
services:
db:
container_name: pgdb_test
image: postgres:15.1-alpine
env_file:
- .env
environment:
POSTGRES_DB: "${POSTGRES_DB}_test"
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
ports:
- 6432:5432
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}_test"]
interval: 10s
timeout: 5s
retries: 5
app:
container_name: fastfood_app_test
build:
context: .
env_file:
- .env
ports:
- 8000:8000
depends_on:
db:
condition: service_healthy
restart: always
command: /bin/bash -c 'poetry run pytest -vv'

44
docker-compose.yml Normal file
View File

@ -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"]

View File

@ -1,5 +1,5 @@
DB_HOST=db
DB_PORT=5432
POSTGRES_USER=testuser
POSTGRES_PASSWORD=test
POSTGRES_USER=postges
POSTGRES_PASSWORD=postgres
POSTGRES_DB=fastfood_db

View File

@ -61,7 +61,7 @@ tags_metadata = [
]
def create_app() -> FastAPI:
def create_app():
"""
Фабрика FastAPI.
"""

View File

@ -4,10 +4,9 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
DB_HOST: str = "db"
DB_PORT: int = 5432
POSTGRES_DB: str = "fastfood_db"
POSTGRES_PASSWORD: str = "test"
POSTGRES_USER: str = "testuser"
POSTGRES_DB_TEST: str = "fastfood_db_test"
POSTGRES_DB: str = "fastfod_db"
POSTGRES_PASSWORD: str = "postgres"
POSTGRES_USER: str = "postgres"
@property
def DATABASE_URL_asyncpg(self):
@ -28,7 +27,7 @@ class Settings(BaseSettings):
return (
"postgresql+asyncpg://"
f"{self.POSTGRES_USER}:{self.POSTGRES_PASSWORD}"
f"@{self.DB_HOST}:{self.DB_PORT}/{self.POSTGRES_DB_TEST}"
f"@{self.DB_HOST}:{self.DB_PORT}/{self.POSTGRES_DB}_test"
)
model_config = SettingsConfigDict(env_file=".env")

1
scripts/db_prepare.sql Normal file
View File

@ -0,0 +1 @@
CREATE DATABASE fastfood_db_test WITH OWNER postgres;

View File

@ -0,0 +1,5 @@
#!/bin/bash
#
# Тут можно выполнить миграции или дополнительные перед запуском приложения
#
poetry run python manage.py --run-test-server

2
scripts/testing.sh Normal file
View File

@ -0,0 +1,2 @@
#!/bin/bash
poetry run pytest -vv

View File

@ -1,18 +1,20 @@
import asyncio
from typing import AsyncGenerator, Generator
from typing import AsyncGenerator
from httpx import AsyncClient
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
from fastfood.dbase import get_async_session
from fastfood.models import Base
async_engine = create_async_engine(settings.TESTDATABASE_URL_asyncpg)
async_session_maker = async_sessionmaker(
async_engine,
@ -21,7 +23,7 @@ async_session_maker = async_sessionmaker(
)
@pytest.fixture(scope="session", autouse=True)
@pytest.fixture(scope="session")
def event_loop():
try:
loop = asyncio.get_event_loop()
@ -32,7 +34,7 @@ def event_loop():
@pytest_asyncio.fixture(scope="function", autouse=True)
async def db_init(event_loop):
async def db_init():
async with async_engine.begin() as conn:
await conn.run_sync(Base.metadata.drop_all)
await conn.run_sync(Base.metadata.create_all)
@ -47,22 +49,21 @@ async def get_test_session() -> AsyncGenerator[AsyncSession, None]:
@pytest.fixture(scope="session")
def app(event_loop) -> Generator[FastAPI, None, None]:
app: FastAPI = create_app()
def app():
app = create_app()
app.dependency_overrides[get_async_session] = get_test_session
yield app
@pytest_asyncio.fixture(scope="function")
async def client(app) -> AsyncGenerator[AsyncClient, None]:
async def client(app):
async with AsyncClient(
app=app,
base_url="http://localhost:8000/api/v1/menus",
app=app, base_url="http://localhost:8000/api/v1/menus",
) as async_client:
yield async_client
@pytest_asyncio.fixture(scope="function")
async def asession(event_loop) -> AsyncGenerator[AsyncSession, None]:
async def asession() -> AsyncGenerator[AsyncSession, None]:
async with async_session_maker() as session:
yield session

View File

@ -47,11 +47,7 @@ class TestBaseCrud:
return response.status_code, response.json()
@staticmethod
async def get(
ac: AsyncClient,
menu: dict,
submenu: dict,
) -> Tuple[int, dict]:
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')}",
@ -59,11 +55,7 @@ class TestBaseCrud:
return response.status_code, response.json()
@staticmethod
async def write(
ac: AsyncClient,
menu: dict,
submenu: dict,
) -> Tuple[int, dict]:
async def write(ac: AsyncClient, menu: dict, submenu: dict) -> Tuple[int, dict]:
"""создания меню"""
response: Response = await ac.post(
f"/{menu.get('id')}/submenus/",

View File

@ -48,7 +48,7 @@ async def test_menu(asession: AsyncSession) -> None:
async def test_submenu(asession: AsyncSession) -> None:
async with asession:
# Создаем меню напрямую
menu = Menu(title="SomeMenu", description="SomeDescription")
menu: Menu = Menu(title="SomeMenu", description="SomeDescription")
asession.add(menu)
await asession.commit()
await asession.refresh(menu)
@ -69,18 +69,14 @@ async def test_submenu(asession: AsyncSession) -> None:
# Проверяем подменю
req_submenu = await SubMenuCrud.get_submenu_item(
menu_id,
submenu.id,
asession,
menu_id, submenu.id, asession,
)
assert submenu == req_submenu
# Обновляем меню
submenu.title = "UpdatedSubmenu"
req_submenu = await SubMenuCrud.update_submenu_item(
submenu_id,
menubaseschema.model_validate(submenu),
asession,
submenu_id, menubaseschema.model_validate(submenu), asession,
)
assert submenu == req_submenu.scalar_one_or_none()