refactor: crud mixins for repositories and services

This commit is contained in:
2025-09-08 18:00:34 +04:00
parent d73748deab
commit be8052848c
9 changed files with 48 additions and 51 deletions

View File

@ -1,19 +1,11 @@
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
from models import Board from models import Board
from repositories.base import BaseRepository
from repositories.mixins import * from repositories.mixins import *
from schemas.board import UpdateBoardSchema, CreateBoardSchema from schemas.board import UpdateBoardSchema, CreateBoardSchema
class BoardRepository( class BoardRepository(RepCrudMixin[Board, CreateBoardSchema, UpdateBoardSchema]):
BaseRepository,
RepGetAllMixin[Board],
RepDeleteMixin[Board],
RepCreateMixin[Board, CreateBoardSchema],
RepUpdateMixin[Board, UpdateBoardSchema],
RepGetByIdMixin[Board],
):
entity_class = Board entity_class = Board
def _process_get_all_stmt_with_args(self, stmt: Select, *args) -> Select: def _process_get_all_stmt_with_args(self, stmt: Select, *args) -> Select:

View File

@ -1,7 +1,6 @@
from sqlalchemy.orm import joinedload from sqlalchemy.orm import joinedload
from models import Deal, CardStatusHistory, Board from models import Deal, CardStatusHistory, Board
from repositories.base import BaseRepository
from repositories.mixins import * from repositories.mixins import *
from schemas.base import SortDir from schemas.base import SortDir
from schemas.deal import UpdateDealSchema, CreateDealSchema from schemas.deal import UpdateDealSchema, CreateDealSchema

View File

@ -1,14 +1,21 @@
from typing import Type, Optional from typing import Type, Optional, TypeVar, Generic
from sqlalchemy import select, Select from sqlalchemy import select, Select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from repositories.base import BaseRepository
from schemas.base import BaseSchema
class RepBaseMixin[EntityType]: EntityType = TypeVar("EntityType")
CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseSchema)
UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseSchema)
class RepBaseMixin(Generic[EntityType]):
session: AsyncSession session: AsyncSession
class RepDeleteMixin[EntityType](RepBaseMixin[EntityType]): class RepDeleteMixin(Generic[EntityType], RepBaseMixin[EntityType]):
async def delete(self, obj: EntityType, is_soft: bool) -> None: async def delete(self, obj: EntityType, is_soft: bool) -> None:
if not is_soft: if not is_soft:
await self.session.delete(obj) await self.session.delete(obj)
@ -24,10 +31,10 @@ class RepDeleteMixin[EntityType](RepBaseMixin[EntityType]):
await self.session.commit() await self.session.commit()
class RepCreateMixin[EntityType, CreateType](RepBaseMixin[EntityType]): class RepCreateMixin(Generic[EntityType, CreateSchemaType], RepBaseMixin[EntityType]):
entity_class: Type[EntityType] entity_class: Type[EntityType]
async def create(self, data: CreateType) -> int: async def create(self, data: CreateSchemaType) -> int:
obj = self.entity_class(**data.model_dump()) obj = self.entity_class(**data.model_dump())
self.session.add(obj) self.session.add(obj)
await self.session.commit() await self.session.commit()
@ -35,11 +42,11 @@ class RepCreateMixin[EntityType, CreateType](RepBaseMixin[EntityType]):
return obj.id return obj.id
class RepUpdateMixin[EntityType, UpdateType](RepBaseMixin[EntityType]): class RepUpdateMixin(Generic[EntityType, UpdateSchemaType], RepBaseMixin[EntityType]):
async def _apply_update_data_to_model( async def _apply_update_data_to_model(
self, self,
model: EntityType, model: EntityType,
data: UpdateType, data: UpdateSchemaType,
with_commit: Optional[bool] = False, with_commit: Optional[bool] = False,
fields: Optional[list[str]] = None, fields: Optional[list[str]] = None,
) -> EntityType: ) -> EntityType:
@ -57,11 +64,11 @@ class RepUpdateMixin[EntityType, UpdateType](RepBaseMixin[EntityType]):
await self.session.refresh(model) await self.session.refresh(model)
return model return model
async def update(self, entity: EntityType, data: UpdateType) -> EntityType: async def update(self, entity: EntityType, data: UpdateSchemaType) -> EntityType:
pass pass
class RepGetByIdMixin[EntityType](RepBaseMixin[EntityType]): class RepGetByIdMixin(Generic[EntityType], RepBaseMixin[EntityType]):
entity_class: Type[EntityType] entity_class: Type[EntityType]
def _process_get_by_id_stmt(self, stmt: Select) -> Select: def _process_get_by_id_stmt(self, stmt: Select) -> Select:
@ -77,7 +84,7 @@ class RepGetByIdMixin[EntityType](RepBaseMixin[EntityType]):
return result.scalar_one_or_none() return result.scalar_one_or_none()
class RepGetAllMixin[EntityType](RepBaseMixin[EntityType]): class RepGetAllMixin(Generic[EntityType], RepBaseMixin[EntityType]):
entity_class: Type[EntityType] entity_class: Type[EntityType]
def _process_get_all_stmt_with_args(self, stmt: Select, *args) -> Select: def _process_get_all_stmt_with_args(self, stmt: Select, *args) -> Select:
@ -97,3 +104,15 @@ class RepGetAllMixin[EntityType](RepBaseMixin[EntityType]):
stmt = self._process_get_all_stmt(stmt) stmt = self._process_get_all_stmt(stmt)
result = await self.session.execute(stmt) result = await self.session.execute(stmt)
return list(result.scalars().all()) return list(result.scalars().all())
class RepCrudMixin(
Generic[EntityType, CreateSchemaType, UpdateSchemaType],
BaseRepository,
RepGetAllMixin[EntityType],
RepCreateMixin[EntityType, CreateSchemaType],
RepUpdateMixin[EntityType, UpdateSchemaType],
RepGetByIdMixin[EntityType],
RepDeleteMixin[EntityType],
):
pass

View File

@ -1,18 +1,12 @@
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
from models.project import Project from models.project import Project
from repositories.base import BaseRepository
from repositories.mixins import * from repositories.mixins import *
from schemas.project import CreateProjectSchema, UpdateProjectSchema from schemas.project import CreateProjectSchema, UpdateProjectSchema
class ProjectRepository( class ProjectRepository(
BaseRepository, RepCrudMixin[Project, CreateProjectSchema, UpdateProjectSchema]
RepGetAllMixin[Project],
RepDeleteMixin[Project],
RepCreateMixin[Project, CreateProjectSchema],
RepUpdateMixin[Project, UpdateProjectSchema],
RepGetByIdMixin[Project],
): ):
entity_class = Project entity_class = Project

View File

@ -1,19 +1,11 @@
from sqlalchemy import func from sqlalchemy import func
from models import Status, Deal from models import Status, Deal
from repositories.base import BaseRepository
from repositories.mixins import * from repositories.mixins import *
from schemas.status import UpdateStatusSchema, CreateStatusSchema from schemas.status import UpdateStatusSchema, CreateStatusSchema
class StatusRepository( class StatusRepository(RepCrudMixin[Status, CreateStatusSchema, UpdateStatusSchema]):
BaseRepository,
RepGetAllMixin[Status],
RepDeleteMixin[Status],
RepCreateMixin[Status, CreateStatusSchema],
RepUpdateMixin[Status, UpdateStatusSchema],
RepGetByIdMixin[Status],
):
entity_class = Status entity_class = Status
def _process_get_all_stmt_with_args(self, stmt: Select, *args) -> Select: def _process_get_all_stmt_with_args(self, stmt: Select, *args) -> Select:

View File

@ -5,10 +5,7 @@ from services.mixins import *
class BoardService( class BoardService(
ServiceGetAllMixin[Board, BoardSchema], ServiceCrudMixin[Board, BoardSchema, CreateBoardSchema, UpdateBoardSchema]
ServiceCreateMixin[Board, CreateBoardRequest, BoardSchema],
ServiceUpdateMixin[Board, UpdateBoardRequest],
ServiceDeleteMixin[Board],
): ):
schema_class = BoardSchema schema_class = BoardSchema
entity_not_found_msg = "Доска не найдена" entity_not_found_msg = "Доска не найдена"

View File

@ -34,8 +34,8 @@ class ServiceCreateMixin(
class ServiceGetAllMixin( class ServiceGetAllMixin(
ServiceBaseMixin[RepGetAllMixin],
Generic[EntityType, SchemaType], Generic[EntityType, SchemaType],
ServiceBaseMixin[RepGetAllMixin],
): ):
schema_class: type[SchemaType] schema_class: type[SchemaType]
@ -47,8 +47,8 @@ class ServiceGetAllMixin(
class ServiceUpdateMixin( class ServiceUpdateMixin(
ServiceBaseMixin[RepUpdateMixin | RepGetByIdMixin],
Generic[EntityType, UpdateRequestType], Generic[EntityType, UpdateRequestType],
ServiceBaseMixin[RepUpdateMixin | RepGetByIdMixin],
): ):
entity_not_found_msg = "Entity not found" entity_not_found_msg = "Entity not found"
entity_updated_msg = "Entity updated" entity_updated_msg = "Entity updated"
@ -64,8 +64,8 @@ class ServiceUpdateMixin(
class ServiceDeleteMixin( class ServiceDeleteMixin(
ServiceBaseMixin[RepDeleteMixin | RepGetByIdMixin],
Generic[EntityType], Generic[EntityType],
ServiceBaseMixin[RepDeleteMixin | RepGetByIdMixin],
): ):
entity_not_found_msg = "Entity not found" entity_not_found_msg = "Entity not found"
entity_deleted_msg = "Entity deleted" entity_deleted_msg = "Entity deleted"
@ -82,3 +82,13 @@ class ServiceDeleteMixin(
await self.repository.delete(entity, is_soft) await self.repository.delete(entity, is_soft)
return BaseDeleteResponse(message=self.entity_deleted_msg) return BaseDeleteResponse(message=self.entity_deleted_msg)
class ServiceCrudMixin(
Generic[EntityType, SchemaType, CreateRequestType, UpdateRequestType],
ServiceGetAllMixin[EntityType, SchemaType],
ServiceCreateMixin[EntityType, CreateRequestType, SchemaType],
ServiceUpdateMixin[EntityType, UpdateRequestType],
ServiceDeleteMixin[EntityType],
):
pass

View File

@ -5,10 +5,7 @@ from services.mixins import *
class ProjectService( class ProjectService(
ServiceGetAllMixin[Project, ProjectSchema], ServiceCrudMixin[Project, ProjectSchema, CreateProjectSchema, UpdateProjectSchema]
ServiceCreateMixin[Project, CreateProjectRequest, ProjectSchema],
ServiceUpdateMixin[Project, UpdateProjectRequest],
ServiceDeleteMixin[Project],
): ):
schema_class = ProjectSchema schema_class = ProjectSchema
entity_not_found_msg = "Проект не найден" entity_not_found_msg = "Проект не найден"

View File

@ -5,10 +5,7 @@ from services.mixins import *
class StatusService( class StatusService(
ServiceGetAllMixin[Status, StatusSchema], ServiceCrudMixin[Status, StatusSchema, CreateStatusRequest, UpdateStatusRequest]
ServiceCreateMixin[Status, CreateStatusRequest, StatusSchema],
ServiceUpdateMixin[Status, UpdateStatusRequest],
ServiceDeleteMixin[Status],
): ):
schema_class = StatusSchema schema_class = StatusSchema
entity_not_found_msg = "Статус не найден" entity_not_found_msg = "Статус не найден"