Compare commits
2 Commits
1afb88d746
...
53aeaef20f
Author | SHA1 | Date |
---|---|---|
|
53aeaef20f | |
|
ca83ca6e2a |
File diff suppressed because it is too large
Load Diff
|
@ -15,6 +15,8 @@ uvicorn = "^0.30.6"
|
|||
sqlalchemy = "^2.0.32"
|
||||
asyncpg = "^0.29.0"
|
||||
alembic = "^1.13.2"
|
||||
pytest-xdist = "^3.6.1"
|
||||
httpx = "^0.28.1"
|
||||
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
|
@ -23,13 +25,19 @@ pytest = "^8.3.2"
|
|||
pytest-asyncio = "^0.23.8"
|
||||
pytest-flakefinder = "^1.1.0"
|
||||
pytest-randomly = "^3.15.0"
|
||||
pytest-cov = "^5.0.0"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
api = "fastfood_two.__main__:main"
|
||||
app = "fastfood_two.__main__:main"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
asyncio_mode = "auto"
|
||||
pythonpath = [
|
||||
"src"
|
||||
]
|
||||
testpaths = "tests"
|
||||
addopts = "-n=2 --cov=fastfood_two"
|
||||
|
|
|
@ -4,6 +4,7 @@ from uuid import UUID
|
|||
|
||||
@dataclass(frozen=True)
|
||||
class AddMenuDTO:
|
||||
id: UUID | None
|
||||
title: str
|
||||
description: str | None
|
||||
|
||||
|
@ -23,3 +24,8 @@ class DeleteMenuDTO:
|
|||
@dataclass(frozen=True)
|
||||
class GetMenuByIdDTO:
|
||||
id: UUID
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AddSubMenuDTO(AddMenuDTO):
|
||||
parent_menu: UUID
|
||||
|
|
|
@ -7,3 +7,8 @@ class MenuDTO:
|
|||
id: UUID
|
||||
title: str
|
||||
description: str | None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SubMenuDTO(MenuDTO):
|
||||
parent_menu: UUID
|
||||
|
|
|
@ -15,7 +15,7 @@ class AddMenu(Interactor[AddMenuDTO, MenuDTO]):
|
|||
|
||||
menu = await self._menu_gateway.insert_menu(
|
||||
menu=Menu(
|
||||
id=MenuId(uuid4()),
|
||||
id=MenuId(uuid4()) if request.id is None else MenuId(request.id),
|
||||
title=Title(request.title),
|
||||
description=Description(request.description),
|
||||
)
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
from uuid import uuid4
|
||||
|
||||
from fastfood_two.application.abstractions.interactor import Interactor
|
||||
from fastfood_two.application.contracts.requests import AddMenuDTO, AddSubMenuDTO
|
||||
from fastfood_two.application.contracts.responses import SubMenuDTO
|
||||
from fastfood_two.domain.submenu.gateway import SubMenuGateway
|
||||
from fastfood_two.domain.submenu.submenu_entity import (
|
||||
Description,
|
||||
SubMenu,
|
||||
SubMenuId,
|
||||
Title,
|
||||
)
|
||||
|
||||
|
||||
class AddMenu(Interactor[AddSubMenuDTO, SubMenuDTO]):
|
||||
def __init__(self, gateway: SubMenuGateway) -> None:
|
||||
self._menu_gateway = gateway
|
||||
|
||||
async def __call__(self, request: AddSubMenuDTO) -> SubMenuDTO:
|
||||
|
||||
menu = await self._menu_gateway.insert_submenu(
|
||||
menu=SubMenu(
|
||||
id=SubMenuId(uuid4()) if request.id is None else SubMenuId(request.id),
|
||||
title=Title(request.title),
|
||||
description=Description(request.description),
|
||||
)
|
||||
)
|
||||
return SubMenuDTO(
|
||||
id=menu.id.value,
|
||||
parent_menu=menu.parent_menu.value,
|
||||
title=menu.title.value,
|
||||
description=menu.description.value,
|
||||
)
|
|
@ -0,0 +1,9 @@
|
|||
from fastfood_two.domain.core.error import DomainError
|
||||
|
||||
|
||||
class SubMenuNotFoundError(DomainError):
|
||||
pass
|
||||
|
||||
|
||||
class SubMenuDataValidationError(DomainError):
|
||||
pass
|
|
@ -0,0 +1,30 @@
|
|||
from typing import Protocol
|
||||
|
||||
from fastfood_two.domain.submenu.submenu_entity import (
|
||||
Description,
|
||||
SubMenu,
|
||||
SubMenuId,
|
||||
Title,
|
||||
)
|
||||
|
||||
|
||||
class SubMenuGateway(Protocol):
|
||||
async def get_submenu_by_id(self, id: SubMenuId) -> SubMenu | None:
|
||||
raise NotImplementedError
|
||||
|
||||
async def insert_submenu(self, menu: SubMenu) -> SubMenu:
|
||||
raise NotImplementedError
|
||||
|
||||
async def get_all_submenus_by_menu_id(self) -> list[SubMenu]:
|
||||
raise NotImplementedError
|
||||
|
||||
async def update_submenu(
|
||||
self,
|
||||
id: SubMenuId,
|
||||
title: Title | None,
|
||||
description: Description | None,
|
||||
) -> SubMenu:
|
||||
raise NotImplementedError
|
||||
|
||||
async def delete_submenu(self, id: SubMenuId) -> None:
|
||||
raise NotImplementedError
|
|
@ -0,0 +1,34 @@
|
|||
from dataclasses import dataclass
|
||||
from uuid import UUID
|
||||
|
||||
from fastfood_two.domain.core.entity import DomainEntity
|
||||
from fastfood_two.domain.core.value_obj import DomainValueObject
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SubMenuId(DomainValueObject):
|
||||
value: UUID
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Title(DomainValueObject):
|
||||
value: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Description(DomainValueObject):
|
||||
value: str | None
|
||||
|
||||
|
||||
@dataclass
|
||||
class SubMenu(DomainEntity[SubMenuId]):
|
||||
title: Title
|
||||
description: Description
|
||||
|
||||
@staticmethod
|
||||
def create(id: UUID, title: str, description: str | None) -> "Menu":
|
||||
return SubMenu(
|
||||
id=SubMenuId(id),
|
||||
title=Title(title),
|
||||
description=Description(description),
|
||||
)
|
|
@ -12,18 +12,18 @@ class MenuGatewayImpl:
|
|||
self._session = session
|
||||
|
||||
async def get_all_menus(self) -> list[Menu]:
|
||||
query = text("SELECT * FROM menu;")
|
||||
query = text("SELECT * FROM menus;")
|
||||
menus = await self._session.execute(query)
|
||||
return [db_entity_to_domain(menu.tuple()) for menu in menus]
|
||||
return [db_entity_to_domain(menu._tuple()) for menu in menus]
|
||||
|
||||
async def get_menu_by_id(self, id: MenuId) -> Menu | None:
|
||||
query = text("SELECT * FROM menu WHERE id = :id;")
|
||||
query = text("SELECT * FROM menus WHERE id = :id;")
|
||||
menu = (await self._session.execute(query, {"id": id.value})).tuples().first()
|
||||
|
||||
return db_entity_to_domain(menu) if menu else None
|
||||
|
||||
async def insert_menu(self, menu: Menu) -> Menu:
|
||||
query = text("INSERT INTO menu (id, title, description) VALUES (:id, :title, :description);")
|
||||
query = text("INSERT INTO menus (id, title, description) VALUES (:id, :title, :description);")
|
||||
await self._session.execute(
|
||||
query,
|
||||
{
|
||||
|
@ -37,7 +37,7 @@ class MenuGatewayImpl:
|
|||
|
||||
async def update_menu(self, id: MenuId, title: Title | None, description: Description | None) -> Menu:
|
||||
query = text(
|
||||
"UPDATE menu SET {}{} {} WHERE id = :id RETURNING *;".format(
|
||||
"UPDATE menus SET {}{} {} WHERE id = :id RETURNING *;".format(
|
||||
"title = :title " if title is not None else "",
|
||||
"," if all([title is not None, description is not None]) else "",
|
||||
"description = :description" if description is not None else "",
|
||||
|
@ -52,9 +52,9 @@ class MenuGatewayImpl:
|
|||
},
|
||||
)
|
||||
await self._session.commit()
|
||||
return db_entity_to_domain(menu.one().tuple())
|
||||
return db_entity_to_domain(menu.one()._tuple())
|
||||
|
||||
async def delete_menu(self, id: MenuId) -> None:
|
||||
query = text("DELETE FROM menu WHERE id = :id;")
|
||||
query = text("DELETE FROM menus WHERE id = :id;")
|
||||
await self._session.execute(query, {"id": id.value})
|
||||
await self._session.commit()
|
||||
|
|
|
@ -8,11 +8,11 @@ from .common_attrs import str_25, uuidpk
|
|||
|
||||
|
||||
class Dish(Base):
|
||||
__tablename__ = "dish"
|
||||
__tablename__ = "dishes"
|
||||
|
||||
id: Mapped[uuidpk]
|
||||
title: Mapped[str_25]
|
||||
description: Mapped[str | None]
|
||||
price: Mapped[float]
|
||||
|
||||
parent_submenu: Mapped[uuid.UUID] = mapped_column(ForeignKey("menu.id", ondelete="CASCADE"))
|
||||
parent_submenu: Mapped[uuid.UUID] = mapped_column(ForeignKey("menus.id", ondelete="CASCADE"))
|
||||
|
|
|
@ -8,15 +8,19 @@ from .common_attrs import str_25, uuidpk
|
|||
class SQLAMenu(Base):
|
||||
__tablename__ = "menus"
|
||||
|
||||
id: Mapped[uuidpk]
|
||||
id: Mapped[uuidpk] = mapped_column(primary_key=True, unique=True)
|
||||
title: Mapped[str_25]
|
||||
description: Mapped[str | None]
|
||||
|
||||
parent: Mapped[uuidpk | None] = mapped_column(ForeignKey("menus.id", ondelete="CASCADE"), nullable=True)
|
||||
parent: Mapped["SQLAMenu"] = mapped_column(ForeignKey("menus.id", ondelete="CASCADE"), nullable=True)
|
||||
|
||||
submenus: Mapped[list["SQLAMenu"]] = relationship(
|
||||
"SQLAMenu",
|
||||
backref="menus",
|
||||
lazy="selectin",
|
||||
cascade="all, delete",
|
||||
"SQLAMenu", backref="menus", lazy="selectin", cascade="all, delete", remote_side=id
|
||||
)
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
"id": str(self.id),
|
||||
"title": self.title,
|
||||
"description": self.description,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
from fastapi import Request
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from fastfood_two.domain.menu.error import MenuDataValidationError, MenuNotFoundError
|
||||
|
||||
|
||||
async def handle_menu_not_found_error(request: Request, error: MenuNotFoundError) -> JSONResponse:
|
||||
return JSONResponse(status_code=404, content={"message": error.message})
|
||||
|
||||
|
||||
async def handle_menu_data_validation_error(request: Request, error: MenuDataValidationError) -> JSONResponse:
|
||||
return JSONResponse(status_code=422, content={"message": error.message})
|
|
@ -1,5 +1,11 @@
|
|||
from fastapi import FastAPI
|
||||
|
||||
from fastfood_two.domain.menu.error import MenuDataValidationError, MenuNotFoundError
|
||||
from fastfood_two.presentation.fastapi_backend.depends.errors import (
|
||||
handle_menu_data_validation_error,
|
||||
handle_menu_not_found_error,
|
||||
)
|
||||
|
||||
|
||||
def init_errorhandlers(app: FastAPI) -> None:
|
||||
"""Initialize FastAPI error handlers.
|
||||
|
@ -7,4 +13,5 @@ def init_errorhandlers(app: FastAPI) -> None:
|
|||
:param app: FastAPI application
|
||||
:type app: FastAPI
|
||||
"""
|
||||
pass
|
||||
app.add_exception_handler(MenuNotFoundError, handle_menu_not_found_error)
|
||||
app.add_exception_handler(MenuDataValidationError, handle_menu_data_validation_error)
|
||||
|
|
|
@ -26,10 +26,6 @@ async def app_lifespan(app: FastAPI) -> AsyncGenerator:
|
|||
|
||||
logger.info("Application lifespan started")
|
||||
|
||||
init_dependencies(app)
|
||||
init_errorhandlers(app)
|
||||
init_routers(app)
|
||||
|
||||
yield
|
||||
|
||||
logger.info("Application lifespan stopped")
|
||||
|
@ -48,4 +44,8 @@ def app_factory() -> FastAPI:
|
|||
lifespan=app_lifespan,
|
||||
)
|
||||
|
||||
init_dependencies(app)
|
||||
init_errorhandlers(app)
|
||||
init_routers(app)
|
||||
|
||||
return app
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class AddMenuPDModel(BaseModel):
|
||||
id: UUID | None = None
|
||||
title: str
|
||||
description: str | None
|
||||
|
||||
|
|
|
@ -56,6 +56,8 @@ async def add_menu(
|
|||
|
||||
Parameters
|
||||
----------
|
||||
Optional id: UUID
|
||||
id of the menu
|
||||
title: str
|
||||
title of the menu
|
||||
description: str
|
||||
|
@ -66,7 +68,7 @@ async def add_menu(
|
|||
MenuDTO
|
||||
created menu
|
||||
"""
|
||||
menus = await usecase(request=AddMenuDTO(title=request.title, description=request.description))
|
||||
menus = await usecase(request=AddMenuDTO(id=request.id, title=request.title, description=request.description))
|
||||
return menus
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
import logging
|
||||
import os
|
||||
|
||||
import xdist
|
||||
from pytest import Session, fixture, hookimpl
|
||||
from sqlalchemy.ext.asyncio import (
|
||||
AsyncEngine,
|
||||
AsyncSession,
|
||||
async_sessionmaker,
|
||||
create_async_engine,
|
||||
)
|
||||
|
||||
from fastfood_two.application.config import Config
|
||||
from tests.utils import db_creator, db_deleter, get_test_settings
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
worker_id = os.environ.get("PYTEST_XDIST_WORKER")
|
||||
if worker_id is not None:
|
||||
logging.basicConfig(
|
||||
format=config.getini("log_file_format"),
|
||||
filename=f"tests_{worker_id}.log",
|
||||
level=config.getini("log_file_level"),
|
||||
)
|
||||
|
||||
|
||||
@hookimpl(trylast=True)
|
||||
def pytest_sessionstart(session: Session) -> None:
|
||||
"""Hook запуска приложения в отдельном процессе."""
|
||||
worker_id = xdist.get_xdist_worker_id(session)
|
||||
|
||||
import asyncio
|
||||
|
||||
db_name = f"fastfood_two_test_db_{worker_id}"
|
||||
loop = asyncio.get_event_loop_policy().new_event_loop()
|
||||
loop.run_until_complete(db_creator(name=db_name))
|
||||
loop.close()
|
||||
|
||||
|
||||
@hookimpl(trylast=True)
|
||||
def pytest_sessionfinish(session: Session) -> None:
|
||||
"""Hook остановки приложения."""
|
||||
worker_id = xdist.get_xdist_worker_id(session)
|
||||
import asyncio
|
||||
|
||||
db_name = f"fastfood_two_test_db_{worker_id}"
|
||||
loop = asyncio.get_event_loop_policy().new_event_loop()
|
||||
loop.run_until_complete(db_deleter(name=db_name))
|
||||
loop.close()
|
||||
|
||||
|
||||
@fixture(scope="session")
|
||||
def app_settings() -> Config:
|
||||
worker_id = os.environ.get("PYTEST_XDIST_WORKER")
|
||||
if worker_id == None:
|
||||
worker_id = "master"
|
||||
settings = get_test_settings(db_name=f"fastfood_two_test_db_{worker_id}")
|
||||
return settings
|
||||
|
||||
|
||||
@fixture
|
||||
def get_loop():
|
||||
import asyncio
|
||||
|
||||
loop = asyncio.get_event_loop_policy().new_event_loop()
|
||||
yield loop
|
||||
loop.close()
|
||||
|
||||
|
||||
@fixture
|
||||
def test_engine(app_settings):
|
||||
return create_async_engine(url=app_settings.db.url)
|
||||
|
||||
|
||||
@fixture
|
||||
def test_sessionmaker(test_engine):
|
||||
return async_sessionmaker(test_engine, expire_on_commit=False)
|
||||
|
||||
|
||||
@fixture
|
||||
async def test_session(test_sessionmaker):
|
||||
|
||||
async with test_sessionmaker() as session:
|
||||
try:
|
||||
yield session
|
||||
finally:
|
||||
pass
|
||||
|
||||
|
||||
@fixture
|
||||
def app(app_settings, test_engine, test_sessionmaker, test_session):
|
||||
from fastfood_two.presentation.fastapi_backend.main import app_factory
|
||||
|
||||
app = app_factory()
|
||||
app.dependency_overrides[Config] = lambda: app_settings
|
||||
app.dependency_overrides[AsyncEngine] = lambda: test_engine
|
||||
app.dependency_overrides[async_sessionmaker[AsyncSession]] = lambda: test_sessionmaker
|
||||
app.dependency_overrides[AsyncSession] = lambda: test_session
|
||||
return app
|
|
@ -0,0 +1,74 @@
|
|||
from uuid import uuid4
|
||||
|
||||
from _pytest.fixtures import SubRequest
|
||||
from pytest import fixture
|
||||
from sqlalchemy import delete
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from fastfood_two.infrastructure.pg_storage.models import SQLAMenu
|
||||
|
||||
|
||||
def generate_str():
|
||||
return str(uuid4())
|
||||
|
||||
|
||||
@fixture
|
||||
async def menu(request: SubRequest, test_session: AsyncSession):
|
||||
if request.param is not None:
|
||||
id = uuid4()
|
||||
title = "test_menu"
|
||||
description = None
|
||||
|
||||
match len(request.param):
|
||||
case 2:
|
||||
need_create, need_delete = request.param
|
||||
if not isinstance(need_create, bool):
|
||||
raise ValueError
|
||||
if not isinstance(need_delete, bool):
|
||||
raise ValueError
|
||||
case 3:
|
||||
need_create, need_delete, title = request.param
|
||||
if not isinstance(need_create, bool):
|
||||
raise ValueError
|
||||
if not isinstance(need_delete, bool):
|
||||
raise ValueError
|
||||
if not isinstance(title, str | bool):
|
||||
raise ValueError
|
||||
|
||||
if isinstance(title, bool):
|
||||
title = "test_menu" if not title else generate_str
|
||||
case 4:
|
||||
need_create, need_delete, title, description = request.param
|
||||
if not isinstance(need_create, bool):
|
||||
raise ValueError
|
||||
if not isinstance(need_delete, bool):
|
||||
raise ValueError
|
||||
if not isinstance(title, str | bool):
|
||||
raise ValueError
|
||||
if not isinstance(description, str | bool | None):
|
||||
raise ValueError
|
||||
|
||||
if isinstance(title, bool):
|
||||
title = "test_menu" if not title else generate_str
|
||||
|
||||
if isinstance(description, bool):
|
||||
description = "test_desc" if not description else generate_str
|
||||
|
||||
case _:
|
||||
raise ValueError
|
||||
|
||||
menu = SQLAMenu(id=id, title=title, description=description)
|
||||
|
||||
if need_create is True:
|
||||
test_session.add(menu)
|
||||
await test_session.commit()
|
||||
|
||||
yield menu
|
||||
|
||||
if need_delete is True:
|
||||
stmt = delete(SQLAMenu).where(SQLAMenu.id == menu.id)
|
||||
await test_session.execute(stmt)
|
||||
await test_session.commit()
|
||||
|
||||
else:
|
||||
raise ValueError
|
|
@ -0,0 +1,65 @@
|
|||
from pytest import mark
|
||||
|
||||
|
||||
@mark.parametrize(
|
||||
"menu, is_created",
|
||||
[((False, False), False), ((True, True), True)],
|
||||
ids=["Get empty menu list", "Get non-empty menu list"],
|
||||
indirect=["menu"],
|
||||
)
|
||||
async def test_get_menu_list(client, menu, is_created: bool):
|
||||
response = await client.get("menu/")
|
||||
assert response.status_code == 200
|
||||
if is_created:
|
||||
expected = [menu.to_json()]
|
||||
else:
|
||||
expected = []
|
||||
assert response.json() == expected
|
||||
|
||||
|
||||
@mark.parametrize(
|
||||
"menu",
|
||||
[(False, True, False, False), (False, True, False, None)],
|
||||
ids=["Post menu with description", "Post menu without description"],
|
||||
indirect=True,
|
||||
)
|
||||
async def test_post_menu_add(client, menu):
|
||||
response = await client.post("menu/", json=menu.to_json())
|
||||
assert response.status_code == 200
|
||||
assert response.json() == menu.to_json()
|
||||
|
||||
|
||||
@mark.parametrize(
|
||||
"menu, json",
|
||||
[
|
||||
((True, True), {"title": "updated_title"}),
|
||||
((True, True), {"title": "updated_title", "description": "updated_description"}),
|
||||
((True, True), {"title": "updated_title", "description": None}),
|
||||
],
|
||||
ids=[
|
||||
"Update menu title",
|
||||
"Update menu title and description",
|
||||
"Update menu title and description to None",
|
||||
],
|
||||
indirect=["menu"],
|
||||
)
|
||||
async def test_update_menu(client, menu, json):
|
||||
|
||||
response = await client.patch(f"menu/{menu.id}", json=json)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"id": str(menu.id),
|
||||
"title": json.get("title"),
|
||||
"description": json.get("description", None),
|
||||
}
|
||||
|
||||
|
||||
@mark.parametrize(
|
||||
"menu",
|
||||
[(True, False)],
|
||||
ids=["Delete menu"],
|
||||
indirect=True,
|
||||
)
|
||||
async def test_delete_menu(client, menu):
|
||||
response = await client.delete(f"menu/{menu.id}")
|
||||
assert response.status_code == 204
|
|
@ -0,0 +1,13 @@
|
|||
from httpx import ASGITransport, AsyncClient
|
||||
from pytest import fixture
|
||||
|
||||
|
||||
@fixture
|
||||
async def client(app):
|
||||
transport = ASGITransport(app=app)
|
||||
|
||||
async with AsyncClient(
|
||||
transport=transport,
|
||||
base_url="http://127.0.0.1:8000/api/v1/",
|
||||
) as async_client:
|
||||
yield async_client
|
Binary file not shown.
|
@ -1,6 +0,0 @@
|
|||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_coreapp():
|
||||
assert 1 == 1
|
|
@ -0,0 +1,3 @@
|
|||
class TestMenuDomain:
|
||||
def test_entity(self):
|
||||
assert 1 == 1
|
|
@ -0,0 +1,50 @@
|
|||
import contextlib
|
||||
import os
|
||||
from functools import lru_cache
|
||||
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.exc import ProgrammingError
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
|
||||
from fastfood_two.application.config import Config
|
||||
from fastfood_two.infrastructure.pg_storage.config import PostgresConfig
|
||||
from fastfood_two.infrastructure.pg_storage.models import Base
|
||||
|
||||
|
||||
async def db_creator(name: str) -> None:
|
||||
sql = f"create database {name} with owner test_user;"
|
||||
async with create_async_engine(
|
||||
url="postgresql+asyncpg://pi3c:test_password@localhost:5432/test_db",
|
||||
isolation_level="AUTOCOMMIT",
|
||||
).begin() as conn:
|
||||
with contextlib.suppress(ProgrammingError):
|
||||
await conn.execute(text(sql))
|
||||
|
||||
async with create_async_engine(
|
||||
url=f"postgresql+asyncpg://test_user:test_password@localhost:5432/{name}",
|
||||
).begin() as conn:
|
||||
await conn.run_sync(Base.metadata.drop_all)
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
|
||||
|
||||
async def db_deleter(name: str) -> None:
|
||||
sql = f"DROP DATABASE {name} WITH (FORCE);"
|
||||
async with create_async_engine(
|
||||
url="postgresql+asyncpg://pi3c:test_password@localhost:5432/test_db",
|
||||
isolation_level="AUTOCOMMIT",
|
||||
).begin() as conn:
|
||||
await conn.execute(text(sql))
|
||||
|
||||
|
||||
@lru_cache()
|
||||
def get_test_settings(db_name: str) -> Config:
|
||||
"""Возвращает настройки приложения"""
|
||||
return Config(
|
||||
db=PostgresConfig(
|
||||
user=os.getenv("DB_USER", "test_user"),
|
||||
password=os.getenv("DB_PASS", "test_password"),
|
||||
host=os.getenv("DB_HOST", "localhost"),
|
||||
port=int(os.getenv("DB_PORT", 5432)),
|
||||
dbname=db_name,
|
||||
),
|
||||
)
|
Loading…
Reference in New Issue