From 5fd53206de4418dd250366dd5e1719ef9777c8a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=AF=D0=BA=D0=B8=D0=BC=D0=B5=D0=BD=D0=BA=D0=BE=D0=B2=20?= =?UTF-8?q?=D0=9A=D0=B8=D1=80=D0=B8=D0=BB=D0=BB=20=D0=90=D0=BB=D0=B5=D0=BA?= =?UTF-8?q?=D1=81=D0=B0=D0=BD=D0=B4=D1=80=D0=BE=D0=B2=D0=B8=D1=87?= Date: Thu, 6 Aug 2026 12:33:54 +0300 Subject: [PATCH 1/2] [DOP-39512] Remove fastapi dependeciy_override and Stub --- .../changelog/next_release/409.improvement.md | 1 + syncmaster/db/factory.py | 5 +- syncmaster/server/__init__.py | 23 ++------- syncmaster/server/api/v1/auth.py | 18 +++---- syncmaster/server/api/v1/runs.py | 9 ++-- syncmaster/server/dependencies/__init__.py | 2 - syncmaster/server/dependencies/stub.py | 49 ------------------- .../server/providers/auth/base_provider.py | 18 +++---- .../server/providers/auth/dummy_provider.py | 32 +++++------- .../providers/auth/keycloak_provider.py | 31 +++++------- .../providers/auth/oauth2_gateway_provider.py | 29 +++++------ syncmaster/server/services/auth.py | 13 +++++ syncmaster/server/services/get_user.py | 9 ++-- syncmaster/server/services/unit_of_work.py | 19 ++++--- tests/conftest.py | 2 +- 15 files changed, 100 insertions(+), 160 deletions(-) create mode 100644 mddocs/docs/changelog/next_release/409.improvement.md delete mode 100644 syncmaster/server/dependencies/stub.py create mode 100644 syncmaster/server/services/auth.py diff --git a/mddocs/docs/changelog/next_release/409.improvement.md b/mddocs/docs/changelog/next_release/409.improvement.md new file mode 100644 index 00000000..69f19794 --- /dev/null +++ b/mddocs/docs/changelog/next_release/409.improvement.md @@ -0,0 +1 @@ +Drop FastAPI `dependency_overrides` feature. See [#14441](https://github.com/fastapi/fastapi/pull/14441). diff --git a/syncmaster/db/factory.py b/syncmaster/db/factory.py index 44c73be8..2aa33527 100644 --- a/syncmaster/db/factory.py +++ b/syncmaster/db/factory.py @@ -6,11 +6,13 @@ from sqlalchemy.ext.asyncio import ( AsyncEngine, AsyncSession, + async_engine_from_config, async_sessionmaker, create_async_engine, ) from syncmaster.server.services.unit_of_work import UnitOfWork +from syncmaster.server.settings import DatabaseSettings from syncmaster.server.settings import ServerAppSettings as Settings @@ -18,7 +20,8 @@ def create_engine(connection_uri: str, **engine_kwargs: Any) -> AsyncEngine: return create_async_engine(url=connection_uri, **engine_kwargs) -def create_session_factory(engine: AsyncEngine) -> async_sessionmaker[AsyncSession]: +def create_session_factory(settings: DatabaseSettings) -> async_sessionmaker[AsyncSession]: + engine = async_engine_from_config(settings.model_dump(), prefix="") return async_sessionmaker( bind=engine, class_=AsyncSession, diff --git a/syncmaster/server/__init__.py b/syncmaster/server/__init__.py index f898d04f..90ae71da 100644 --- a/syncmaster/server/__init__.py +++ b/syncmaster/server/__init__.py @@ -6,10 +6,9 @@ from fastapi import FastAPI, HTTPException from fastapi.exceptions import RequestValidationError from pydantic import ValidationError -from sqlalchemy.ext.asyncio import async_engine_from_config from syncmaster import _raw_version as syncmaster_version -from syncmaster.db.factory import create_session_factory, get_uow +from syncmaster.db.factory import create_session_factory from syncmaster.exceptions import SyncmasterError from syncmaster.server.api.router import api_router from syncmaster.server.handler import ( @@ -19,7 +18,6 @@ validation_exception_handler, ) from syncmaster.server.middlewares import apply_middlewares -from syncmaster.server.services.unit_of_work import UnitOfWork from syncmaster.server.settings import ServerAppSettings as Settings from syncmaster.settings.logging import setup_logging @@ -49,6 +47,8 @@ def application_factory(settings: Settings) -> FastAPI: ) application.state.settings = settings application.state.celery = celery_factory(settings) + application.state.session_factory = create_session_factory(settings.database) + application.include_router(api_router) application.exception_handler(RequestValidationError)(validation_exception_handler) application.exception_handler(ValidationError)(validation_exception_handler) @@ -56,23 +56,6 @@ def application_factory(settings: Settings) -> FastAPI: application.exception_handler(HTTPException)(http_exception_handler) application.exception_handler(Exception)(unknown_exception_handler) - engine = async_engine_from_config(settings.database.model_dump(), prefix="") - session_factory = create_session_factory(engine=engine) - - async def get_settings(): - return settings - - async def get_celery(): - return application.state.celery - - application.dependency_overrides.update( - { - Settings: get_settings, - UnitOfWork: get_uow(session_factory, settings=settings), - Celery: get_celery, - }, - ) - auth_class: type[AuthProvider] = settings.auth.provider # type: ignore[assignment] auth_class.setup(application) diff --git a/syncmaster/server/api/v1/auth.py b/syncmaster/server/api/v1/auth.py index 9a86b449..745f3af2 100644 --- a/syncmaster/server/api/v1/auth.py +++ b/syncmaster/server/api/v1/auth.py @@ -11,13 +11,10 @@ from syncmaster.errors.schemas.invalid_request import InvalidRequestSchema from syncmaster.errors.schemas.not_authorized import NotAuthorizedSchema from syncmaster.schemas.v1.auth import AuthTokenSchema -from syncmaster.server.dependencies import Stub -from syncmaster.server.providers.auth import ( - AuthProvider, - DummyAuthProvider, - KeycloakAuthProvider, -) +from syncmaster.server.providers.auth import AuthProvider +from syncmaster.server.services.auth import get_auth_provider from syncmaster.server.services.get_user import get_user +from syncmaster.server.services.unit_of_work import UnitOfWork router = APIRouter( prefix="/auth", @@ -28,10 +25,12 @@ @router.post("/token") async def token( - auth_provider: Annotated[DummyAuthProvider, Depends(Stub(AuthProvider))], + uow: Annotated[UnitOfWork, Depends()], form_data: Annotated[OAuth2PasswordRequestForm, Depends()], + auth_provider: Annotated[AuthProvider, Depends(get_auth_provider)], ) -> AuthTokenSchema: token = await auth_provider.get_token_password_grant( + uow=uow, grant_type=form_data.grant_type, login=form_data.username, password=form_data.password, @@ -46,10 +45,11 @@ async def token( async def auth_callback( request: Request, code: str, - auth_provider: Annotated[KeycloakAuthProvider, Depends(Stub(AuthProvider))], + auth_provider: Annotated[AuthProvider, Depends(get_auth_provider)], ): token = await auth_provider.get_token_authorization_code_grant( code=code, + request=request, ) request.session["access_token"] = token["access_token"] request.session["refresh_token"] = token["refresh_token"] @@ -64,7 +64,7 @@ async def auth_callback( async def logout( request: Request, current_user: Annotated[User, Depends(get_user())], - auth_provider: Annotated[KeycloakAuthProvider, Depends(Stub(AuthProvider))], + auth_provider: Annotated[AuthProvider, Depends(get_auth_provider)], ): refresh_token = request.session.get("refresh_token", None) request.session.clear() diff --git a/syncmaster/server/api/v1/runs.py b/syncmaster/server/api/v1/runs.py index 64d7eb46..14dbd224 100644 --- a/syncmaster/server/api/v1/runs.py +++ b/syncmaster/server/api/v1/runs.py @@ -5,7 +5,7 @@ from typing import Annotated from celery import Celery -from fastapi import APIRouter, Depends, Query +from fastapi import APIRouter, Depends, Query, Request from kombu.exceptions import KombuError from syncmaster.db.models import RunType, Status, User @@ -20,13 +20,16 @@ ReadRunSchema, RunPageSchema, ) -from syncmaster.server.dependencies import Stub from syncmaster.server.services.get_user import get_user from syncmaster.server.services.unit_of_work import UnitOfWork router = APIRouter(tags=["Runs"], responses=get_error_responses()) +async def get_celery(request: Request) -> Celery: + return request.app.state.celery + + @router.get("/runs") async def read_runs( # noqa: PLR0913, PLR0917 transfer_id: int, @@ -81,7 +84,7 @@ async def read_run( @router.post("/runs") async def start_run( create_run_data: CreateRunSchema, - celery: Annotated[Celery, Depends(Stub(Celery))], + celery: Annotated[Celery, Depends(get_celery)], unit_of_work: Annotated[UnitOfWork, Depends(UnitOfWork)], current_user: Annotated[User, Depends(get_user())], ) -> ReadRunSchema: diff --git a/syncmaster/server/dependencies/__init__.py b/syncmaster/server/dependencies/__init__.py index e625c6f3..0982d0eb 100644 --- a/syncmaster/server/dependencies/__init__.py +++ b/syncmaster/server/dependencies/__init__.py @@ -2,9 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 from syncmaster.server.dependencies.get_access_token import get_access_token -from syncmaster.server.dependencies.stub import Stub __all__ = [ - "Stub", "get_access_token", ] diff --git a/syncmaster/server/dependencies/stub.py b/syncmaster/server/dependencies/stub.py deleted file mode 100644 index c16e6dfb..00000000 --- a/syncmaster/server/dependencies/stub.py +++ /dev/null @@ -1,49 +0,0 @@ -# SPDX-FileCopyrightText: 2023 Andrey Tikhonov (@Tishka17) -# SPDX-License-Identifier: Apache-2.0 - - -# Implementation copied from: -# https://github.com/Tishka17/deseos17/blob/master/src/deseos17/presentation/web_api/dependencies/depends_stub.py -class Stub: - """ - This class is used to prevent fastapi from digging into - real dependencies attributes detecting them as request data - - So instead of - `value: Annotated[MyDependency, Depends()]` - Write - `value: Annotated[MyDependency, Depends(Stub(MyDependency))]` - - And then you can declare how to create it: - `app.dependency_overrides[MyDependency] = my_dependency_factory` - - """ - - def __init__(self, dependency: type, **kwargs): - self._dependency = dependency - self._kwargs = kwargs - - def __call__(self): - raise NotImplementedError - - def __eq__(self, other) -> bool: - if isinstance(other, Stub): - return self._dependency == other._dependency and self._kwargs == other._kwargs - if not self._kwargs: - return self._dependency == other - return False - - def __hash__(self): - if not self._kwargs: - return hash(self._dependency) - serial = ( - self._dependency, - *self._kwargs.items(), - ) - return hash(serial) - - def __repr__(self): - args = "".join(f", {k}={v!r}" for k, v in self._kwargs.items()) - class_name = self.__class__.__name__ - dependency_class = self._dependency.__name__ - return f"{class_name}({dependency_class}{args})" diff --git a/syncmaster/server/providers/auth/base_provider.py b/syncmaster/server/providers/auth/base_provider.py index 79cc2a0a..31894226 100644 --- a/syncmaster/server/providers/auth/base_provider.py +++ b/syncmaster/server/providers/auth/base_provider.py @@ -7,6 +7,7 @@ from fastapi import FastAPI, Request from syncmaster.db.models import User +from syncmaster.server.services.unit_of_work import UnitOfWork class AuthProvider(ABC): @@ -22,7 +23,7 @@ def setup(cls, app: FastAPI) -> FastAPI: """ This method is called by `application_factory`. - Here you should add dependency overrides for auth provider, + Here you should configure your auth provider, set `app.state.auth_provider` and return new `app` object. Examples @@ -31,28 +32,25 @@ def setup(cls, app: FastAPI) -> FastAPI: ```python from fastapi import FastAPI from my_awesome_auth_provider.settings import MyAwesomeAuthProviderSettings - from syncmaster.server.dependencies import Stub class MyAwesomeAuthProvider(AuthProvider): def setup(app): - app.dependency_overrides[AuthProvider] = MyAwesomeAuthProvider - - # `settings_object_factory` returns MyAwesomeAuthProviderSettings object - app.dependency_overrides[MyAwesomeAuthProviderSettings] = settings_object_factory + settings_dict = app.state.settings.auth.model_dump(exclude={"provider}) + settings = MyAwesomeAuthProviderSettings.model_validate(settings_dict) + app.state.auth_provider = MyAwesomeAuthProvider(settings) return app def __init__( self, - settings: Annotated[MyAwesomeAuthProviderSettings, Depends(Stub(MyAwesomeAuthProviderSettings))], + settings: MyAwesomeAuthProviderSettings, ): - # settings object is set automatically by FastAPI's dependency_overrides self.settings = settings ``` """ ... @abstractmethod - async def get_current_user(self, access_token: str | None, request: Request) -> User: + async def get_current_user(self, access_token: str | None, request: Request, uow: UnitOfWork) -> User: """ This method should return currently logged in user. @@ -71,6 +69,7 @@ async def get_current_user(self, access_token: str | None, request: Request) -> @abstractmethod async def get_token_password_grant( # noqa: PLR0913, PLR0917 self, + uow: UnitOfWork, grant_type: str | None = None, login: str | None = None, password: str | None = None, @@ -103,6 +102,7 @@ async def get_token_password_grant( # noqa: PLR0913, PLR0917 async def get_token_authorization_code_grant( self, code: str, + request: Request, scopes: list[str] | None = None, client_id: str | None = None, client_secret: str | None = None, diff --git a/syncmaster/server/providers/auth/dummy_provider.py b/syncmaster/server/providers/auth/dummy_provider.py index e31fb8b1..b60be9b2 100644 --- a/syncmaster/server/providers/auth/dummy_provider.py +++ b/syncmaster/server/providers/auth/dummy_provider.py @@ -4,14 +4,13 @@ import logging from pprint import pformat from time import time -from typing import Annotated, Any +from typing import Any -from fastapi import Depends, FastAPI +from fastapi import FastAPI, Request from syncmaster.db.models import User from syncmaster.exceptions import EntityNotFoundError from syncmaster.exceptions.auth import AuthorizationError -from syncmaster.server.dependencies import Stub from syncmaster.server.providers.auth.base_provider import AuthProvider from syncmaster.server.services.unit_of_work import UnitOfWork from syncmaster.server.settings.auth.dummy import DummyAuthProviderSettings @@ -21,36 +20,30 @@ class DummyAuthProvider(AuthProvider): - def __init__( - self, - settings: Annotated[DummyAuthProviderSettings, Depends(Stub(DummyAuthProviderSettings))], - unit_of_work: Annotated[UnitOfWork, Depends()], - ) -> None: + def __init__(self, settings: DummyAuthProviderSettings) -> None: self._settings = settings - self._uow = unit_of_work @classmethod def setup(cls, app: FastAPI) -> FastAPI: settings = DummyAuthProviderSettings.model_validate(app.state.settings.auth.model_dump(exclude={"provider"})) log.info("Using %s provider with settings:\n%s", cls.__name__, pformat(settings)) - async def get_settings(): - return settings - - app.dependency_overrides[AuthProvider] = cls - app.dependency_overrides[DummyAuthProviderSettings] = get_settings + app.state.auth_provider = cls(settings=settings) return app - async def get_current_user(self, access_token: str | None, *args, **kwargs) -> User: + async def get_current_user( + self, access_token: str | None, request: Request, uow: UnitOfWork, *args, **kwargs + ) -> User: if not access_token: msg = "Missing auth credentials" raise AuthorizationError(msg) user_id = self._get_user_id_from_token(access_token) - return await self._uow.user.read_by_id(user_id) + return await uow.user.read_by_id(user_id) async def get_token_password_grant( # noqa: PLR0913, PLR0917 self, + uow: UnitOfWork, grant_type: str | None = None, login: str | None = None, password: str | None = None, @@ -63,11 +56,11 @@ async def get_token_password_grant( # noqa: PLR0913, PLR0917 raise AuthorizationError(msg) log.info("Get/create user %r in database", login) - async with self._uow: + async with uow: try: - user = await self._uow.user.read_by_username(login) + user = await uow.user.read_by_username(login) except EntityNotFoundError: - user = await self._uow.user.create(username=login) + user = await uow.user.create(username=login) log.info("User with id %r found", user.id) if not user.is_active: @@ -110,6 +103,7 @@ def _get_user_id_from_token(self, token: str) -> int: async def get_token_authorization_code_grant( self, code: str, + request: Request, scopes: list[str] | None = None, client_id: str | None = None, client_secret: str | None = None, diff --git a/syncmaster/server/providers/auth/keycloak_provider.py b/syncmaster/server/providers/auth/keycloak_provider.py index 48799fa2..191f27b4 100644 --- a/syncmaster/server/providers/auth/keycloak_provider.py +++ b/syncmaster/server/providers/auth/keycloak_provider.py @@ -1,9 +1,9 @@ # SPDX-FileCopyrightText: 2023-present MTS PJSC # SPDX-License-Identifier: Apache-2.0 import logging -from typing import Annotated, Any, NoReturn +from typing import Any, NoReturn -from fastapi import Depends, FastAPI, Request +from fastapi import FastAPI, Request from jwcrypto.common import JWException from keycloak import KeycloakOpenID, KeycloakOperationError from starlette.middleware.sessions import SessionMiddleware @@ -12,7 +12,6 @@ from syncmaster.exceptions import EntityNotFoundError from syncmaster.exceptions.auth import AuthorizationError, LogoutError from syncmaster.exceptions.redirect import RedirectError -from syncmaster.server.dependencies import Stub from syncmaster.server.providers.auth.base_provider import AuthProvider from syncmaster.server.services.unit_of_work import UnitOfWork from syncmaster.server.settings.auth.keycloak import KeycloakAuthProviderSettings @@ -21,13 +20,8 @@ class KeycloakAuthProvider(AuthProvider): - def __init__( - self, - settings: Annotated[KeycloakAuthProviderSettings, Depends(Stub(KeycloakAuthProviderSettings))], - unit_of_work: Annotated[UnitOfWork, Depends()], - ) -> None: + def __init__(self, settings: KeycloakAuthProviderSettings) -> None: self.settings = settings - self._uow = unit_of_work self.keycloak_openid = KeycloakOpenID( server_url=str(self.settings.keycloak.api_url).rstrip("/") + "/", client_id=self.settings.keycloak.client_id, @@ -41,12 +35,7 @@ def setup(cls, app: FastAPI) -> FastAPI: settings = KeycloakAuthProviderSettings.model_validate(app.state.settings.auth.model_dump(exclude={"provider"})) log.info("Using %s provider with settings:\n%s", cls.__name__, settings) - async def get_settings(): - return settings - - app.dependency_overrides[AuthProvider] = cls - app.dependency_overrides[KeycloakAuthProviderSettings] = get_settings - + app.state.auth_provider = cls(settings=settings) app.add_middleware( SessionMiddleware, secret_key=settings.cookie.secret_key.get_secret_value(), @@ -59,8 +48,9 @@ async def get_settings(): ) return app - async def get_token_password_grant( # noqa: PLR0913, PLR0917 + async def get_token_password_grant( # noqa: PLR0913 PLR0917 self, + uow: UnitOfWork, grant_type: str | None = None, login: str | None = None, password: str | None = None, @@ -74,6 +64,7 @@ async def get_token_password_grant( # noqa: PLR0913, PLR0917 async def get_token_authorization_code_grant( self, code: str, + request: Request, scopes: list[str] | None = None, client_id: str | None = None, client_secret: str | None = None, @@ -88,7 +79,7 @@ async def get_token_authorization_code_grant( msg = "Failed to get token" raise AuthorizationError(msg) from e - async def get_current_user(self, access_token: str | None, request: Request) -> User: + async def get_current_user(self, access_token: str | None, request: Request, uow: UnitOfWork) -> User: if not access_token: log.debug("No access token found in session") await self.redirect_to_auth() @@ -138,11 +129,11 @@ async def get_current_user(self, access_token: str | None, request: Request) -> middle_name = token_info.get("middle_name") last_name = token_info.get("family_name") - async with self._uow: + async with uow: try: - user = await self._uow.user.read_by_username(login) + user = await uow.user.read_by_username(login) except EntityNotFoundError: - user = await self._uow.user.create( + user = await uow.user.create( username=login, email=email, first_name=first_name, diff --git a/syncmaster/server/providers/auth/oauth2_gateway_provider.py b/syncmaster/server/providers/auth/oauth2_gateway_provider.py index 71803d44..7cf22d76 100644 --- a/syncmaster/server/providers/auth/oauth2_gateway_provider.py +++ b/syncmaster/server/providers/auth/oauth2_gateway_provider.py @@ -1,15 +1,14 @@ # SPDX-FileCopyrightText: 2025-present MTS PJSC # SPDX-License-Identifier: Apache-2.0 import logging -from typing import Annotated, Any +from typing import Any -from fastapi import Depends, FastAPI, Request +from fastapi import FastAPI, Request from keycloak import KeycloakOpenID, KeycloakOperationError from syncmaster.db.models import User from syncmaster.exceptions import EntityNotFoundError from syncmaster.exceptions.auth import AuthorizationError -from syncmaster.server.dependencies import Stub from syncmaster.server.providers.auth.base_provider import AuthProvider from syncmaster.server.services.unit_of_work import UnitOfWork from syncmaster.server.settings.auth.oauth2_gateway import OAuth2GatewayProviderSettings @@ -18,13 +17,8 @@ class OAuth2GatewayProvider(AuthProvider): - def __init__( - self, - settings: Annotated[OAuth2GatewayProviderSettings, Depends(Stub(OAuth2GatewayProviderSettings))], - unit_of_work: Annotated[UnitOfWork, Depends()], - ) -> None: + def __init__(self, settings: OAuth2GatewayProviderSettings) -> None: self.settings = settings - self._uow = unit_of_work self.keycloak_openid = KeycloakOpenID( server_url=str(self.settings.keycloak.api_url).rstrip("/") + "/", client_id=self.settings.keycloak.client_id, @@ -40,17 +34,14 @@ def setup(cls, app: FastAPI) -> FastAPI: ) log.info("Using %s provider with settings:\n%s", cls.__name__, settings) - async def get_settings(): - return settings - - app.dependency_overrides[AuthProvider] = cls - app.dependency_overrides[OAuth2GatewayProviderSettings] = get_settings + app.state.auth_provider = cls(settings=settings) return app async def get_current_user( self, access_token: str | None, request: Request, + uow: UnitOfWork, ) -> User: if not access_token: log.debug("No access token found in request") @@ -81,11 +72,11 @@ async def get_current_user( middle_name = token_info.get("middle_name") last_name = token_info.get("family_name") - async with self._uow: + async with uow: try: - user = await self._uow.user.read_by_username(login) + user = await uow.user.read_by_username(login) except EntityNotFoundError: - user = await self._uow.user.create( + user = await uow.user.create( username=login, email=email, first_name=first_name, @@ -94,8 +85,9 @@ async def get_current_user( ) return user - async def get_token_password_grant( # noqa: PLR0913, PLR0917 + async def get_token_password_grant( # noqa: PLR0913 PLR0917 self, + uow: UnitOfWork, grant_type: str | None = None, login: str | None = None, password: str | None = None, @@ -111,6 +103,7 @@ async def get_token_password_grant( # noqa: PLR0913, PLR0917 async def get_token_authorization_code_grant( self, code: str, + request: Request, scopes: list[str] | None = None, client_id: str | None = None, client_secret: str | None = None, diff --git a/syncmaster/server/services/auth.py b/syncmaster/server/services/auth.py new file mode 100644 index 00000000..bf389433 --- /dev/null +++ b/syncmaster/server/services/auth.py @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2025-present MTS PJSC +# SPDX-License-Identifier: Apache-2.0 +from fastapi import Request + +from syncmaster.server.providers.auth.base_provider import AuthProvider + + +async def get_auth_provider(request: Request) -> AuthProvider: + return request.app.state.auth_provider + + +async def get_personal_token_provider(request: Request) -> AuthProvider: + return request.app.state.personal_token_auth_provider diff --git a/syncmaster/server/services/get_user.py b/syncmaster/server/services/get_user.py index 67b23027..4f0a7d6b 100644 --- a/syncmaster/server/services/get_user.py +++ b/syncmaster/server/services/get_user.py @@ -12,8 +12,9 @@ from syncmaster.db.models import User from syncmaster.exceptions import ActionNotAllowedError, EntityNotFoundError -from syncmaster.server.dependencies import Stub from syncmaster.server.providers.auth import AuthProvider +from syncmaster.server.services.auth import get_auth_provider +from syncmaster.server.services.unit_of_work import UnitOfWork bearer_token = HTTPBearer( description="Perform authentication using Bearer token", @@ -29,14 +30,15 @@ def get_user( is_superuser: bool = False, # noqa: FBT001, FBT002 ) -> Callable[ - [Request, AuthProvider, str | None, HTTPAuthorizationCredentials | None], + [Request, AuthProvider, str | None, HTTPAuthorizationCredentials | None, UnitOfWork], Coroutine[Any, Any, User], ]: async def wrapper( request: Request, - auth_provider: Annotated[AuthProvider, Depends(Stub(AuthProvider))], + auth_provider: Annotated[AuthProvider, Depends(get_auth_provider)], oauth_token: Annotated[str | None, Depends(oauth_schema)], bearer_token: Annotated[HTTPAuthorizationCredentials | None, Depends(bearer_token)], + uow: Annotated[UnitOfWork, Depends()], ) -> User: access_token: str | None = None if bearer_token: @@ -52,6 +54,7 @@ async def wrapper( user = await auth_provider.get_current_user( access_token=access_token, request=request, + uow=uow, ) if user is None: msg = "User not found" diff --git a/syncmaster/server/services/unit_of_work.py b/syncmaster/server/services/unit_of_work.py index 7eef6275..bdd82955 100644 --- a/syncmaster/server/services/unit_of_work.py +++ b/syncmaster/server/services/unit_of_work.py @@ -1,8 +1,9 @@ # SPDX-FileCopyrightText: 2023-present MTS PJSC # SPDX-License-Identifier: Apache-2.0 +from collections.abc import AsyncGenerator from typing import Annotated -from fastapi import Depends +from fastapi import Depends, Request from sqlalchemy.ext.asyncio import AsyncSession from syncmaster.db.models import AuthData @@ -16,18 +17,24 @@ UserRepository, ) from syncmaster.scheduler.settings import SchedulerAppSettings -from syncmaster.server.dependencies import Stub from syncmaster.server.settings import ServerAppSettings from syncmaster.worker.settings import WorkerAppSettings +async def get_session(request: Request) -> AsyncGenerator[AsyncSession]: + async with request.app.state.session_factory() as session: + yield session + + +async def get_settings(request: Request) -> ServerAppSettings: + return request.app.state.settings + + class UnitOfWork: def __init__( self, - settings: Annotated[ - SchedulerAppSettings | ServerAppSettings | WorkerAppSettings, Depends(Stub(ServerAppSettings)) - ], - session: Annotated[AsyncSession, Depends(Stub(AsyncSession))], + settings: Annotated[SchedulerAppSettings | ServerAppSettings | WorkerAppSettings, Depends(get_settings)], + session: Annotated[AsyncSession, Depends(get_session)], ): self._session = session self.user = UserRepository(session=session) diff --git a/tests/conftest.py b/tests/conftest.py index 24fb9d72..ad281a13 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -131,7 +131,7 @@ def mocked_celery() -> Celery: @pytest_asyncio.fixture(scope="session") async def app(settings: Settings, mocked_celery: Celery) -> FastAPI: app = application_factory(settings=settings) - app.dependency_overrides[Celery] = lambda: mocked_celery + app.state.celery = mocked_celery return app From 0e72797090ee0f6c416e67cd5535471510d94341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=AF=D0=BA=D0=B8=D0=BC=D0=B5=D0=BD=D0=BA=D0=BE=D0=B2=20?= =?UTF-8?q?=D0=9A=D0=B8=D1=80=D0=B8=D0=BB=D0=BB=20=D0=90=D0=BB=D0=B5=D0=BA?= =?UTF-8?q?=D1=81=D0=B0=D0=BD=D0=B4=D1=80=D0=BE=D0=B2=D0=B8=D1=87?= Date: Thu, 6 Aug 2026 12:42:24 +0300 Subject: [PATCH 2/2] [DOP-39512] Remove fastapi dependeciy_override and Stub --- syncmaster/server/services/auth.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/syncmaster/server/services/auth.py b/syncmaster/server/services/auth.py index bf389433..989324a9 100644 --- a/syncmaster/server/services/auth.py +++ b/syncmaster/server/services/auth.py @@ -7,7 +7,3 @@ async def get_auth_provider(request: Request) -> AuthProvider: return request.app.state.auth_provider - - -async def get_personal_token_provider(request: Request) -> AuthProvider: - return request.app.state.personal_token_auth_provider