diff --git a/config/toml_config_manager.py b/config/toml_config_manager.py index cfb4572..e4ead1b 100644 --- a/config/toml_config_manager.py +++ b/config/toml_config_manager.py @@ -38,19 +38,26 @@ def validate_logging_level(*, level: str) -> LoggingLevel: raise ValueError(f"Invalid log level: '{level}'.") from err -def configure_logging(*, level: LoggingLevel = DEFAULT_LOG_LEVEL) -> None: - logging.getLogger().handlers.clear() - +FMT: Final[str] = ( + "[%(asctime)s.%(msecs)03d] " + "[%(threadName)s] " + "%(funcName)20s " + "%(module)s:%(lineno)d " + "%(levelname)-8s - " + "%(message)s" +) +DATEFMT: Final[str] = "%Y-%m-%d %H:%M:%S" + + +def configure_logging( + *, + level: LoggingLevel = DEFAULT_LOG_LEVEL, +) -> None: logging.basicConfig( - level=getattr(logging, level), - datefmt="%Y-%m-%d %H:%M:%S", - format=( - "[%(asctime)s.%(msecs)03d] " - "%(funcName)20s " - "%(module)s:%(lineno)d " - "%(levelname)-8s - " - "%(message)s" - ), + level=level, + datefmt=DATEFMT, + format=FMT, + force=True, ) diff --git a/src/app/application/common/ports/user_query_gateway.py b/src/app/application/common/ports/user_query_gateway.py index 01404be..0ca0985 100644 --- a/src/app/application/common/ports/user_query_gateway.py +++ b/src/app/application/common/ports/user_query_gateway.py @@ -1,14 +1,32 @@ from abc import abstractmethod -from typing import Protocol +from typing import Protocol, TypedDict +from uuid import UUID -from app.application.common.query_models.user import UserQueryModel -from app.application.common.query_params.user import UserListParams +from app.application.common.query_params.offset_pagination import OffsetPaginationParams +from app.application.common.query_params.sorting import SortingParams +from app.domain.enums.user_role import UserRole + + +class UserQueryModel(TypedDict): + id_: UUID + username: str + role: UserRole + is_active: bool + + +class ListUsersQM(TypedDict): + users: list[UserQueryModel] + total: int class UserQueryGateway(Protocol): @abstractmethod async def read_all( self, - user_read_all_params: UserListParams, - ) -> list[UserQueryModel] | None: - """:raises ReaderError:""" + pagination: OffsetPaginationParams, + sorting: SortingParams, + ) -> ListUsersQM: + """ + :raises SortingError: + :raises ReaderError: + """ diff --git a/src/app/application/common/query_models/__init__.py b/src/app/application/common/query_models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/application/common/query_models/user.py b/src/app/application/common/query_models/user.py deleted file mode 100644 index a45d7a9..0000000 --- a/src/app/application/common/query_models/user.py +++ /dev/null @@ -1,11 +0,0 @@ -from typing import TypedDict -from uuid import UUID - -from app.domain.enums.user_role import UserRole - - -class UserQueryModel(TypedDict): - id_: UUID - username: str - role: UserRole - is_active: bool diff --git a/src/app/application/common/query_params/pagination.py b/src/app/application/common/query_params/offset_pagination.py similarity index 94% rename from src/app/application/common/query_params/pagination.py rename to src/app/application/common/query_params/offset_pagination.py index c19ccc5..aee98c0 100644 --- a/src/app/application/common/query_params/pagination.py +++ b/src/app/application/common/query_params/offset_pagination.py @@ -4,7 +4,7 @@ @dataclass(frozen=True, slots=True, kw_only=True) -class Pagination: +class OffsetPaginationParams: """ raises PaginationError """ diff --git a/src/app/application/common/query_params/sorting.py b/src/app/application/common/query_params/sorting.py index 7573c5f..4dc5ec8 100644 --- a/src/app/application/common/query_params/sorting.py +++ b/src/app/application/common/query_params/sorting.py @@ -1,6 +1,13 @@ +from dataclasses import dataclass from enum import StrEnum class SortingOrder(StrEnum): ASC = "ASC" DESC = "DESC" + + +@dataclass(frozen=True, slots=True, kw_only=True) +class SortingParams: + field: str + order: SortingOrder diff --git a/src/app/application/common/query_params/user.py b/src/app/application/common/query_params/user.py deleted file mode 100644 index b8de3cb..0000000 --- a/src/app/application/common/query_params/user.py +++ /dev/null @@ -1,16 +0,0 @@ -from dataclasses import dataclass - -from app.application.common.query_params.pagination import Pagination -from app.application.common.query_params.sorting import SortingOrder - - -@dataclass(frozen=True, slots=True, kw_only=True) -class UserListSorting: - sorting_field: str - sorting_order: SortingOrder - - -@dataclass(frozen=True, slots=True) -class UserListParams: - pagination: Pagination - sorting: UserListSorting diff --git a/src/app/application/queries/list_users.py b/src/app/application/queries/list_users.py index 2857a01..8efe542 100644 --- a/src/app/application/queries/list_users.py +++ b/src/app/application/queries/list_users.py @@ -1,16 +1,12 @@ import logging from dataclasses import dataclass -from typing import TypedDict -from app.application.common.exceptions.query import SortingError -from app.application.common.ports.user_query_gateway import UserQueryGateway -from app.application.common.query_models.user import UserQueryModel -from app.application.common.query_params.pagination import Pagination -from app.application.common.query_params.sorting import SortingOrder -from app.application.common.query_params.user import ( - UserListParams, - UserListSorting, +from app.application.common.ports.user_query_gateway import ( + ListUsersQM, + UserQueryGateway, ) +from app.application.common.query_params.offset_pagination import OffsetPaginationParams +from app.application.common.query_params.sorting import SortingOrder, SortingParams from app.application.common.services.authorization.authorize import ( authorize, ) @@ -26,16 +22,12 @@ @dataclass(frozen=True, slots=True, kw_only=True) class ListUsersRequest: - limit: int offset: int + limit: int sorting_field: str sorting_order: SortingOrder -class ListUsersResponse(TypedDict): - users: list[UserQueryModel] - - class ListUsersQueryService: """ - Open to admins. @@ -50,14 +42,14 @@ def __init__( self._current_user_service = current_user_service self._user_query_gateway = user_query_gateway - async def execute(self, request_data: ListUsersRequest) -> ListUsersResponse: + async def execute(self, request_data: ListUsersRequest) -> ListUsersQM: """ :raises AuthenticationError: :raises DataMapperError: :raises AuthorizationError: - :raises ReaderError: :raises PaginationError: :raises SortingError: + :raises ReaderError: """ log.info("List users: started.") @@ -72,28 +64,18 @@ async def execute(self, request_data: ListUsersRequest) -> ListUsersResponse: ) log.debug("Retrieving list of users.") - user_list_params = UserListParams( - pagination=Pagination( - limit=request_data.limit, - offset=request_data.offset, - ), - sorting=UserListSorting( - sorting_field=request_data.sorting_field, - sorting_order=request_data.sorting_order, - ), + pagination = OffsetPaginationParams( + limit=request_data.limit, + offset=request_data.offset, ) - - users: list[UserQueryModel] | None = await self._user_query_gateway.read_all( - user_list_params, + sorting = SortingParams( + field=request_data.sorting_field, + order=request_data.sorting_order, + ) + response = await self._user_query_gateway.read_all( + pagination=pagination, + sorting=sorting, ) - if users is None: - log.error( - "Retrieving list of users failed: invalid sorting column '%s'.", - request_data.sorting_field, - ) - raise SortingError("Invalid sorting field.") - - response = ListUsersResponse(users=users) log.info("List users: done.") return response diff --git a/src/app/domain/entities/base.py b/src/app/domain/entities/base.py index 179d78e..1eb8aa4 100644 --- a/src/app/domain/entities/base.py +++ b/src/app/domain/entities/base.py @@ -42,3 +42,6 @@ def __hash__(self) -> int: reduces the risk of hash collisions between different entity types. """ return hash((type(self), self.id_)) + + def __repr__(self) -> str: + return f"{type(self).__name__}(id_={self.id_!r})" diff --git a/src/app/infrastructure/adapters/main_flusher_sqla.py b/src/app/infrastructure/adapters/main_flusher_sqla.py index 8b997af..62447e1 100644 --- a/src/app/infrastructure/adapters/main_flusher_sqla.py +++ b/src/app/infrastructure/adapters/main_flusher_sqla.py @@ -12,7 +12,7 @@ DB_FLUSH_FAILED, DB_QUERY_FAILED, ) -from app.infrastructure.adapters.types import MainAsyncSession +from app.infrastructure.adapters.types_ import MainAsyncSession from app.infrastructure.exceptions.gateway import DataMapperError log = logging.getLogger(__name__) diff --git a/src/app/infrastructure/adapters/main_transaction_manager_sqla.py b/src/app/infrastructure/adapters/main_transaction_manager_sqla.py index 4fd1f7d..def768e 100644 --- a/src/app/infrastructure/adapters/main_transaction_manager_sqla.py +++ b/src/app/infrastructure/adapters/main_transaction_manager_sqla.py @@ -10,7 +10,7 @@ DB_COMMIT_FAILED, DB_QUERY_FAILED, ) -from app.infrastructure.adapters.types import MainAsyncSession +from app.infrastructure.adapters.types_ import MainAsyncSession from app.infrastructure.exceptions.gateway import DataMapperError log = logging.getLogger(__name__) diff --git a/src/app/infrastructure/adapters/password_hasher_bcrypt.py b/src/app/infrastructure/adapters/password_hasher_bcrypt.py index 01585b0..20d0107 100644 --- a/src/app/infrastructure/adapters/password_hasher_bcrypt.py +++ b/src/app/infrastructure/adapters/password_hasher_bcrypt.py @@ -11,7 +11,7 @@ from app.domain.ports.password_hasher import PasswordHasher from app.domain.value_objects.raw_password import RawPassword from app.domain.value_objects.user_password_hash import UserPasswordHash -from app.infrastructure.adapters.types import HasherSemaphore, HasherThreadPoolExecutor +from app.infrastructure.adapters.types_ import HasherSemaphore, HasherThreadPoolExecutor from app.infrastructure.exceptions.password_hasher import PasswordHasherBusyError log = logging.getLogger(__name__) diff --git a/src/app/infrastructure/adapters/types.py b/src/app/infrastructure/adapters/types_.py similarity index 100% rename from src/app/infrastructure/adapters/types.py rename to src/app/infrastructure/adapters/types_.py diff --git a/src/app/infrastructure/adapters/user_data_mapper_sqla.py b/src/app/infrastructure/adapters/user_data_mapper_sqla.py index b7e4846..d0008ef 100644 --- a/src/app/infrastructure/adapters/user_data_mapper_sqla.py +++ b/src/app/infrastructure/adapters/user_data_mapper_sqla.py @@ -1,4 +1,4 @@ -from sqlalchemy import Select, select +from sqlalchemy import select from sqlalchemy.exc import SQLAlchemyError from app.application.common.ports.user_command_gateway import UserCommandGateway @@ -6,7 +6,7 @@ from app.domain.value_objects.user_id import UserId from app.domain.value_objects.username import Username from app.infrastructure.adapters.constants import DB_QUERY_FAILED -from app.infrastructure.adapters.types import MainAsyncSession +from app.infrastructure.adapters.types_ import MainAsyncSession from app.infrastructure.exceptions.gateway import DataMapperError @@ -18,7 +18,6 @@ def add(self, user: User) -> None: """:raises DataMapperError:""" try: self._session.add(user) - except SQLAlchemyError as err: raise DataMapperError(DB_QUERY_FAILED) from err @@ -28,18 +27,13 @@ async def read_by_id( for_update: bool = False, ) -> User | None: """:raises DataMapperError:""" - select_stmt: Select[tuple[User]] = select(User).where(User.id_ == user_id) # type: ignore + stmt = select(User).where(User.id_ == user_id) # type: ignore if for_update: - select_stmt = select_stmt.with_for_update() + stmt = stmt.with_for_update() try: - user: User | None = ( - await self._session.execute(select_stmt) - ).scalar_one_or_none() - - return user - + return (await self._session.execute(stmt)).scalar_one_or_none() except SQLAlchemyError as err: raise DataMapperError(DB_QUERY_FAILED) from err @@ -49,17 +43,12 @@ async def read_by_username( for_update: bool = False, ) -> User | None: """:raises DataMapperError:""" - select_stmt: Select[tuple[User]] = select(User).where(User.username == username) # type: ignore + stmt = select(User).where(User.username == username) # type: ignore if for_update: - select_stmt = select_stmt.with_for_update() + stmt = stmt.with_for_update() try: - user: User | None = ( - await self._session.execute(select_stmt) - ).scalar_one_or_none() - - return user - + return (await self._session.execute(stmt)).scalar_one_or_none() except SQLAlchemyError as err: raise DataMapperError(DB_QUERY_FAILED) from err diff --git a/src/app/infrastructure/adapters/user_reader_sqla.py b/src/app/infrastructure/adapters/user_reader_sqla.py index bef0e3e..b12427e 100644 --- a/src/app/infrastructure/adapters/user_reader_sqla.py +++ b/src/app/infrastructure/adapters/user_reader_sqla.py @@ -1,17 +1,18 @@ import logging -from collections.abc import Sequence -from uuid import UUID -from sqlalchemy import ColumnElement, Result, Row, Select, select +from sqlalchemy import func, select from sqlalchemy.exc import SQLAlchemyError -from app.application.common.ports.user_query_gateway import UserQueryGateway -from app.application.common.query_models.user import UserQueryModel -from app.application.common.query_params.sorting import SortingOrder -from app.application.common.query_params.user import UserListParams -from app.domain.enums.user_role import UserRole +from app.application.common.exceptions.query import SortingError +from app.application.common.ports.user_query_gateway import ( + ListUsersQM, + UserQueryGateway, + UserQueryModel, +) +from app.application.common.query_params.offset_pagination import OffsetPaginationParams +from app.application.common.query_params.sorting import SortingOrder, SortingParams from app.infrastructure.adapters.constants import DB_QUERY_FAILED -from app.infrastructure.adapters.types import MainAsyncSession +from app.infrastructure.adapters.types_ import MainAsyncSession from app.infrastructure.exceptions.gateway import ReaderError from app.infrastructure.persistence_sqla.mappings.user import users_table @@ -24,52 +25,53 @@ def __init__(self, session: MainAsyncSession) -> None: async def read_all( self, - user_read_all_params: UserListParams, - ) -> list[UserQueryModel] | None: - """:raises ReaderError:""" - table_sorting_field: ColumnElement[UUID | str | UserRole | bool] | None = ( - users_table.c.get(user_read_all_params.sorting.sorting_field) - ) - if table_sorting_field is None: - log.error( - "Invalid sorting field: '%s'.", - user_read_all_params.sorting.sorting_field, - ) - return None + pagination: OffsetPaginationParams, + sorting: SortingParams, + ) -> ListUsersQM: + """ + :raises SortingError: + :raises ReaderError: + """ + sorting_col = users_table.c.get(sorting.field) + if sorting_col is None: + raise SortingError(f"Invalid sorting field: '{sorting.field}'") - order_by: ColumnElement[UUID | str | UserRole | bool] = ( - table_sorting_field.asc() - if user_read_all_params.sorting.sorting_order == SortingOrder.ASC - else table_sorting_field.desc() + order_by = ( + sorting_col.asc() + if sorting.order == SortingOrder.ASC + else sorting_col.desc() ) - select_stmt: Select[tuple[UUID, str, UserRole, bool]] = ( + stmt = ( select( users_table.c.id, users_table.c.username, users_table.c.role, users_table.c.is_active, + func.count().over().label("total"), ) .order_by(order_by) - .limit(user_read_all_params.pagination.limit) - .offset(user_read_all_params.pagination.offset) + .limit(pagination.limit) + .offset(pagination.offset) ) try: - result: Result[ - tuple[UUID, str, UserRole, bool] - ] = await self._session.execute(select_stmt) - rows: Sequence[Row[tuple[UUID, str, UserRole, bool]]] = result.all() - - return [ - UserQueryModel( - id_=row.id, - username=row.username, - role=row.role, - is_active=row.is_active, - ) - for row in rows - ] - + result = await self._session.execute(stmt) + rows = result.all() except SQLAlchemyError as err: raise ReaderError(DB_QUERY_FAILED) from err + + if not rows: + return ListUsersQM(users=[], total=0) + + users = [ + UserQueryModel( + id_=row.id, + username=row.username, + role=row.role, + is_active=row.is_active, + ) + for row in rows + ] + total = rows[0].total + return ListUsersQM(users=users, total=total) diff --git a/src/app/infrastructure/auth/adapters/data_mapper_sqla.py b/src/app/infrastructure/auth/adapters/data_mapper_sqla.py index 2a4e3be..4e59f1f 100644 --- a/src/app/infrastructure/auth/adapters/data_mapper_sqla.py +++ b/src/app/infrastructure/auth/adapters/data_mapper_sqla.py @@ -1,9 +1,9 @@ -from sqlalchemy import Delete, delete +from sqlalchemy import delete from sqlalchemy.exc import SQLAlchemyError from app.domain.value_objects.user_id import UserId from app.infrastructure.adapters.constants import DB_QUERY_FAILED -from app.infrastructure.auth.adapters.types import AuthAsyncSession +from app.infrastructure.auth.adapters.types_ import AuthAsyncSession from app.infrastructure.auth.session.model import AuthSession from app.infrastructure.auth.session.ports.gateway import ( AuthSessionGateway, @@ -19,7 +19,6 @@ def add(self, auth_session: AuthSession) -> None: """:raises DataMapperError:""" try: self._session.add(auth_session) - except SQLAlchemyError as err: raise DataMapperError(DB_QUERY_FAILED) from err @@ -30,14 +29,11 @@ async def read_by_id( ) -> AuthSession | None: """:raises DataMapperError:""" try: - auth_session: AuthSession | None = await self._session.get( + return await self._session.get( AuthSession, auth_session_id, with_for_update=for_update, ) - - return auth_session - except SQLAlchemyError as err: raise DataMapperError(DB_QUERY_FAILED) from err @@ -45,30 +41,21 @@ async def update(self, auth_session: AuthSession) -> None: """:raises DataMapperError:""" try: await self._session.merge(auth_session) - except SQLAlchemyError as err: raise DataMapperError(DB_QUERY_FAILED) from err async def delete(self, auth_session_id: str) -> None: """:raises DataMapperError:""" - delete_stmt: Delete = delete(AuthSession).where( - AuthSession.id_ == auth_session_id, # type: ignore - ) - + stmt = delete(AuthSession).where(AuthSession.id_ == auth_session_id) # type: ignore try: - await self._session.execute(delete_stmt) - + await self._session.execute(stmt) except SQLAlchemyError as err: raise DataMapperError(DB_QUERY_FAILED) from err async def delete_all_for_user(self, user_id: UserId) -> None: """:raises DataMapperError:""" - delete_stmt: Delete = delete(AuthSession).where( - AuthSession.user_id == user_id, # type: ignore - ) - + stmt = delete(AuthSession).where(AuthSession.user_id == user_id) # type: ignore try: - await self._session.execute(delete_stmt) - + await self._session.execute(stmt) except SQLAlchemyError as err: raise DataMapperError(DB_QUERY_FAILED) from err diff --git a/src/app/infrastructure/auth/adapters/transaction_manager_sqla.py b/src/app/infrastructure/auth/adapters/transaction_manager_sqla.py index 2e2c7d2..4927967 100644 --- a/src/app/infrastructure/auth/adapters/transaction_manager_sqla.py +++ b/src/app/infrastructure/auth/adapters/transaction_manager_sqla.py @@ -7,7 +7,7 @@ DB_COMMIT_FAILED, DB_QUERY_FAILED, ) -from app.infrastructure.auth.adapters.types import AuthAsyncSession +from app.infrastructure.auth.adapters.types_ import AuthAsyncSession from app.infrastructure.auth.session.ports.transaction_manager import ( AuthSessionTransactionManager, ) diff --git a/src/app/infrastructure/auth/adapters/types.py b/src/app/infrastructure/auth/adapters/types_.py similarity index 100% rename from src/app/infrastructure/auth/adapters/types.py rename to src/app/infrastructure/auth/adapters/types_.py diff --git a/src/app/presentation/http/controllers/users/list_users.py b/src/app/presentation/http/controllers/users/list_users.py index 420029a..65e5403 100644 --- a/src/app/presentation/http/controllers/users/list_users.py +++ b/src/app/presentation/http/controllers/users/list_users.py @@ -9,11 +9,11 @@ from app.application.common.exceptions.authorization import AuthorizationError from app.application.common.exceptions.query import PaginationError, SortingError +from app.application.common.ports.user_query_gateway import ListUsersQM from app.application.common.query_params.sorting import SortingOrder from app.application.queries.list_users import ( ListUsersQueryService, ListUsersRequest, - ListUsersResponse, ) from app.infrastructure.auth.exceptions import AuthenticationError from app.infrastructure.exceptions.gateway import DataMapperError, ReaderError @@ -52,13 +52,13 @@ def create_list_users_router() -> APIRouter: on_error=log_error, ), AuthorizationError: status.HTTP_403_FORBIDDEN, + PaginationError: status.HTTP_400_BAD_REQUEST, + SortingError: status.HTTP_400_BAD_REQUEST, ReaderError: rule( status=status.HTTP_503_SERVICE_UNAVAILABLE, translator=ServiceUnavailableTranslator(), on_error=log_error, ), - PaginationError: status.HTTP_400_BAD_REQUEST, - SortingError: status.HTTP_400_BAD_REQUEST, }, default_on_error=log_info, status_code=status.HTTP_200_OK, @@ -68,7 +68,7 @@ def create_list_users_router() -> APIRouter: async def list_users( request_data_pydantic: Annotated[ListUsersRequestPydantic, Depends()], interactor: FromDishka[ListUsersQueryService], - ) -> ListUsersResponse: + ) -> ListUsersQM: request_data = ListUsersRequest( limit=request_data_pydantic.limit, offset=request_data_pydantic.offset, diff --git a/src/app/setup/app_factory.py b/src/app/setup/app_factory.py index 6cdba93..9cac5f5 100644 --- a/src/app/setup/app_factory.py +++ b/src/app/setup/app_factory.py @@ -40,6 +40,6 @@ def create_web_app() -> FastAPI: @asynccontextmanager async def lifespan(app: FastAPI) -> AsyncIterator[None]: map_tables() - yield None + yield # https://dishka.readthedocs.io/en/stable/integrations/fastapi.html await app.state.dishka_container.close() diff --git a/src/app/setup/config/loader.py b/src/app/setup/config/loader.py index c8af741..d0349b1 100644 --- a/src/app/setup/config/loader.py +++ b/src/app/setup/config/loader.py @@ -35,7 +35,7 @@ class DirContents(StrEnum): DOTENV_NAME = ".env" -BASE_DIR_PATH = Path(__file__).resolve().parents[4] +BASE_DIR_PATH: Final[Path] = Path(__file__).resolve().parents[4] CONFIG_PATH: Final[Path] = BASE_DIR_PATH / "config" ENV_TO_DIR_PATHS: Final[Mapping[ValidEnvs, Path]] = MappingProxyType({ diff --git a/src/app/setup/config/logs.py b/src/app/setup/config/logs.py index 41fc6f2..90cfcc3 100644 --- a/src/app/setup/config/logs.py +++ b/src/app/setup/config/logs.py @@ -15,21 +15,26 @@ class LoggingLevel(StrEnum): DEFAULT_LOG_LEVEL: Final[LoggingLevel] = LoggingLevel.INFO - -def configure_logging(*, level: LoggingLevel = DEFAULT_LOG_LEVEL) -> None: - logging.getLogger().handlers.clear() - +FMT: Final[str] = ( + "[%(asctime)s.%(msecs)03d] " + "[%(threadName)s] " + "%(funcName)20s " + "%(module)s:%(lineno)d " + "%(levelname)-8s - " + "%(message)s" +) +DATEFMT: Final[str] = "%Y-%m-%d %H:%M:%S" + + +def configure_logging( + *, + level: LoggingLevel = DEFAULT_LOG_LEVEL, +) -> None: logging.basicConfig( - level=getattr(logging, level), - datefmt="%Y-%m-%d %H:%M:%S", - format=( - "[%(asctime)s.%(msecs)03d] " - "[%(threadName)s] " - "%(funcName)20s " - "%(module)s:%(lineno)d " - "%(levelname)-8s - " - "%(message)s" - ), + level=level, + datefmt=DATEFMT, + format=FMT, + force=True, ) diff --git a/src/app/setup/ioc/domain.py b/src/app/setup/ioc/domain.py index 8042fe7..a6835bb 100644 --- a/src/app/setup/ioc/domain.py +++ b/src/app/setup/ioc/domain.py @@ -6,7 +6,7 @@ from app.infrastructure.adapters.password_hasher_bcrypt import ( BcryptPasswordHasher, ) -from app.infrastructure.adapters.types import HasherSemaphore, HasherThreadPoolExecutor +from app.infrastructure.adapters.types_ import HasherSemaphore, HasherThreadPoolExecutor from app.infrastructure.adapters.user_id_generator_uuid import ( UuidUserIdGenerator, ) diff --git a/src/app/setup/ioc/infrastructure.py b/src/app/setup/ioc/infrastructure.py index 96ace96..9fc2980 100644 --- a/src/app/setup/ioc/infrastructure.py +++ b/src/app/setup/ioc/infrastructure.py @@ -12,7 +12,7 @@ create_async_engine, ) -from app.infrastructure.adapters.types import ( +from app.infrastructure.adapters.types_ import ( HasherSemaphore, HasherThreadPoolExecutor, MainAsyncSession, @@ -23,7 +23,7 @@ from app.infrastructure.auth.adapters.transaction_manager_sqla import ( SqlaAuthSessionTransactionManager, ) -from app.infrastructure.auth.adapters.types import AuthAsyncSession +from app.infrastructure.auth.adapters.types_ import AuthAsyncSession from app.infrastructure.auth.handlers.change_password import ( ChangePasswordHandler, ) diff --git a/tests/app/unit/domain/entities/test_base.py b/tests/app/unit/domain/entities/test_base.py index 02d13ff..933bd98 100644 --- a/tests/app/unit/domain/entities/test_base.py +++ b/tests/app/unit/domain/entities/test_base.py @@ -102,3 +102,11 @@ def test_entity_can_be_used_in_set() -> None: entity_set = {e1, e2, e3, e4} assert len(entity_set) == 3 + + +def test_entity_repr_shows_id() -> None: + sut = create_named_entity(id_=1, name="Alice") + + repr_str = repr(sut) + + assert repr_str == "NamedEntity(id_=NamedEntityId(1))" diff --git a/tests/app/unit/infrastructure/conftest.py b/tests/app/unit/infrastructure/conftest.py index 0d901ba..407b65c 100644 --- a/tests/app/unit/infrastructure/conftest.py +++ b/tests/app/unit/infrastructure/conftest.py @@ -6,7 +6,7 @@ import pytest from app.infrastructure.adapters.password_hasher_bcrypt import BcryptPasswordHasher -from app.infrastructure.adapters.types import HasherSemaphore, HasherThreadPoolExecutor +from app.infrastructure.adapters.types_ import HasherSemaphore, HasherThreadPoolExecutor @pytest.fixture(scope="session")