From 361378929f1d4a7ce698974a9226058233059bc2 Mon Sep 17 00:00:00 2001 From: pi3c Date: Mon, 8 Apr 2024 23:55:30 +0300 Subject: [PATCH] add some in company and linters --- .pre-commit-config.yaml | 15 ++++--- api/app_builder/main.py | 1 - api/app_builder/routers.py | 8 +++- api/application/usecase/company/__init__.py | 0 .../usecase/company/get_users_company.py | 12 +++++ api/domain/company/model.py | 10 ++--- api/domain/company/repository.py | 4 +- api/domain/user/error.py | 10 ++--- api/infrastructure/auth/jwt_processor.py | 8 +--- api/infrastructure/persistence/models/base.py | 2 +- .../repositories/company_repository.py | 45 +++++++++++++++++++ api/presentation/auth/fasapi_auth.py | 4 +- api/presentation/routers/company.py | 4 +- 13 files changed, 87 insertions(+), 36 deletions(-) create mode 100644 api/application/usecase/company/__init__.py create mode 100644 api/application/usecase/company/get_users_company.py create mode 100644 api/infrastructure/persistence/repositories/company_repository.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5451ddd..c6b68b5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.3.0 + rev: v4.6.0 hooks: - id: trailing-whitespace # убирает лишние пробелы - id: check-added-large-files # проверяет тяжелые файлы на изменения @@ -12,7 +12,7 @@ repos: # Отсортировывает импорты в проекте - repo: https://github.com/pycqa/isort - rev: 5.12.0 + rev: 5.13.2 hooks: - id: isort exclude: __init__.py @@ -20,29 +20,30 @@ repos: # Обновляет синтаксис Python кода в соответствии с последними версиями - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 + rev: v3.15.2 hooks: - id: pyupgrade args: [ --py310-plus ] # Форматирует код под PEP8 - repo: https://github.com/pre-commit/mirrors-autopep8 - rev: v2.0.1 + rev: v2.0.4 hooks: - id: autopep8 args: [ "-i", "--in-place", "--max-line-length=120" ] # Сканер стилистических ошибок, нарушающие договоренности PEP8 - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 + rev: 7.0.0 hooks: - id: flake8 + additional_dependencies: [flake8-print, pep8-naming, flake8-bugbear] exclude: __init__.py args: [ "--ignore=E501,F821", "--max-line-length=120" ] # Форматирует код под PEP8 c помощью black - repo: https://github.com/psf/black - rev: 23.1.0 + rev: 24.3.0 hooks: - id: black language_version: python3.10 @@ -50,7 +51,7 @@ repos: # Проверка статических типов с помощью mypy - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.991 + rev: v1.9.0 hooks: - id: mypy exclude: 'migrations' diff --git a/api/app_builder/main.py b/api/app_builder/main.py index 9d820e1..8a1c7fb 100644 --- a/api/app_builder/main.py +++ b/api/app_builder/main.py @@ -22,7 +22,6 @@ from .routers import init_routers @asynccontextmanager async def lifespan(app: FastAPI) -> AsyncGenerator: - print("init lifespan") app.dependency_overrides[DBSettings] = get_db_settings app.dependency_overrides[JwtSettings] = get_jwt_settings app.dependency_overrides[Settings] = app_settings diff --git a/api/app_builder/routers.py b/api/app_builder/routers.py index 714fbac..9a34e3b 100644 --- a/api/app_builder/routers.py +++ b/api/app_builder/routers.py @@ -1,7 +1,11 @@ from fastapi import FastAPI -from api.presentation.routers import (auth_router, company_router, - healthcheck_router, user_router) +from api.presentation.routers import ( + auth_router, + company_router, + healthcheck_router, + user_router, +) def init_routers(app: FastAPI) -> None: diff --git a/api/application/usecase/company/__init__.py b/api/application/usecase/company/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/application/usecase/company/get_users_company.py b/api/application/usecase/company/get_users_company.py new file mode 100644 index 0000000..30f9b8a --- /dev/null +++ b/api/application/usecase/company/get_users_company.py @@ -0,0 +1,12 @@ +from api.application.contracts.company.company_request import CompanyByOwnerEmail +from api.application.contracts.company.company_response import CompanyBaseResponse +from api.domain.company.repository import CompanyRepository + + +class GetCompaniesByOwnerEmail: + def __init__(self, company_repository: CompanyRepository) -> None: + self.company_repository = company_repository + + async def execute(self, request: CompanyByOwnerEmail) -> list[CompanyBaseResponse]: + companies = await self.company_repository.get_companies_by_owner_email(filter={"email": request.email}) + return [CompanyBaseResponse(name=comp.name.value, email=comp.email.value) for comp in companies] diff --git a/api/domain/company/model.py b/api/domain/company/model.py index 018f1b2..08dca89 100644 --- a/api/domain/company/model.py +++ b/api/domain/company/model.py @@ -4,7 +4,7 @@ from uuid import UUID, uuid4 from api.domain import DomainValidationError from api.domain.entity import DomainEntity -from api.domain.user.model import User, UserId +from api.domain.user.model import User from api.domain.value_obj import DomainValueObject @@ -16,9 +16,7 @@ class CompanyEmail(DomainValueObject): pattern = r"^[\w\.-]+@[a-zA-Z\d\.-]+\.[a-zA-Z]{2,}$" if not re.match(pattern, self.value): - raise DomainValidationError( - "Invalid email format. Email must be in the format 'example@example.com'." - ) + raise DomainValidationError("Invalid email format. Email must be in the format 'example@example.com'.") @dataclass(frozen=True) @@ -29,9 +27,7 @@ class CompanyName(DomainValueObject): if len(self.value) < 1: raise DomainValidationError("First name must be at least 1 character long.") if len(self.value) > 100: - raise DomainValidationError( - "First name must be at most 100 characters long." - ) + raise DomainValidationError("First name must be at most 100 characters long.") if not self.value.isalpha(): raise DomainValidationError("First name must only contain letters.") diff --git a/api/domain/company/repository.py b/api/domain/company/repository.py index 390dfd5..ffd0f54 100644 --- a/api/domain/company/repository.py +++ b/api/domain/company/repository.py @@ -5,11 +5,11 @@ from api.domain.user.model import User class CompanyRepository(Protocol): - async def get_company(self, filter: dict) -> User | None: + async def get_companies_by_owner_email(self, filter: dict) -> list[Company]: raise NotImplementedError async def create_company(self, company: Company) -> None: raise NotImplementedError - async def get_workes_list(self) -> list[User] | None: + async def get_workes_list(self) -> list[User]: raise NotImplementedError diff --git a/api/domain/user/error.py b/api/domain/user/error.py index ca8bc58..857d25c 100644 --- a/api/domain/user/error.py +++ b/api/domain/user/error.py @@ -2,20 +2,20 @@ from api.domain import DomainError class UserNotFoundError(DomainError): - ... + pass class UserValidationError(DomainError): - ... + pass class UserInvalidCredentialsError(DomainError): - ... + pass class UserAlreadyExistsError(DomainError): - ... + pass class UserIsNotAuthorizedError(DomainError): - ... + pass diff --git a/api/infrastructure/auth/jwt_processor.py b/api/infrastructure/auth/jwt_processor.py index 7ed460b..67b33ae 100644 --- a/api/infrastructure/auth/jwt_processor.py +++ b/api/infrastructure/auth/jwt_processor.py @@ -12,9 +12,7 @@ from api.infrastructure.auth.jwt_settings import JwtSettings class JoseJwtTokenProcessor(JwtTokenProcessor): - def __init__( - self, jwt_options: JwtSettings, date_time_provider: DateTimeProvider - ) -> None: + def __init__(self, jwt_options: JwtSettings, date_time_provider: DateTimeProvider) -> None: self.jwt_options = jwt_options self.date_time_provider = date_time_provider @@ -32,9 +30,7 @@ class JoseJwtTokenProcessor(JwtTokenProcessor): def validate_token(self, token: str) -> UserId | None: try: - payload = decode( - token, self.jwt_options.secret, [self.jwt_options.algorithm] - ) + payload = decode(token, self.jwt_options.secret, [self.jwt_options.algorithm]) return UserId(UUID(payload["sub"])) except (JWTError, ValueError, KeyError): return None diff --git a/api/infrastructure/persistence/models/base.py b/api/infrastructure/persistence/models/base.py index 21145b0..fa2b68a 100644 --- a/api/infrastructure/persistence/models/base.py +++ b/api/infrastructure/persistence/models/base.py @@ -2,4 +2,4 @@ from sqlalchemy.orm import DeclarativeBase class Base(DeclarativeBase): - ... + pass diff --git a/api/infrastructure/persistence/repositories/company_repository.py b/api/infrastructure/persistence/repositories/company_repository.py new file mode 100644 index 0000000..039d555 --- /dev/null +++ b/api/infrastructure/persistence/repositories/company_repository.py @@ -0,0 +1,45 @@ +# from sqlalchemy import text +# from sqlalchemy.ext.asyncio import AsyncSession +# +# from api.domain.company import CompanyRepository, company +# from api.domain.user.model import UserEmail, UserFirstName, UserId +# +# +# class SqlAlchemyUserRepository(UserRepository): +# def __init__(self, session: AsyncSession) -> None: +# self.session = session +# +# async def create_user(self, user: User) -> None: +# stmt = text( +# """INSERT INTO users (id, name, email, hashed_password) +# VALUES(:id, :name, :email, :hashed_password) +# """ +# ) +# await self.session.execute( +# stmt, +# { +# "id": str(user.id.value), +# "name": user.name.value, +# "email": user.email.value, +# "hashed_password": user.hashed_password, +# }, +# ) +# +# async def get_user(self, filter: dict) -> User | None: +# stmt = text("""SELECT * FROM users WHERE email = :val""") +# result = await self.session.execute(stmt, {"val": filter["email"]}) +# +# result = result.mappings().one_or_none() +# +# if result is None: +# return None +# +# return User( +# id=UserId(result.id), +# name=UserFirstName(result.name), +# email=UserEmail(result.email), +# hashed_password=result.hashed_password, +# ) +# +# async def get_users(self) -> list[User]: +# return [] diff --git a/api/presentation/auth/fasapi_auth.py b/api/presentation/auth/fasapi_auth.py index 0101bb2..2a68978 100644 --- a/api/presentation/auth/fasapi_auth.py +++ b/api/presentation/auth/fasapi_auth.py @@ -1,6 +1,6 @@ from typing import Annotated -from fastapi import Depends, HTTPException, Request, Response, status +from fastapi import Depends, HTTPException, Request, status from fastapi.openapi.models import OAuthFlows as OAuthFlowsModel from fastapi.security import OAuth2 from fastapi.security.utils import get_authorization_scheme_param @@ -13,7 +13,7 @@ from api.infrastructure.dependencies.stub import Stub class OAuth2PasswordBearerWithCookie(OAuth2): def __init__( self, - tokenUrl: str, + tokenUrl: str, # noqa scheme_name: str | None = None, scopes: dict[str, str] | None = None, auto_error: bool = True, diff --git a/api/presentation/routers/company.py b/api/presentation/routers/company.py index 4d7f038..e2a304b 100644 --- a/api/presentation/routers/company.py +++ b/api/presentation/routers/company.py @@ -1,7 +1,6 @@ from fastapi import APIRouter, Depends, Request -from api.application.contracts.company.company_response import \ - CompanyBaseResponse +from api.application.contracts.company.company_response import CompanyBaseResponse from api.presentation.auth.fasapi_auth import auth_required company_router = APIRouter(prefix="/company", tags=["Company"]) @@ -13,7 +12,6 @@ company_router = APIRouter(prefix="/company", tags=["Company"]) dependencies=[Depends(auth_required)], ) async def get_company(request: Request) -> CompanyBaseResponse: - print(request.scope["auth"]) return CompanyBaseResponse( name="some", email="some",