This commit is contained in:
Piotr Domański 2026-04-07 17:12:27 +02:00
parent e34208a91b
commit e7160c922b
10 changed files with 46 additions and 18 deletions

View file

@ -47,6 +47,9 @@ class ContextDependency:
return Context(repo=Repository(session)) return Context(repo=Repository(session))
_oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")
class AuthContextDependency: class AuthContextDependency:
""" """
Context dependency for authorized endpoints Context dependency for authorized endpoints
@ -54,7 +57,7 @@ class AuthContextDependency:
async def __call__( async def __call__(
self, self,
token: str = Depends(OAuth2PasswordBearer(tokenUrl="/token")), token: str = Depends(_oauth2_scheme),
session: AsyncSession = Depends(get_db_session), session: AsyncSession = Depends(get_db_session),
) -> Context: ) -> Context:
ctx = Context(repo=Repository(session)) ctx = Context(repo=Repository(session))

View file

@ -1,2 +1,4 @@
from .user import UserController from fooder.controller.user import UserController
from .product import ProductController from fooder.controller.product import ProductController
__all__ = ["UserController", "ProductController"]

View file

@ -23,11 +23,13 @@ class DatabaseSessionManager:
else {} else {}
), ),
) )
self._sessionmaker: async_sessionmaker[AsyncSession] | None = async_sessionmaker( self._sessionmaker: async_sessionmaker[AsyncSession] | None = (
autocommit=False, async_sessionmaker(
autoflush=False, autocommit=False,
bind=self._engine, autoflush=False,
expire_on_commit=False, bind=self._engine,
expire_on_commit=False,
)
) )
async def close(self) -> None: async def close(self) -> None:

View file

@ -7,6 +7,7 @@ class ApiException(Exception):
def __init__(self, message: str | None = None) -> None: def __init__(self, message: str | None = None) -> None:
self.message = message or self.MESSAGE self.message = message or self.MESSAGE
super().__init__(self.message)
class NotFound(ApiException): class NotFound(ApiException):

View file

@ -1,4 +1,10 @@
from .base import ObjModelMixin, Macronutrient, OptionalMacronutrient, Calories, OptionalCalories from .base import (
ObjModelMixin,
Macronutrient,
OptionalMacronutrient,
Calories,
OptionalCalories,
)
from pydantic import BaseModel from pydantic import BaseModel
from fooder.utils.calories import calculate_calories from fooder.utils.calories import calculate_calories

View file

@ -1 +1,3 @@
from .repository import Repository from fooder.repository.repository import Repository
__all__ = ["Repository"]

View file

@ -1,6 +1,13 @@
from typing import TypeVar, Generic, Type, Sequence from typing import TypeVar, Generic, Type, Sequence
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import Delete, Update, select, delete as sa_delete, update as sa_update, ColumnElement from sqlalchemy import (
Delete,
Update,
ColumnElement,
select,
delete as sa_delete,
update as sa_update,
)
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm.exc import StaleDataError from sqlalchemy.orm.exc import StaleDataError
from sqlalchemy.sql import Select from sqlalchemy.sql import Select

View file

@ -7,10 +7,12 @@ from fooder.exc import NotFound
router = APIRouter(tags=["product"]) router = APIRouter(tags=["product"])
_auth_ctx = AuthContextDependency()
@router.get("", response_model=list[ProductModel]) @router.get("", response_model=list[ProductModel])
async def list_products( async def list_products(
ctx: Context = Depends(AuthContextDependency()), ctx: Context = Depends(_auth_ctx),
limit: int = 10, limit: int = 10,
offset: int = 0, offset: int = 0,
q: str | None = None, q: str | None = None,
@ -22,7 +24,7 @@ async def list_products(
async def update_product( async def update_product(
product_id: int, product_id: int,
data: ProductUpdateModel, data: ProductUpdateModel,
ctx: Context = Depends(AuthContextDependency()), ctx: Context = Depends(_auth_ctx),
): ):
async with ctx.repo.transaction(): async with ctx.repo.transaction():
obj = await ctx.repo.product.get_by_id(product_id) obj = await ctx.repo.product.get_by_id(product_id)
@ -33,7 +35,7 @@ async def update_product(
@router.get("/barcode/{barcode}", response_model=ProductModel) @router.get("/barcode/{barcode}", response_model=ProductModel)
async def get_by_barcode( async def get_by_barcode(
barcode: str, barcode: str,
ctx: Context = Depends(AuthContextDependency()), ctx: Context = Depends(_auth_ctx),
): ):
try: try:
return await ctx.repo.product.get_by_barcode(barcode) return await ctx.repo.product.get_by_barcode(barcode)
@ -46,7 +48,7 @@ async def get_by_barcode(
@router.post("", response_model=ProductModel, status_code=201) @router.post("", response_model=ProductModel, status_code=201)
async def create_product( async def create_product(
data: ProductCreateModel, data: ProductCreateModel,
ctx: Context = Depends(AuthContextDependency()), ctx: Context = Depends(_auth_ctx),
): ):
async with ctx.repo.transaction(): async with ctx.repo.transaction():
ctrl = await ProductController.create(ctx, data) ctrl = await ProductController.create(ctx, data)

View file

@ -11,6 +11,8 @@ from fooder.utils.jwt import RefreshToken, generate_token_pair
router = APIRouter(tags=["token"]) router = APIRouter(tags=["token"])
_ctx = ContextDependency()
def gen_token_response(entity_id: int, now: datetime) -> TokenResponse: def gen_token_response(entity_id: int, now: datetime) -> TokenResponse:
access_token, refresh_token = generate_token_pair(entity_id, now) access_token, refresh_token = generate_token_pair(entity_id, now)
@ -23,7 +25,7 @@ def gen_token_response(entity_id: int, now: datetime) -> TokenResponse:
@router.post("", response_model=TokenResponse) @router.post("", response_model=TokenResponse)
async def token_create( async def token_create(
data: Annotated[OAuth2PasswordRequestForm, Depends()], data: Annotated[OAuth2PasswordRequestForm, Depends()],
ctx: Context = Depends(ContextDependency()), ctx: Context = Depends(_ctx),
) -> TokenResponse: ) -> TokenResponse:
now = ctx.clock() now = ctx.clock()
user_ctrl = await UserController.session_start(ctx, data.username, data.password) user_ctrl = await UserController.session_start(ctx, data.username, data.password)
@ -33,7 +35,7 @@ async def token_create(
@router.post("/refresh", response_model=TokenResponse) @router.post("/refresh", response_model=TokenResponse)
async def token_refresh( async def token_refresh(
refresh_token: str, refresh_token: str,
ctx: Context = Depends(ContextDependency()), ctx: Context = Depends(_ctx),
) -> TokenResponse: ) -> TokenResponse:
now = ctx.clock() now = ctx.clock()
token = RefreshToken.decode(refresh_token) token = RefreshToken.decode(refresh_token)

View file

@ -1,6 +1,7 @@
[flake8] [flake8]
max-line-length = 80 max-line-length = 89
extend-select = B950 extend-select = B950
extend-ignore = E203,E501,E701,E712 extend-ignore = E203,E501,E701,E712
exclude = fooder/alembic,fooder/test
extend-immutable-calls = extend-immutable-calls =
Depends Depends