diff --git a/api/app_builder/dependencies.py b/api/app_builder/dependencies.py index 73050b5..ee3d216 100644 --- a/api/app_builder/dependencies.py +++ b/api/app_builder/dependencies.py @@ -10,11 +10,14 @@ from api.infrastructure.dependencies.adapters import ( new_session, new_unit_of_work, ) +from api.infrastructure.dependencies.configs import app_settings from api.infrastructure.dependencies.repositories import get_user_repository from api.infrastructure.dependencies.usecases import provide_create_user +from api.infrastructure.settings import Settings def init_dependencies(app: FastAPI) -> None: + app.dependency_overrides[Settings] = app_settings app.dependency_overrides[AsyncEngine] = create_engine app.dependency_overrides[async_sessionmaker[AsyncSession]] = create_session_maker app.dependency_overrides[AsyncSession] = new_session diff --git a/api/app_builder/main.py b/api/app_builder/main.py index 444fb99..a7b4204 100644 --- a/api/app_builder/main.py +++ b/api/app_builder/main.py @@ -1,3 +1,6 @@ +from collections.abc import AsyncGenerator +from contextlib import asynccontextmanager + from fastapi import FastAPI from api.app_builder.dependencies import init_dependencies @@ -5,8 +8,16 @@ from api.app_builder.dependencies import init_dependencies from .routers import init_routers +@asynccontextmanager +async def lifespan(app: FastAPI) -> AsyncGenerator: + print("init lifespan") + yield + + def app_factory() -> FastAPI: - app = FastAPI() + app = FastAPI( + lifespan=lifespan, + ) init_dependencies(app) init_routers(app) diff --git a/api/infrastructure/dependencies/adapters.py b/api/infrastructure/dependencies/adapters.py index f28bd29..38bff9b 100644 --- a/api/infrastructure/dependencies/adapters.py +++ b/api/infrastructure/dependencies/adapters.py @@ -12,6 +12,7 @@ from sqlalchemy.ext.asyncio import ( from api.application.abstractions import UnitOfWork from api.infrastructure.dependencies.stub import Stub from api.infrastructure.persistence.uow import SqlAlchemyUnitOfWork +from api.infrastructure.settings import Settings def new_unit_of_work( @@ -20,15 +21,16 @@ def new_unit_of_work( return SqlAlchemyUnitOfWork(session) -def create_engine() -> AsyncEngine: - return create_async_engine("postgresql+asyncpg://postgresql+asyncpg//demo_user:user_pass@db:5432/serviceman_db") +def create_engine( + settings: Annotated[Settings, Depends(Stub(Settings))], +) -> AsyncEngine: + return create_async_engine(settings.db.db_url) def create_session_maker( engine: Annotated[AsyncEngine, Depends(Stub(AsyncEngine))], ) -> async_sessionmaker[AsyncSession]: maker = async_sessionmaker(engine, expire_on_commit=False) - print("session_maker id:", id(maker)) return maker diff --git a/api/infrastructure/dependencies/configs.py b/api/infrastructure/dependencies/configs.py new file mode 100644 index 0000000..9979b06 --- /dev/null +++ b/api/infrastructure/dependencies/configs.py @@ -0,0 +1,30 @@ +import os +from functools import lru_cache + +import yaml # type: ignore + +from api.infrastructure.persistence.db_setings import DBSettings +from api.infrastructure.settings import Settings + + +def yaml_loader(file: str) -> dict[str, dict[str, str]]: + with open(file) as f: + yaml_data: dict = yaml.safe_load(f) + return yaml_data + + +@lru_cache +def app_settings() -> Settings: + config_data = yaml_loader( + file=os.getenv("CONFIG_PATH", "./config/api_config.yml"), + ) + + return Settings( + db=DBSettings( + pg_user=config_data["db"]["user"], + pg_pass=config_data["db"]["password"], + pg_host=config_data["db"]["host"], + pg_port=int(config_data["db"]["port"]), + pg_db=config_data["db"]["database"], + ), + ) diff --git a/api/infrastructure/dependencies/usecases.py b/api/infrastructure/dependencies/usecases.py index df9e50d..2343656 100644 --- a/api/infrastructure/dependencies/usecases.py +++ b/api/infrastructure/dependencies/usecases.py @@ -5,10 +5,11 @@ from fastapi import Depends from api.application.abstractions.uow import UnitOfWork from api.application.usecase.create_user import CreateUser from api.domain.user.repository import UserRepository +from api.infrastructure.dependencies.stub import Stub def provide_create_user( - user_repository: Annotated[UserRepository, Depends()], + user_repository: Annotated[UserRepository, Depends(Stub(UserRepository))], uow: Annotated[UnitOfWork, Depends()], ) -> CreateUser: return CreateUser(uow=uow, user_repository=user_repository) diff --git a/api/infrastructure/persistence/db_setings.py b/api/infrastructure/persistence/db_setings.py new file mode 100644 index 0000000..74f9f7c --- /dev/null +++ b/api/infrastructure/persistence/db_setings.py @@ -0,0 +1,20 @@ +from dataclasses import dataclass + + +@dataclass(frozen=True) +class DBSettings: + pg_user: str + pg_pass: str + pg_host: str + pg_port: int + pg_db: str + + @property + def db_url(self) -> str: + return "postgresql+asyncpg://{}:{}@{}:{}/{}".format( + self.pg_user, + self.pg_pass, + self.pg_host, + self.pg_port, + self.pg_db, + ) diff --git a/api/infrastructure/persistence/models/base.py b/api/infrastructure/persistence/models/base.py new file mode 100644 index 0000000..21145b0 --- /dev/null +++ b/api/infrastructure/persistence/models/base.py @@ -0,0 +1,5 @@ +from sqlalchemy.orm import DeclarativeBase + + +class Base(DeclarativeBase): + ... diff --git a/api/infrastructure/persistence/models/user.py b/api/infrastructure/persistence/models/user.py new file mode 100644 index 0000000..7ef93b7 --- /dev/null +++ b/api/infrastructure/persistence/models/user.py @@ -0,0 +1,18 @@ +import uuid + +from sqlalchemy import UUID +from sqlalchemy.orm import Mapped, mapped_column + +from api.infrastructure.persistence.models.base import Base + + +class UserModel(Base): + __tablename__ = "user" + + id: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), + primary_key=True, + ) + name: Mapped[str] + email: Mapped[str] = mapped_column(unique=True) + hashed_password: Mapped[str] diff --git a/api/infrastructure/persistence/repositories/user_repository.py b/api/infrastructure/persistence/repositories/user_repository.py index ea16a63..1d359bc 100644 --- a/api/infrastructure/persistence/repositories/user_repository.py +++ b/api/infrastructure/persistence/repositories/user_repository.py @@ -1,6 +1,8 @@ +from sqlalchemy import insert from sqlalchemy.ext.asyncio import AsyncSession from api.domain.user import User, UserRepository +from api.infrastructure.persistence.models.user import UserModel class SqlAlchemyUserRepository(UserRepository): @@ -8,7 +10,13 @@ class SqlAlchemyUserRepository(UserRepository): self.session = session async def create_user(self, user: User) -> None: - pass + stmt = insert(UserModel).values( + id=user.id, + name=user.name, + email=user.email, + hashed_password=user.password, + ) + await self.session.execute(stmt) async def get_user(self, filter: dict) -> User | None: pass diff --git a/api/infrastructure/settings.py b/api/infrastructure/settings.py new file mode 100644 index 0000000..a5cedbc --- /dev/null +++ b/api/infrastructure/settings.py @@ -0,0 +1,8 @@ +from dataclasses import dataclass + +from api.infrastructure.persistence.db_setings import DBSettings + + +@dataclass() +class Settings: + db: DBSettings diff --git a/api/presentation/routers/user.py b/api/presentation/routers/user.py index c1eade3..83cbc68 100644 --- a/api/presentation/routers/user.py +++ b/api/presentation/routers/user.py @@ -14,8 +14,8 @@ async def get_all_users() -> list[UserResponse]: return [] -@user_router.post("/") -async def create_task( +@user_router.post("/", status_code=201) +async def create_user( request: UserCreateRequest, usecase: Annotated[CreateUser, Depends(Stub(CreateUser))], ) -> None: diff --git a/config/api_config.yml b/config/api_config.yml index eb29195..ed7f7bb 100644 --- a/config/api_config.yml +++ b/config/api_config.yml @@ -1,5 +1,5 @@ db: - host: "localhost" + host: "db" port: 5432 database: "serviceman_db" user: "demo_user"