Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 19 additions & 12 deletions config/toml_config_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)


Expand Down
30 changes: 24 additions & 6 deletions src/app/application/common/ports/user_query_gateway.py
Original file line number Diff line number Diff line change
@@ -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:
"""
Empty file.
11 changes: 0 additions & 11 deletions src/app/application/common/query_models/user.py

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@


@dataclass(frozen=True, slots=True, kw_only=True)
class Pagination:
class OffsetPaginationParams:
"""
raises PaginationError
"""
Expand Down
7 changes: 7 additions & 0 deletions src/app/application/common/query_params/sorting.py
Original file line number Diff line number Diff line change
@@ -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
16 changes: 0 additions & 16 deletions src/app/application/common/query_params/user.py

This file was deleted.

54 changes: 18 additions & 36 deletions src/app/application/queries/list_users.py
Original file line number Diff line number Diff line change
@@ -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,
)
Expand All @@ -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.
Expand All @@ -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.")

Expand All @@ -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
3 changes: 3 additions & 0 deletions src/app/domain/entities/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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})"
2 changes: 1 addition & 1 deletion src/app/infrastructure/adapters/main_flusher_sqla.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand Down
2 changes: 1 addition & 1 deletion src/app/infrastructure/adapters/password_hasher_bcrypt.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand Down
27 changes: 8 additions & 19 deletions src/app/infrastructure/adapters/user_data_mapper_sqla.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from sqlalchemy import Select, select
from sqlalchemy import select
from sqlalchemy.exc import SQLAlchemyError

from app.application.common.ports.user_command_gateway import UserCommandGateway
from app.domain.entities.user import User
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


Expand All @@ -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

Expand All @@ -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

Expand All @@ -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
Loading