Compare commits

..

7 Commits

Author SHA1 Message Date
IluaAir
723f59d35e add task post 2025-08-15 01:03:48 +03:00
IluaAir
fdc688cf5e repo typing fix 2025-08-11 10:58:41 +03:00
IluaAir
860962bcc3 pydantic validation in service 2025-08-11 10:15:27 +03:00
IluaAir
ddc38dbd07 type cheking 2025-08-11 10:13:08 +03:00
IluaAir
644d5614b9 add get filtered 2025-08-10 20:51:25 +03:00
IluaAir
d6646716b8 move get_all_users to get_all 2025-08-09 01:00:39 +03:00
IluaAir
b2824c2bb2 move delete_one 2025-08-09 00:56:13 +03:00
14 changed files with 122 additions and 54 deletions

View File

@@ -64,7 +64,7 @@ exclude = [
# Set the maximum line length for both linting and formatting. # Set the maximum line length for both linting and formatting.
line-length = 88 line-length = 88
# Assume Python 3.9 for compatibility checks. # Assume Python 3.9 for compatibility checks.
target-version = "py39" target-version = "py312"
# Enable preview features for early access to new rules and formatting changes. # Enable preview features for early access to new rules and formatting changes.
preview = true preview = true
@@ -81,7 +81,7 @@ select = [
] ]
# Ignore specific rules within the selected groups. # Ignore specific rules within the selected groups.
ignore = [ ignore = [
"UP035", "UP",
"B903", "B903",
"B904", "B904",
"E501", "E501",

View File

@@ -1,15 +1,15 @@
from typing import Annotated, AsyncGenerator from typing import Annotated, AsyncGenerator
from fastapi import Depends from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession
from src.core.database import async_session_maker from src.core.database import async_session_maker
from src.core.db_manager import DBManager from src.core.db_manager import DBManager
from src.core.interfaces import IUOWDB
async def get_db() -> AsyncGenerator[AsyncSession, None]: async def get_db() -> AsyncGenerator[IUOWDB, None]:
async with DBManager(async_session_maker) as db: async with DBManager(async_session_maker) as db:
yield db yield db
sessionDep = Annotated[AsyncSession, Depends(get_db)] sessionDep = Annotated[IUOWDB, Depends(get_db)]

View File

@@ -1,17 +1,21 @@
from fastapi import APIRouter from typing import Annotated
from fastapi import APIRouter, Depends
from sqlalchemy import select from sqlalchemy import select
from src.api.dependacies.db_dep import sessionDep from src.api.dependacies.db_dep import sessionDep
from src.api.dependacies.user_dep import ActiveUser from src.api.dependacies.user_dep import ActiveUser
from src.models.tasks import TasksORM from src.models.tasks import TasksORM
from src.schemas.tasks import TaskADDRequest
from src.services.tasks import TaskService
router = APIRouter(prefix="/tasks", tags=["Tasks"]) router = APIRouter(prefix="/tasks", tags=["Tasks"])
@router.get("/") @router.get("/")
async def get_tasks(db: sessionDep, user: ActiveUser): async def get_tasks(session: sessionDep, user: ActiveUser):
query = select(TasksORM.id, TasksORM.description).where(TasksORM.user_id == user.id) query = select(TasksORM.id, TasksORM.description).where(TasksORM.user_id == user.id)
tasks = await db.session.execute(query) tasks = await session.session.execute(query)
result = tasks.scalars().all() result = tasks.scalars().all()
return result return result
@@ -21,7 +25,15 @@ async def get_task_id(task_id: int): ...
@router.post("/") @router.post("/")
async def post_task(): ... async def post_task(
task_data: Annotated[TaskADDRequest, Depends()],
session: sessionDep,
user: ActiveUser,
):
result = await TaskService(session).create_task(
user_id=user.id, task_data=task_data
)
return result
@router.put("/{task_id}") @router.put("/{task_id}")

View File

@@ -15,26 +15,28 @@ async def get_me(user: ActiveUser):
@router.get("/") @router.get("/")
async def get_all_users(db: sessionDep, _: AdminUser): async def get_all_users(session: sessionDep, _: AdminUser):
users = await UserService(db).get_all_users() users = await UserService(session).get_all_users()
return users return users
@router.get("/{id}") @router.get("/{id}")
async def get_user_by_id(db: sessionDep, id: int, _: CurrentOrAdmin): async def get_user_by_id(session: sessionDep, id: int, _: CurrentOrAdmin):
user = await UserService(db).get_user_by_filter_or_raise(id=id) user = await UserService(session).get_user_by_filter_or_raise(id=id)
return user return user
@router.patch("/{id}") @router.patch("/{id}")
async def patch_user( async def patch_user(
db: sessionDep, id: int, _: CurrentOrAdmin, user_update: UserUpdate = Body() session: sessionDep, id: int, _: CurrentOrAdmin, user_update: UserUpdate = Body()
): ):
updated_user = await UserService(db).update_user(id=id, update_data=user_update) updated_user = await UserService(session).update_user(
id=id, update_data=user_update
)
return updated_user return updated_user
@router.delete("/{id}") @router.delete("/{id}")
async def delete_user(db: sessionDep, id: int, _: AdminUser): async def delete_user(session: sessionDep, id: int, _: AdminUser):
await UserService(db).delete_user(id) await UserService(session).delete_user(id)
return {"message": "User deleted successfully"} return {"message": "User deleted successfully"}

View File

@@ -1,20 +1,24 @@
from typing import Any
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
from src.repository.tasks import TasksRepo from src.repository.tasks import TasksRepo
from src.repository.users import UsersRepo from src.repository.users import UsersRepo
class DBManager: class DBManager:
def __init__(self, session_factory): def __init__(self, session_factory: async_sessionmaker[AsyncSession]):
self.session_factory = session_factory self.session_factory = session_factory
async def __aenter__(self): async def __aenter__(self) -> "DBManager":
self.session = self.session_factory() self.session: AsyncSession = self.session_factory()
self.user = UsersRepo(self.session) self.user = UsersRepo(self.session)
self.task = TasksRepo(self.session) self.task = TasksRepo(self.session)
return self return self
async def __aexit__(self, exc_type, exc_val, exc_tb): async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
await self.session.rollback() await self.session.rollback()
await self.session.close() await self.session.close()
async def commit(self): async def commit(self) -> None:
await self.session.commit() await self.session.commit()

View File

@@ -1,15 +1,18 @@
from typing import Protocol from typing import Any, Protocol
from sqlalchemy.ext.asyncio import AsyncSession
from src.repository.tasks import TasksRepo from src.repository.tasks import TasksRepo
from src.repository.users import UsersRepo from src.repository.users import UsersRepo
class IUOWDB(Protocol): class IUOWDB(Protocol):
session: AsyncSession
user: UsersRepo user: UsersRepo
task: TasksRepo task: TasksRepo
async def __aenter__(self) -> "IUOWDB": ... async def __aenter__(self) -> "IUOWDB": ...
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: ... async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: ...
async def commit(self) -> None: ... async def commit(self) -> None: ...

View File

@@ -1,23 +1,42 @@
from pydantic import BaseModel from typing import Any, Generic, Mapping, Sequence, Type, TypeVar
from sqlalchemy import insert, select
from sqlalchemy import delete, insert, select
from sqlalchemy.ext.asyncio import AsyncSession
from src.core.database import Base from src.core.database import Base
ModelType = TypeVar("ModelType", bound=Base)
class BaseRepo:
model: type[Base] = None
def __init__(self, session): class BaseRepo(Generic[ModelType]):
self.session = session model: Type[ModelType]
async def create_one(self, data: BaseModel): def __init__(self, session: AsyncSession) -> None:
statement = insert(self.model).values(data.model_dump()).returning(self.model) self.session: AsyncSession = session
async def get_filtered(
self, *filters: Any, **filter_by: Any
) -> Sequence[ModelType]:
query = select(self.model).filter(*filters).filter_by(**filter_by)
result = await self.session.execute(query)
models = result.scalars().all()
return models
async def create_one(self, data: Mapping[str, Any]) -> ModelType:
statement = insert(self.model).values(data).returning(self.model)
result = await self.session.execute(statement) result = await self.session.execute(statement)
obj = result.scalar_one() obj: ModelType = result.scalar_one()
return obj return obj
async def get_one_or_none(self, **filter_by): async def get_one_or_none(self, **filter_by: Any) -> ModelType | None:
query = select(self.model).filter_by(**filter_by) query = select(self.model).filter_by(**filter_by)
result = await self.session.execute(query) result = await self.session.execute(query)
model = result.scalars().one_or_none() model_obj: ModelType | None = result.scalars().one_or_none()
return model return model_obj
async def get_all(self, *args: Any, **kwargs: Any) -> Sequence[ModelType]:
result: Sequence[ModelType] = await self.get_filtered(*args, **kwargs)
return result
async def delete_one(self, **filter_by) -> None:
await self.session.execute(delete(self.model).filter_by(**filter_by))

View File

@@ -3,4 +3,4 @@ from src.repository.base import BaseRepo
class TasksRepo(BaseRepo): class TasksRepo(BaseRepo):
model = TasksORM model: type[TasksORM] = TasksORM

View File

@@ -1,26 +1,17 @@
from sqlalchemy import delete, select, update from sqlalchemy import update
from src.models import UsersORM from src.models import UsersORM
from src.repository.base import BaseRepo from src.repository.base import BaseRepo
class UsersRepo(BaseRepo): class UsersRepo(BaseRepo):
model = UsersORM model: type[UsersORM] = UsersORM
async def get_all_users(self) -> list[UsersORM]:
query = select(self.model)
result = await self.session.execute(query)
models = result.scalars().all()
return models
async def delete_one(self, id: int) -> None:
await self.session.execute(delete(self.model).where(self.model.id == id))
async def update_one(self, id: int, data: dict) -> UsersORM: async def update_one(self, id: int, data: dict) -> UsersORM:
stmt = ( stmt = (
update(self.model) update(self.model)
.where(self.model.id == id) .where(self.model.id == id)
.values(data.model_dump(exclude_unset=True)) .values(data)
.returning(self.model) .returning(self.model)
) )
result = await self.session.execute(stmt) result = await self.session.execute(stmt)

View File

@@ -0,0 +1,20 @@
from datetime import date
from typing import Literal
from pydantic import BaseModel, ConfigDict
class TaskADDRequest(BaseModel):
title: str
description: str | None = None
due_date: date | None = None
priority: Literal["low", "medium", "high", "critical"] = "medium"
class Task(TaskADDRequest):
id: int
user_id: int
status: Literal["open", "closed", "in_progress", "todo"]
time_spent: int
model_config = ConfigDict(from_attributes=True)

View File

@@ -15,7 +15,7 @@ class AuthService(BaseService):
email=cred.email, email=cred.email,
hashed_password=hashed_pass, hashed_password=hashed_pass,
) )
result = await self.session.user.create_one(user_to_insert) result = await self.session.user.create_one(user_to_insert.model_dump())
await self.session.commit() await self.session.commit()
return User.model_validate(result) return User.model_validate(result)

View File

@@ -2,7 +2,7 @@ from src.core.interfaces import IUOWDB
class BaseService: class BaseService:
session: IUOWDB | None session: IUOWDB
def __init__(self, session: "IUOWDB"): def __init__(self, session: "IUOWDB"):
self.session = session self.session = session

View File

@@ -1,4 +1,19 @@
from fastapi import HTTPException
from src.models.tasks import TasksORM
from src.schemas.tasks import Task, TaskADDRequest
from src.services.base import BaseService from src.services.base import BaseService
class TasksService(BaseService): ... class TaskService(BaseService):
model = TasksORM
async def create_task(self, user_id: int, task_data: TaskADDRequest) -> Task:
user = await self.session.user.get_one_or_none(id=user_id)
if user is None:
raise HTTPException(status_code=404, detail="User not found.")
data_to_insert = task_data.model_dump(exclude_none=True)
data_to_insert["user_id"] = user.id
created_task_orm = await self.session.task.create_one(data_to_insert)
await self.session.commit()
return Task.model_validate(created_task_orm)

View File

@@ -24,7 +24,7 @@ class UserService(BaseService):
return user return user
async def get_all_users(self) -> list[User]: async def get_all_users(self) -> list[User]:
users = await self.session.user.get_all_users() users = await self.session.user.get_all()
return [User.model_validate(user) for user in users] return [User.model_validate(user) for user in users]
async def delete_user(self, id: int) -> None: async def delete_user(self, id: int) -> None:
@@ -33,6 +33,8 @@ class UserService(BaseService):
async def update_user(self, id: int, update_data: UserUpdate) -> User: async def update_user(self, id: int, update_data: UserUpdate) -> User:
await self.get_user_by_filter_or_raise(id=id) await self.get_user_by_filter_or_raise(id=id)
user = await self.session.user.update_one(id=id, data=update_data) user = await self.session.user.update_one(
id=id, data=update_data.model_dump(exclude_unset=True)
)
await self.session.commit() await self.session.commit()
return User.model_validate(user) return User.model_validate(user)