remove fucking uow and replace it by TransactionContextManager

main
Сергей Ванюшкин 2024-04-23 08:10:17 +00:00
parent 12d61e01e1
commit ad92682eda
11 changed files with 108 additions and 89 deletions

View File

@ -2,7 +2,7 @@ from fastapi import FastAPI
from sqlalchemy.ext.asyncio import (AsyncEngine, AsyncSession, from sqlalchemy.ext.asyncio import (AsyncEngine, AsyncSession,
async_sessionmaker) 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.date_time import DateTimeProvider
from api.application.protocols.jwt import JwtTokenProcessor from api.application.protocols.jwt import JwtTokenProcessor
from api.application.protocols.password_hasher import PasswordHasher 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.auth.jwt_settings import JwtSettings
from api.infrastructure.dependencies.adapters import (create_engine, from api.infrastructure.dependencies.adapters import (create_engine,
create_session_maker, create_session_maker,
new_session, get_transaction_context,
new_unit_of_work) new_session)
from api.infrastructure.dependencies.configs import (app_settings, from api.infrastructure.dependencies.configs import (app_settings,
get_db_settings, get_db_settings,
get_jwt_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[async_sessionmaker[AsyncSession]] = create_session_maker
app.dependency_overrides[AsyncSession] = new_session 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[DateTimeProvider] = get_date_time_provider
app.dependency_overrides[PasswordHasher] = get_password_hasher app.dependency_overrides[PasswordHasher] = get_password_hasher

View File

@ -2,18 +2,27 @@ from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from api.domain.error import DomainValidationError from api.domain.error import DomainValidationError
from api.domain.user.error import ( from api.domain.user.error import (UserAlreadyExistsError,
UserAlreadyExistsError, UserInvalidCredentialsError,
UserInvalidCredentialsError, UserIsNotAuthorizedError)
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}) 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( return JSONResponse(
status_code=401, status_code=401,
content={"detail": exc.message}, 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}) return JSONResponse(status_code=409, content={"detail": exc.message})
@ -36,6 +47,16 @@ def init_exc_handlers(app: FastAPI) -> None:
DomainValidationError, DomainValidationError,
validation_error_exc_handler, validation_error_exc_handler,
) )
app.add_exception_handler(UserIsNotAuthorizedError, user_authentication_error_exc_handler) app.add_exception_handler(
app.add_exception_handler(UserAlreadyExistsError, user_already_exist_error_exc_handler) UserIsNotAuthorizedError, user_authentication_error_exc_handler
app.add_exception_handler(UserInvalidCredentialsError, user_invalid_credentials_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
)

View File

@ -1,3 +0,0 @@
from .uow import UnitOfWork
__all__ = ("UnitOfWork",)

View File

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

View File

@ -1,9 +1,6 @@
from sqlalchemy.exc import IntegrityError from api.application.abstractions.transaction import TransactionContextManager
from api.application.abstractions import UnitOfWork
from api.application.contracts.auth.auth_request import UserCreateRequest from api.application.contracts.auth.auth_request import UserCreateRequest
from api.application.protocols.password_hasher import PasswordHasher 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.model import User
from api.domain.user.repository import UserRepository from api.domain.user.repository import UserRepository
@ -11,11 +8,11 @@ from api.domain.user.repository import UserRepository
class CreateUser: class CreateUser:
def __init__( def __init__(
self, self,
uow: UnitOfWork, transaction: TransactionContextManager,
user_repository: UserRepository, user_repository: UserRepository,
password_hasher: PasswordHasher, password_hasher: PasswordHasher,
) -> None: ) -> None:
self.uow = uow self.transaction = transaction
self.user_repository = user_repository self.user_repository = user_repository
self.hasher = password_hasher self.hasher = password_hasher
@ -27,13 +24,6 @@ class CreateUser:
hashed_password=self.hasher.hash_password(request.password), 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.user_repository.create_user(user=user)
await self.uow.commit() await tr.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)

View File

@ -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_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 from api.domain.company.repository import CompanyRepository
class CreateCompany: class CreateCompany:
def __init__(self, company_repository: CompanyRepository) -> None: def __init__(
self,
transaction: TransactionContextManager,
company_repository: CompanyRepository,
) -> None:
self.company_repository = company_repository self.company_repository = company_repository
self.transaction = transaction
async def execute(self, request: CreateNewCompany) -> CompanyBaseResponse: async def execute(self, request: CreateNewCompany) -> CompanyBaseResponse:
# companies = await self.company_repository. async with self.transaction as tr:
return CompanyBaseResponse(name=request.name, email=request.email) # companies = await self.company_repository.
return CompanyBaseResponse(name=request.name, email=request.email)

View File

@ -1,12 +1,26 @@
from api.application.contracts.company.company_request import CompanyByOwnerEmail from api.application.abstractions.transaction import TransactionContextManager
from api.application.contracts.company.company_response import CompanyBaseResponse 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 from api.domain.company.repository import CompanyRepository
class GetCompaniesByOwnerEmail: class GetCompaniesByOwnerEmail:
def __init__(self, company_repository: CompanyRepository) -> None: def __init__(
self,
transaction: TransactionContextManager,
company_repository: CompanyRepository,
) -> None:
self.company_repository = company_repository self.company_repository = company_repository
self.transaction = transaction
async def execute(self, request: CompanyByOwnerEmail) -> list[CompanyBaseResponse]: async def execute(self, request: CompanyByOwnerEmail) -> list[CompanyBaseResponse]:
companies = await self.company_repository.get_companies_by_owner_email(filter={"email": request.email}) async with self.transaction:
return [CompanyBaseResponse(name=comp.name.value, email=comp.email.value) for comp in companies] 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
]

View File

@ -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.application.contracts.user import GetUserByEmailRequest, UserResponse
from api.domain.user.repository import UserRepository from api.domain.user.repository import UserRepository
class GetUserByEmail: class GetUserByEmail:
def __init__(self, uow: UnitOfWork, user_repository: UserRepository) -> None: def __init__(
self.uow = uow self, transaction: TransactionContextManager, user_repository: UserRepository
) -> None:
self.transaction = transaction
self.user_repository = user_repository self.user_repository = user_repository
async def execute(self, request: GetUserByEmailRequest) -> UserResponse | None: async def execute(self, request: GetUserByEmailRequest) -> UserResponse | None:
user = await self.user_repository.get_user(filter={"email": request.email}) async with self.transaction:
if user: user = await self.user_repository.get_user(filter={"email": request.email})
if user:
return None
return None return None
return None

View File

@ -2,23 +2,20 @@ from collections.abc import AsyncIterable
from typing import Annotated from typing import Annotated
from fastapi import Depends from fastapi import Depends
from sqlalchemy.ext.asyncio import ( from sqlalchemy.ext.asyncio import (AsyncEngine, AsyncSession,
AsyncEngine, async_sessionmaker, create_async_engine)
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.dependencies.stub import Stub
from api.infrastructure.persistence.uow import SqlAlchemyUnitOfWork from api.infrastructure.persistence.transaction import \
SqlalchemyTransactionContextManager
from api.infrastructure.settings import Settings from api.infrastructure.settings import Settings
def new_unit_of_work( def get_transaction_context(
session: Annotated[AsyncSession, Depends(Stub(AsyncSession))], session: Annotated[AsyncSession, Depends(Stub(AsyncSession))]
) -> UnitOfWork: ) -> TransactionContextManager:
return SqlAlchemyUnitOfWork(session) return SqlalchemyTransactionContextManager(session)
def create_engine( def create_engine(

View File

@ -2,7 +2,7 @@ from typing import Annotated
from fastapi import Depends 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.protocols.password_hasher import PasswordHasher
from api.application.usecase.auth.create_user import CreateUser from api.application.usecase.auth.create_user import CreateUser
from api.application.usecase.company.create_company import CreateCompany from api.application.usecase.company.create_company import CreateCompany
@ -15,21 +15,33 @@ from api.infrastructure.dependencies.stub import Stub
def provide_create_user( def provide_create_user(
user_repository: Annotated[UserRepository, Depends(Stub(UserRepository))], 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))], password_hasher: Annotated[PasswordHasher, Depends(Stub(PasswordHasher))],
) -> CreateUser: ) -> CreateUser:
return 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( def provide_get_companies_by_email(
company_repository: Annotated[CompanyRepository, Depends(Stub(CompanyRepository))], company_repository: Annotated[CompanyRepository, Depends(Stub(CompanyRepository))],
transaction: Annotated[
TransactionContextManager, Depends(Stub(TransactionContextManager))
],
) -> GetCompaniesByOwnerEmail: ) -> GetCompaniesByOwnerEmail:
return GetCompaniesByOwnerEmail(company_repository=company_repository) return GetCompaniesByOwnerEmail(
transaction=transaction, company_repository=company_repository
)
def provide_create_company( 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: ) -> CreateCompany:
return CreateCompany(company_repository=company_repository) return CreateCompany(transaction=transaction, company_repository=company_repository)

View File

@ -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()