diff --git a/src/template_project/adapters/access_token/cryptographer.py b/src/template_project/adapters/access_token/cryptographer.py index 7639ca3..9a9f5a4 100644 --- a/src/template_project/adapters/access_token/cryptographer.py +++ b/src/template_project/adapters/access_token/cryptographer.py @@ -6,7 +6,6 @@ from cryptography.fernet import Fernet from template_project.application.access_token.cryptographer import AccessTokenCryptographer from template_project.application.access_token.entity import AccessTokenId - type RawAccessToken = str diff --git a/src/template_project/adapters/access_token/factory.py b/src/template_project/adapters/access_token/factory.py index 6c76de5..b6c59b2 100644 --- a/src/template_project/adapters/access_token/factory.py +++ b/src/template_project/adapters/access_token/factory.py @@ -1,4 +1,5 @@ from typing import override + from template_project.application.access_token.entity import AccessToken from template_project.application.access_token.entity_factory import AccessTokenFactory from template_project.application.user.entity import UserId diff --git a/src/template_project/adapters/data_gateways/access_token.py b/src/template_project/adapters/data_gateways/access_token.py index c8fd6e4..e85b9f7 100644 --- a/src/template_project/adapters/data_gateways/access_token.py +++ b/src/template_project/adapters/data_gateways/access_token.py @@ -2,6 +2,7 @@ from typing import override from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession + from template_project.adapters.data_gateways.tables import access_token_table from template_project.application.access_token.data_gateway import AccessTokenDataGateway from template_project.application.access_token.entity import AccessToken, AccessTokenId @@ -14,7 +15,7 @@ class DefaultAccessTokenDataGateway(AccessTokenDataGateway): @override async def load_with_id(self, access_token_id: AccessTokenId) -> AccessToken | None: statement = select(AccessToken).where( - access_token_table.c.id==access_token_id, + access_token_table.c.id == access_token_id, ) result = await self._session.execute(statement) return result.scalar_one_or_none() diff --git a/src/template_project/adapters/data_gateways/tables.py b/src/template_project/adapters/data_gateways/tables.py index bc1b131..c1a7798 100644 --- a/src/template_project/adapters/data_gateways/tables.py +++ b/src/template_project/adapters/data_gateways/tables.py @@ -10,7 +10,6 @@ from sqlalchemy.orm import registry from template_project.application.access_token.entity import AccessToken from template_project.application.user.entity import User - meta_data = MetaData() user_table = Table( diff --git a/src/template_project/adapters/data_gateways/user.py b/src/template_project/adapters/data_gateways/user.py index 9c1c7bf..346b3fc 100644 --- a/src/template_project/adapters/data_gateways/user.py +++ b/src/template_project/adapters/data_gateways/user.py @@ -1,4 +1,5 @@ -from typing import override +from typing import cast, override + from sqlalchemy import exists, select from sqlalchemy.ext.asyncio import AsyncSession @@ -13,7 +14,7 @@ class DefaultUserDataGateway(UserDataGateway): @override async def load_with_id(self, id_: UserId) -> User | None: - statement = select(User).where(user_table.c.id==id_) + statement = select(User).where(user_table.c.id == id_) result = await self._session.execute(statement) return result.scalar_one_or_none() @@ -24,4 +25,4 @@ class DefaultUserDataGateway(UserDataGateway): result_fetchone = result.fetchone() if result_fetchone is None: return False - return result_fetchone[0] + return cast(bool, result_fetchone[0]) diff --git a/src/template_project/adapters/password_utils.py b/src/template_project/adapters/password_utils.py index d896541..c1a4c94 100644 --- a/src/template_project/adapters/password_utils.py +++ b/src/template_project/adapters/password_utils.py @@ -1,4 +1,3 @@ -from abc import abstractmethod from typing import override import argon2 @@ -27,7 +26,7 @@ class ArgonPasswordVerifying(PasswordVerifying): ) -> None: self._password_hasher = password_hasher - @abstractmethod + @override def verify( self, verifiable_password: SecretString, @@ -35,6 +34,5 @@ class ArgonPasswordVerifying(PasswordVerifying): ) -> bool: try: return self._password_hasher.verify(hashed_password, verifiable_password.get_value()) - except Argon2Error as e: + except Argon2Error: return False - diff --git a/src/template_project/application/access_token/cryptographer.py b/src/template_project/application/access_token/cryptographer.py index 41c5ba3..56de042 100644 --- a/src/template_project/application/access_token/cryptographer.py +++ b/src/template_project/application/access_token/cryptographer.py @@ -3,7 +3,6 @@ from typing import Protocol from template_project.application.access_token.entity import AccessTokenId - type RawAccessToken = str diff --git a/src/template_project/application/access_token/entity.py b/src/template_project/application/access_token/entity.py index fdc0f2b..122c295 100644 --- a/src/template_project/application/access_token/entity.py +++ b/src/template_project/application/access_token/entity.py @@ -10,6 +10,7 @@ from template_project.application.user.entity import UserId AccessTokenId = NewType("AccessTokenId", UUID) + @to_entity class AccessToken(Entity[AccessTokenId]): user_id: UserId @@ -32,17 +33,12 @@ class AccessToken(Entity[AccessTokenId]): revoked=False, ) - def ensure_expired(self) -> None: if self.expired_predicate(): raise AccessTokenExpiredError(id_=self.id) def expired_predicate(self) -> bool: - return ( - (self.expires_in < datetime.now(tz=UTC)) - or self.revoked - or self.deleted_at is not None - ) + return (self.expires_in < datetime.now(tz=UTC)) or self.revoked or self.deleted_at is not None def revoke(self) -> None: self.revoked = True diff --git a/src/template_project/application/common/data_structure.py b/src/template_project/application/common/data_structure.py index 4fd1c8a..096fa63 100644 --- a/src/template_project/application/common/data_structure.py +++ b/src/template_project/application/common/data_structure.py @@ -7,11 +7,11 @@ from typing import dataclass_transform eq_default=False, kw_only_default=True, ) -def to_data_structure[_InteractorClsT](interactor_cls: type[_InteractorClsT]) -> type[_InteractorClsT]: +def to_data_structure[InteractorClsT](interactor_cls: type[InteractorClsT]) -> type[InteractorClsT]: return dataclass( kw_only=True, eq=False, match_args=False, frozen=True, slots=True, - )(interactor_cls) \ No newline at end of file + )(interactor_cls) diff --git a/src/template_project/application/common/entity.py b/src/template_project/application/common/entity.py index 03a39d0..19df759 100644 --- a/src/template_project/application/common/entity.py +++ b/src/template_project/application/common/entity.py @@ -1,20 +1,20 @@ from collections.abc import Hashable from dataclasses import dataclass from datetime import datetime -from typing import dataclass_transform, override +from typing import cast, dataclass_transform, override from uuid import UUID from template_project.application.common.errors import EntityAlreadyDeletedError @dataclass_transform(kw_only_default=True) -def to_entity[_EntityCLsT](entity_cls: type[_EntityCLsT]) -> type[_EntityCLsT]: +def to_entity[EntityCLsT](entity_cls: type[EntityCLsT]) -> type[EntityCLsT]: return dataclass(kw_only=True)(entity_cls) @to_entity -class Entity[_EntityId: UUID](Hashable): - id: _EntityId +class Entity[EntityId: UUID](Hashable): + id: EntityId created_at: datetime deleted_at: datetime | None = None @@ -25,7 +25,7 @@ class Entity[_EntityId: UUID](Hashable): @override def __eq__(self, other: object) -> bool: if isinstance(other, Entity): - return self.id == other.id + return cast(bool, self.id == other.id) return NotImplemented @override diff --git a/src/template_project/application/common/interactor.py b/src/template_project/application/common/interactor.py index 79a0407..1e7911b 100644 --- a/src/template_project/application/common/interactor.py +++ b/src/template_project/application/common/interactor.py @@ -7,7 +7,7 @@ from typing import dataclass_transform eq_default=False, kw_only_default=True, ) -def to_interactor[_InteractorClsT](interactor_cls: type[_InteractorClsT]) -> type[_InteractorClsT]: +def to_interactor[InteractorClsT](interactor_cls: type[InteractorClsT]) -> type[InteractorClsT]: return dataclass( kw_only=True, eq=False, @@ -15,4 +15,4 @@ def to_interactor[_InteractorClsT](interactor_cls: type[_InteractorClsT]) -> typ frozen=True, match_args=False, slots=True, - )(interactor_cls) \ No newline at end of file + )(interactor_cls) diff --git a/src/template_project/application/user/data_gateway.py b/src/template_project/application/user/data_gateway.py index bce7366..4a96ace 100644 --- a/src/template_project/application/user/data_gateway.py +++ b/src/template_project/application/user/data_gateway.py @@ -10,8 +10,5 @@ class UserDataGateway(Protocol): raise NotImplementedError @abstractmethod - async def exists_by_email( - self, - email: str - ) -> bool: - raise NotImplementedError \ No newline at end of file + async def exists_by_email(self, email: str) -> bool: + raise NotImplementedError diff --git a/src/template_project/application/user/entity.py b/src/template_project/application/user/entity.py index bc1dd22..886a1bb 100644 --- a/src/template_project/application/user/entity.py +++ b/src/template_project/application/user/entity.py @@ -8,6 +8,7 @@ from template_project.application.common.entity import Entity, to_entity UserId = NewType("UserId", UUID) + @to_entity class User(Entity[UserId]): email: str diff --git a/src/template_project/application/user/errors.py b/src/template_project/application/user/errors.py index 796bd6f..346c567 100644 --- a/src/template_project/application/user/errors.py +++ b/src/template_project/application/user/errors.py @@ -1,4 +1,5 @@ from typing import override + from template_project.application.common.errors import ApplicationError, to_error @@ -10,6 +11,7 @@ class UserWithEmailAlreadyExistsError(ApplicationError): def __str__(self) -> str: return f"User with the email={self.email!r} already exists" + @to_error class UserUnauthorizedError(ApplicationError): pass diff --git a/src/template_project/application/user/interactors/get_me.py b/src/template_project/application/user/interactors/get_me.py index 7bf7327..9b3f3d7 100644 --- a/src/template_project/application/user/interactors/get_me.py +++ b/src/template_project/application/user/interactors/get_me.py @@ -11,6 +11,7 @@ class GetMeResponse: id: UserId email: str + response_converter = get_converter(User, GetMeResponse) diff --git a/src/template_project/web_api/configuration.py b/src/template_project/web_api/configuration.py index 6e7e087..087a9a5 100644 --- a/src/template_project/web_api/configuration.py +++ b/src/template_project/web_api/configuration.py @@ -3,6 +3,7 @@ from datetime import timedelta from pathlib import Path from tomllib import loads from typing import dataclass_transform + from adaptix import P, Retort, loader from template_project.application.common.containers import SecretString diff --git a/src/template_project/web_api/entiry_point.py b/src/template_project/web_api/entiry_point.py index b7e7514..00642a1 100644 --- a/src/template_project/web_api/entiry_point.py +++ b/src/template_project/web_api/entiry_point.py @@ -1,16 +1,16 @@ import argparse import asyncio +import sys from collections.abc import AsyncIterator from contextlib import asynccontextmanager from pathlib import Path -import sys from typing import Final +import uvicorn from dishka import AsyncContainer from dishka.integrations.fastapi import setup_dishka from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware -import uvicorn from template_project.web_api.configuration import load_configuration from template_project.web_api.ioc.make import make_ioc @@ -35,11 +35,13 @@ LOG_CONFIG: Final = { }, } + @asynccontextmanager async def lifespan(app: FastAPI) -> AsyncIterator[None]: yield await app.state.dishka_container.close() + def make_asgi_application( ioc: AsyncContainer, ) -> FastAPI: @@ -65,6 +67,7 @@ def make_asgi_application( return app + def _main( configuration_path: Path, ) -> None: @@ -83,7 +86,8 @@ def _main( def main() -> None: if sys.platform == "win32": - from asyncio import WindowsSelectorEventLoopPolicy + from asyncio import WindowsSelectorEventLoopPolicy # noqa: PLC0415 + asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy()) arg_parser = argparse.ArgumentParser() @@ -95,5 +99,6 @@ def main() -> None: args = arg_parser.parse_args() _main(args.configuration) + if __name__ == "__main__": main() diff --git a/src/template_project/web_api/identity_provider.py b/src/template_project/web_api/identity_provider.py index 93bbaa9..3829a0b 100644 --- a/src/template_project/web_api/identity_provider.py +++ b/src/template_project/web_api/identity_provider.py @@ -1,4 +1,4 @@ -from abc import abstractmethod +from typing import override from fastapi import Request @@ -11,8 +11,7 @@ from template_project.application.user.data_gateway import UserDataGateway from template_project.application.user.entity import User from template_project.application.user.errors import UserUnauthorizedError - -TOKEN_TYPE = "Bearer" +TOKEN_TYPE = "Bearer" # noqa: S105 BEARER_SECTIONS = 2 AUTH_HEADER = "Authorization" @@ -31,7 +30,7 @@ class WebApiIdentityProvider(IdentityProvider): self._access_token_data_gateway = access_token_data_gateway self._access_token_cryptographer = access_token_cryptographer - @abstractmethod + @override async def get_current_user(self) -> User: auth_tokn = self._request.headers[AUTH_HEADER] diff --git a/src/template_project/web_api/ioc/connection.py b/src/template_project/web_api/ioc/connection.py index 33d155a..d7cf620 100644 --- a/src/template_project/web_api/ioc/connection.py +++ b/src/template_project/web_api/ioc/connection.py @@ -1,4 +1,5 @@ -from typing import AsyncIterable +from collections.abc import AsyncIterable + from dishka import Provider, Scope, provide from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine diff --git a/src/template_project/web_api/ioc/cryptographer.py b/src/template_project/web_api/ioc/cryptographer.py index 46451dc..f8733a7 100644 --- a/src/template_project/web_api/ioc/cryptographer.py +++ b/src/template_project/web_api/ioc/cryptographer.py @@ -1,6 +1,6 @@ import argon2 from cryptography.fernet import Fernet -from dishka import Provider, Scope, WithParents, provide, provide_all +from dishka import BaseScope, Provider, Scope, WithParents, provide, provide_all from template_project.adapters.access_token.cryptographer import FernetAccessTokenCryptographer from template_project.adapters.password_utils import ArgonPasswordHasher, ArgonPasswordVerifying @@ -8,7 +8,7 @@ from template_project.web_api.configuration import AccessTokenConfiguration class CryptographerProvider(Provider): - scope = Scope.APP + scope: BaseScope | None = Scope.APP @provide def argon_password_hasher(self) -> argon2.PasswordHasher: diff --git a/src/template_project/web_api/ioc/data_gateway.py b/src/template_project/web_api/ioc/data_gateway.py index 6e45e09..dec7533 100644 --- a/src/template_project/web_api/ioc/data_gateway.py +++ b/src/template_project/web_api/ioc/data_gateway.py @@ -1,4 +1,4 @@ -from dishka import Provider, Scope, WithParents, provide, provide_all +from dishka import BaseScope, Provider, Scope, WithParents, provide, provide_all from template_project.adapters.data_gateways.access_token import DefaultAccessTokenDataGateway from template_project.adapters.data_gateways.user import DefaultUserDataGateway @@ -6,7 +6,7 @@ from template_project.adapters.unit_of_work import DefaultUnitOfWork class DataGatewayProvider(Provider): - scope = Scope.REQUEST + scope: BaseScope | None = Scope.REQUEST unit_of_work = provide(WithParents[DefaultUnitOfWork]) data_gateways = provide_all( diff --git a/src/template_project/web_api/ioc/factory.py b/src/template_project/web_api/ioc/factory.py index 24aaaaf..9fcbbdf 100644 --- a/src/template_project/web_api/ioc/factory.py +++ b/src/template_project/web_api/ioc/factory.py @@ -1,10 +1,10 @@ -from dishka import Provider, Scope, WithParents, provide_all +from dishka import BaseScope, Provider, Scope, WithParents, provide_all from template_project.adapters.access_token.factory import DefaultAccessTokenFactory class FactoryProvider(Provider): - scope = Scope.APP + scope: BaseScope | None = Scope.APP provides = provide_all( WithParents[DefaultAccessTokenFactory], diff --git a/src/template_project/web_api/ioc/interactor.py b/src/template_project/web_api/ioc/interactor.py index 657f2c2..87b3f3d 100644 --- a/src/template_project/web_api/ioc/interactor.py +++ b/src/template_project/web_api/ioc/interactor.py @@ -1,10 +1,10 @@ -from dishka import Provider, Scope, provide_all +from dishka import BaseScope, Provider, Scope, provide_all from template_project.application.user.interactors.sign_up import UserSignUpInteractor class InteractorProvider(Provider): - scope = Scope.REQUEST + scope: BaseScope | None = Scope.REQUEST interactors = provide_all( UserSignUpInteractor, diff --git a/src/template_project/web_api/ioc/make.py b/src/template_project/web_api/ioc/make.py index 4443ad2..f905ea0 100644 --- a/src/template_project/web_api/ioc/make.py +++ b/src/template_project/web_api/ioc/make.py @@ -1,11 +1,16 @@ from dishka import AsyncContainer, make_async_container from dishka.integrations.fastapi import FastapiProvider -from template_project.web_api.configuration import AccessTokenConfiguration, Configuration, DatabaseConfiguration, ServerConfiguration +from template_project.web_api.configuration import ( + AccessTokenConfiguration, + Configuration, + DatabaseConfiguration, + ServerConfiguration, +) +from template_project.web_api.ioc.cryptographer import CryptographerProvider from template_project.web_api.ioc.data_gateway import DataGatewayProvider from template_project.web_api.ioc.factory import FactoryProvider from template_project.web_api.ioc.interactor import InteractorProvider -from template_project.web_api.ioc.cryptographer import CryptographerProvider def make_ioc(configuration: Configuration) -> AsyncContainer: diff --git a/src/template_project/web_api/routes/user.py b/src/template_project/web_api/routes/user.py index 497b614..cc98a95 100644 --- a/src/template_project/web_api/routes/user.py +++ b/src/template_project/web_api/routes/user.py @@ -6,7 +6,6 @@ from pydantic import BaseModel, SecretStr from template_project.application.common.containers import SecretString from template_project.application.user.interactors.sign_up import UserSignUpInteractor - router = APIRouter(route_class=DishkaRoute) @@ -25,8 +24,7 @@ async def sign_up( interactor: FromDishka[UserSignUpInteractor], ) -> UserSignUpResponse: response_interactor = await interactor.execute( - email=request.email, - password=SecretString(request.password.get_secret_value()) + email=request.email, password=SecretString(request.password.get_secret_value()) ) return UserSignUpResponse( access_token=response_interactor.access_token,