From ad92682eda74b8d97af8750d6a54f87448cd21a3 Mon Sep 17 00:00:00 2001 From: Sergey Vanyushkin Date: Tue, 23 Apr 2024 08:10:17 +0000 Subject: [PATCH] remove fucking uow and replace it by TransactionContextManager --- api/app_entrypoint/dependencies.py | 8 ++-- api/app_entrypoint/error_handlers.py | 43 ++++++++++++++----- api/application/abstractions/__init__.py | 3 -- api/application/abstractions/uow.py | 9 ---- api/application/usecase/auth/create_user.py | 20 +++------ .../usecase/company/create_company.py | 16 +++++-- .../usecase/company/get_users_company.py | 24 ++++++++--- .../usecase/user/get_user_by_email.py | 15 ++++--- api/infrastructure/dependencies/adapters.py | 21 ++++----- api/infrastructure/dependencies/usecases.py | 24 ++++++++--- api/infrastructure/persistence/uow.py | 14 ------ 11 files changed, 108 insertions(+), 89 deletions(-) delete mode 100644 api/application/abstractions/uow.py delete mode 100644 api/infrastructure/persistence/uow.py diff --git a/api/app_entrypoint/dependencies.py b/api/app_entrypoint/dependencies.py index d292a32..6f5da7d 100644 --- a/api/app_entrypoint/dependencies.py +++ b/api/app_entrypoint/dependencies.py @@ -2,7 +2,7 @@ from fastapi import FastAPI from sqlalchemy.ext.asyncio import (AsyncEngine, AsyncSession, async_sessionmaker) -from api.application.abstractions.uow import UnitOfWork +from api.application.abstractions.transaction import TransactionContextManager from api.application.protocols.date_time import DateTimeProvider from api.application.protocols.jwt import JwtTokenProcessor from api.application.protocols.password_hasher import PasswordHasher @@ -16,8 +16,8 @@ from api.domain.user.repository import UserRepository from api.infrastructure.auth.jwt_settings import JwtSettings from api.infrastructure.dependencies.adapters import (create_engine, create_session_maker, - new_session, - new_unit_of_work) + get_transaction_context, + new_session) from api.infrastructure.dependencies.configs import (app_settings, get_db_settings, get_jwt_settings) @@ -43,7 +43,7 @@ def init_dependencies(app: FastAPI) -> None: app.dependency_overrides[async_sessionmaker[AsyncSession]] = create_session_maker app.dependency_overrides[AsyncSession] = new_session - app.dependency_overrides[UnitOfWork] = new_unit_of_work + app.dependency_overrides[TransactionContextManager] = get_transaction_context app.dependency_overrides[DateTimeProvider] = get_date_time_provider app.dependency_overrides[PasswordHasher] = get_password_hasher diff --git a/api/app_entrypoint/error_handlers.py b/api/app_entrypoint/error_handlers.py index 67ea0a1..2f0e643 100644 --- a/api/app_entrypoint/error_handlers.py +++ b/api/app_entrypoint/error_handlers.py @@ -2,18 +2,27 @@ from fastapi import FastAPI, Request from fastapi.responses import JSONResponse from api.domain.error import DomainValidationError -from api.domain.user.error import ( - UserAlreadyExistsError, - UserInvalidCredentialsError, - UserIsNotAuthorizedError, -) +from api.domain.user.error import (UserAlreadyExistsError, + UserInvalidCredentialsError, + UserIsNotAuthorizedError) +from api.infrastructure.persistence.error import TransactionContextManagerError -async def validation_error_exc_handler(request: Request, exc: DomainValidationError) -> JSONResponse: +async def transaction_error_exec_handler( + request: Request, exc: TransactionContextManagerError +) -> JSONResponse: return JSONResponse(status_code=400, content={"detail": exc.message}) -async def user_authentication_error_exc_handler(request: Request, exc: UserIsNotAuthorizedError) -> JSONResponse: +async def validation_error_exc_handler( + request: Request, exc: DomainValidationError +) -> JSONResponse: + return JSONResponse(status_code=400, content={"detail": exc.message}) + + +async def user_authentication_error_exc_handler( + request: Request, exc: UserIsNotAuthorizedError +) -> JSONResponse: return JSONResponse( status_code=401, content={"detail": exc.message}, @@ -21,7 +30,9 @@ async def user_authentication_error_exc_handler(request: Request, exc: UserIsNot ) -async def user_already_exist_error_exc_handler(request: Request, exc: UserAlreadyExistsError) -> JSONResponse: +async def user_already_exist_error_exc_handler( + request: Request, exc: UserAlreadyExistsError +) -> JSONResponse: return JSONResponse(status_code=409, content={"detail": exc.message}) @@ -36,6 +47,16 @@ def init_exc_handlers(app: FastAPI) -> None: DomainValidationError, validation_error_exc_handler, ) - app.add_exception_handler(UserIsNotAuthorizedError, user_authentication_error_exc_handler) - app.add_exception_handler(UserAlreadyExistsError, user_already_exist_error_exc_handler) - app.add_exception_handler(UserInvalidCredentialsError, user_invalid_credentials_error_exc_handler) + app.add_exception_handler( + UserIsNotAuthorizedError, user_authentication_error_exc_handler + ) + app.add_exception_handler( + UserAlreadyExistsError, user_already_exist_error_exc_handler + ) + app.add_exception_handler( + UserInvalidCredentialsError, user_invalid_credentials_error_exc_handler + ) + + app.add_exception_handler( + TransactionContextManagerError, transaction_error_exec_handler + ) diff --git a/api/application/abstractions/__init__.py b/api/application/abstractions/__init__.py index 6325ed2..e69de29 100644 --- a/api/application/abstractions/__init__.py +++ b/api/application/abstractions/__init__.py @@ -1,3 +0,0 @@ -from .uow import UnitOfWork - -__all__ = ("UnitOfWork",) diff --git a/api/application/abstractions/uow.py b/api/application/abstractions/uow.py deleted file mode 100644 index 3d538c5..0000000 --- a/api/application/abstractions/uow.py +++ /dev/null @@ -1,9 +0,0 @@ -from typing import Protocol - - -class UnitOfWork(Protocol): - async def commit(self) -> None: - raise NotImplementedError - - async def rollback(self) -> None: - raise NotImplementedError diff --git a/api/application/usecase/auth/create_user.py b/api/application/usecase/auth/create_user.py index c150eff..3104b92 100644 --- a/api/application/usecase/auth/create_user.py +++ b/api/application/usecase/auth/create_user.py @@ -1,9 +1,6 @@ -from sqlalchemy.exc import IntegrityError - -from api.application.abstractions import UnitOfWork +from api.application.abstractions.transaction import TransactionContextManager from api.application.contracts.auth.auth_request import UserCreateRequest from api.application.protocols.password_hasher import PasswordHasher -from api.domain.user.error import UserAlreadyExistsError from api.domain.user.model import User from api.domain.user.repository import UserRepository @@ -11,11 +8,11 @@ from api.domain.user.repository import UserRepository class CreateUser: def __init__( self, - uow: UnitOfWork, + transaction: TransactionContextManager, user_repository: UserRepository, password_hasher: PasswordHasher, ) -> None: - self.uow = uow + self.transaction = transaction self.user_repository = user_repository self.hasher = password_hasher @@ -27,13 +24,6 @@ class CreateUser: hashed_password=self.hasher.hash_password(request.password), ) - try: + async with self.transaction as tr: await self.user_repository.create_user(user=user) - await self.uow.commit() - - except IntegrityError as e: - msg = e.args[0].split("\n")[-1] - msg = msg.split(":")[1].strip() - - if "email" in msg: - raise UserAlreadyExistsError(message=msg) + await tr.commit() diff --git a/api/application/usecase/company/create_company.py b/api/application/usecase/company/create_company.py index 189fc06..d02491f 100644 --- a/api/application/usecase/company/create_company.py +++ b/api/application/usecase/company/create_company.py @@ -1,12 +1,20 @@ +from api.application.abstractions.transaction import TransactionContextManager from api.application.contracts.company.company_request import CreateNewCompany -from api.application.contracts.company.company_response import CompanyBaseResponse +from api.application.contracts.company.company_response import \ + CompanyBaseResponse from api.domain.company.repository import CompanyRepository class CreateCompany: - def __init__(self, company_repository: CompanyRepository) -> None: + def __init__( + self, + transaction: TransactionContextManager, + company_repository: CompanyRepository, + ) -> None: self.company_repository = company_repository + self.transaction = transaction async def execute(self, request: CreateNewCompany) -> CompanyBaseResponse: - # companies = await self.company_repository. - return CompanyBaseResponse(name=request.name, email=request.email) + async with self.transaction as tr: + # companies = await self.company_repository. + return CompanyBaseResponse(name=request.name, email=request.email) diff --git a/api/application/usecase/company/get_users_company.py b/api/application/usecase/company/get_users_company.py index 30f9b8a..9c215a5 100644 --- a/api/application/usecase/company/get_users_company.py +++ b/api/application/usecase/company/get_users_company.py @@ -1,12 +1,26 @@ -from api.application.contracts.company.company_request import CompanyByOwnerEmail -from api.application.contracts.company.company_response import CompanyBaseResponse +from api.application.abstractions.transaction import TransactionContextManager +from api.application.contracts.company.company_request import \ + CompanyByOwnerEmail +from api.application.contracts.company.company_response import \ + CompanyBaseResponse from api.domain.company.repository import CompanyRepository class GetCompaniesByOwnerEmail: - def __init__(self, company_repository: CompanyRepository) -> None: + def __init__( + self, + transaction: TransactionContextManager, + company_repository: CompanyRepository, + ) -> None: self.company_repository = company_repository + self.transaction = transaction async def execute(self, request: CompanyByOwnerEmail) -> list[CompanyBaseResponse]: - companies = await self.company_repository.get_companies_by_owner_email(filter={"email": request.email}) - return [CompanyBaseResponse(name=comp.name.value, email=comp.email.value) for comp in companies] + async with self.transaction: + companies = await self.company_repository.get_companies_by_owner_email( + filter={"email": request.email} + ) + return [ + CompanyBaseResponse(name=comp.name.value, email=comp.email.value) + for comp in companies + ] diff --git a/api/application/usecase/user/get_user_by_email.py b/api/application/usecase/user/get_user_by_email.py index fad741d..8933529 100644 --- a/api/application/usecase/user/get_user_by_email.py +++ b/api/application/usecase/user/get_user_by_email.py @@ -1,15 +1,18 @@ -from api.application.abstractions import UnitOfWork +from api.application.abstractions.transaction import TransactionContextManager from api.application.contracts.user import GetUserByEmailRequest, UserResponse from api.domain.user.repository import UserRepository class GetUserByEmail: - def __init__(self, uow: UnitOfWork, user_repository: UserRepository) -> None: - self.uow = uow + def __init__( + self, transaction: TransactionContextManager, user_repository: UserRepository + ) -> None: + self.transaction = transaction self.user_repository = user_repository async def execute(self, request: GetUserByEmailRequest) -> UserResponse | None: - user = await self.user_repository.get_user(filter={"email": request.email}) - if user: + async with self.transaction: + user = await self.user_repository.get_user(filter={"email": request.email}) + if user: + return None return None - return None diff --git a/api/infrastructure/dependencies/adapters.py b/api/infrastructure/dependencies/adapters.py index ccc045e..6d2211d 100644 --- a/api/infrastructure/dependencies/adapters.py +++ b/api/infrastructure/dependencies/adapters.py @@ -2,23 +2,20 @@ from collections.abc import AsyncIterable from typing import Annotated from fastapi import Depends -from sqlalchemy.ext.asyncio import ( - AsyncEngine, - AsyncSession, - async_sessionmaker, - create_async_engine, -) +from sqlalchemy.ext.asyncio import (AsyncEngine, AsyncSession, + async_sessionmaker, create_async_engine) -from api.application.abstractions import UnitOfWork +from api.application.abstractions.transaction import TransactionContextManager from api.infrastructure.dependencies.stub import Stub -from api.infrastructure.persistence.uow import SqlAlchemyUnitOfWork +from api.infrastructure.persistence.transaction import \ + SqlalchemyTransactionContextManager from api.infrastructure.settings import Settings -def new_unit_of_work( - session: Annotated[AsyncSession, Depends(Stub(AsyncSession))], -) -> UnitOfWork: - return SqlAlchemyUnitOfWork(session) +def get_transaction_context( + session: Annotated[AsyncSession, Depends(Stub(AsyncSession))] +) -> TransactionContextManager: + return SqlalchemyTransactionContextManager(session) def create_engine( diff --git a/api/infrastructure/dependencies/usecases.py b/api/infrastructure/dependencies/usecases.py index 4ec45b1..735076a 100644 --- a/api/infrastructure/dependencies/usecases.py +++ b/api/infrastructure/dependencies/usecases.py @@ -2,7 +2,7 @@ from typing import Annotated from fastapi import Depends -from api.application.abstractions.uow import UnitOfWork +from api.application.abstractions.transaction import TransactionContextManager from api.application.protocols.password_hasher import PasswordHasher from api.application.usecase.auth.create_user import CreateUser from api.application.usecase.company.create_company import CreateCompany @@ -15,21 +15,33 @@ from api.infrastructure.dependencies.stub import Stub def provide_create_user( user_repository: Annotated[UserRepository, Depends(Stub(UserRepository))], - uow: Annotated[UnitOfWork, Depends(Stub(UnitOfWork))], + transaction: Annotated[ + TransactionContextManager, Depends(Stub(TransactionContextManager)) + ], password_hasher: Annotated[PasswordHasher, Depends(Stub(PasswordHasher))], ) -> CreateUser: return CreateUser( - uow=uow, user_repository=user_repository, password_hasher=password_hasher + transaction=transaction, + user_repository=user_repository, + password_hasher=password_hasher, ) def provide_get_companies_by_email( company_repository: Annotated[CompanyRepository, Depends(Stub(CompanyRepository))], + transaction: Annotated[ + TransactionContextManager, Depends(Stub(TransactionContextManager)) + ], ) -> GetCompaniesByOwnerEmail: - return GetCompaniesByOwnerEmail(company_repository=company_repository) + return GetCompaniesByOwnerEmail( + transaction=transaction, company_repository=company_repository + ) def provide_create_company( - company_repository: Annotated[CompanyRepository, Depends(Stub(CompanyRepository))] + company_repository: Annotated[CompanyRepository, Depends(Stub(CompanyRepository))], + transaction: Annotated[ + TransactionContextManager, Depends(Stub(TransactionContextManager)) + ], ) -> CreateCompany: - return CreateCompany(company_repository=company_repository) + return CreateCompany(transaction=transaction, company_repository=company_repository) diff --git a/api/infrastructure/persistence/uow.py b/api/infrastructure/persistence/uow.py deleted file mode 100644 index 22790bf..0000000 --- a/api/infrastructure/persistence/uow.py +++ /dev/null @@ -1,14 +0,0 @@ -from sqlalchemy.ext.asyncio import AsyncSession - -from api.application.abstractions import UnitOfWork - - -class SqlAlchemyUnitOfWork(UnitOfWork): - def __init__(self, session: AsyncSession) -> None: - self.session = session - - async def commit(self) -> None: - await self.session.commit() - - async def rollback(self) -> None: - await self.session.rollback()