You've already forked RekomenciBackend
add lints
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ from typing import Protocol
|
||||
|
||||
from template_project.application.access_token.entity import AccessTokenId
|
||||
|
||||
|
||||
type RawAccessToken = str
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,7 +7,7 @@ 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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -10,8 +10,5 @@ class UserDataGateway(Protocol):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
async def exists_by_email(
|
||||
self,
|
||||
email: str
|
||||
) -> bool:
|
||||
async def exists_by_email(self, email: str) -> bool:
|
||||
raise NotImplementedError
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -11,6 +11,7 @@ class GetMeResponse:
|
||||
id: UserId
|
||||
email: str
|
||||
|
||||
|
||||
response_converter = get_converter(User, GetMeResponse)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user