From dd076686169bd645774662fe47b6b790371d20dd Mon Sep 17 00:00:00 2001 From: SamWarden Date: Mon, 14 Apr 2025 14:25:01 +0300 Subject: [PATCH 01/20] refactor(log): improve error logs --- src/infrastructure/log/config.py | 2 +- src/infrastructure/log/main.py | 3 +-- src/infrastructure/log/processors.py | 7 ++++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/infrastructure/log/config.py b/src/infrastructure/log/config.py index fde5900..f674298 100644 --- a/src/infrastructure/log/config.py +++ b/src/infrastructure/log/config.py @@ -2,7 +2,7 @@ from pathlib import Path -@dataclass +@dataclass(frozen=True) class LoggingConfig: render_json_logs: bool = False path: Path | None = None diff --git a/src/infrastructure/log/main.py b/src/infrastructure/log/main.py index 8f77ada..9ad9a34 100644 --- a/src/infrastructure/log/main.py +++ b/src/infrastructure/log/main.py @@ -19,7 +19,7 @@ def configure_logging(cfg: LoggingConfig) -> None: structlog.dev.set_exc_info, structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S.%f", utc=True), structlog.contextvars.merge_contextvars, - structlog.processors.dict_tracebacks, + structlog.processors.format_exc_info, # print exceptions from event dict CallsiteParameterAdder( ( CallsiteParameter.FUNC_NAME, @@ -33,7 +33,6 @@ def configure_logging(cfg: LoggingConfig) -> None: structlog.processors.UnicodeDecoder(), # convert bytes to str # structlog.stdlib.render_to_log_kwargs, structlog.stdlib.ProcessorFormatter.wrap_for_formatter, - # structlog.processors.format_exc_info, # print exceptions from event dict ) logging_processors = (structlog.stdlib.ProcessorFormatter.remove_processors_meta,) logging_console_processors = ( diff --git a/src/infrastructure/log/processors.py b/src/infrastructure/log/processors.py index 78b65d8..ce66396 100644 --- a/src/infrastructure/log/processors.py +++ b/src/infrastructure/log/processors.py @@ -1,3 +1,4 @@ +import logging from collections.abc import Callable from typing import Any from uuid import UUID @@ -6,6 +7,8 @@ import orjson import structlog +logger = logging.getLogger(__name__) + ProcessorType = Callable[ [ structlog.types.WrappedLogger, @@ -21,7 +24,9 @@ def additionally_serialize(obj: object) -> Any: return str(obj) if isinstance(obj, aio_pika.Message): return obj.info() - raise TypeError(f"TypeError: Type is not JSON serializable: {type(obj)}") + + logger.warning("Type is not JSON serializable: %s", type(obj), extra={"obj": repr(obj)}) + return repr(obj) def serialize_to_json(data: Any, default: Any) -> str: From 3bf3ec94d92b7fab69860fe6b112e87b832c9697 Mon Sep 17 00:00:00 2001 From: SamWarden Date: Mon, 14 Apr 2025 14:26:14 +0300 Subject: [PATCH 02/20] refactor(api): remove `"/"` as path for an endpoint --- src/presentation/api/controllers/user.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/presentation/api/controllers/user.py b/src/presentation/api/controllers/user.py index 2d93d60..adddc0a 100644 --- a/src/presentation/api/controllers/user.py +++ b/src/presentation/api/controllers/user.py @@ -78,9 +78,7 @@ async def get_user_by_id( return OkResponse(result=user) -@user_router.get( - "", -) +@user_router.get("") async def get_users( mediator: Annotated[QueryMediator, Depends(Stub(QueryMediator))], deleted: bool | None = None, From a9380da3a8c8b1cc73cd92ebcca40549b7abea99 Mon Sep 17 00:00:00 2001 From: SamWarden Date: Mon, 14 Apr 2025 14:31:40 +0300 Subject: [PATCH 03/20] feat(api): add the `wrap_factory` function --- src/presentation/api/providers/stub.py | 30 +++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/presentation/api/providers/stub.py b/src/presentation/api/providers/stub.py index 7e19106..c06115a 100644 --- a/src/presentation/api/providers/stub.py +++ b/src/presentation/api/providers/stub.py @@ -1,5 +1,8 @@ +import inspect from collections.abc import Callable, Hashable -from typing import Never +from typing import Annotated, Any, Never, ParamSpec, TypeVar + +from fastapi import Depends class Stub: @@ -25,3 +28,28 @@ def __hash__(self) -> int: *self._kwargs.items(), ) return hash(serial) + + +P = ParamSpec("P") +T = TypeVar("T") + + +def wrap_factory(factory: Callable[P, T], **kwargs: Callable[..., Any]) -> Callable[P, T]: + def provider(*args: P.args, **kwargs: P.kwargs) -> T: + return factory(*args, **kwargs) + + factory_sig = inspect.signature(factory) + new_params: list[inspect.Parameter] = [] + for param in factory_sig.parameters.values(): + if param.name in kwargs: + stub = kwargs[param.annotation] + else: + stub = Stub(param.annotation) + new_params.append(param.replace(annotation=Annotated[param.annotation, Depends(stub)])) + + provider_sig = inspect.signature(provider) + new_sig = provider_sig.replace(parameters=new_params, return_annotation=factory_sig.return_annotation) + new_hints = {param.name: param.annotation for param in new_params} + provider.__signature__ = new_sig # type: ignore[attr-defined] + provider.__annotations__ = new_hints + return provider From cc98797e03a05339bff3b995afc2b47a4274b69e Mon Sep 17 00:00:00 2001 From: SamWarden Date: Mon, 14 Apr 2025 14:34:05 +0300 Subject: [PATCH 04/20] refactor(app): use the src-layout --- Dockerfile | 4 +- src/{ => user_service}/__init__.py | 0 src/{ => user_service}/__main__.py | 2 +- .../application/__init__.py | 0 .../application/common/__init__.py | 0 .../application/common/command.py | 0 .../application/common/dto.py | 0 .../application/common/event.py | 2 +- .../application/common/exceptions.py | 2 +- .../application/common/interfaces/__init__.py | 0 .../application/common/interfaces/uow.py | 0 .../application/common/pagination/__init__.py | 0 .../application/common/pagination/dto.py | 4 +- .../application/common/query.py | 0 .../application/user/__init__.py | 0 .../application/user/commands/__init__.py | 0 .../application/user/commands/create_user.py | 10 ++--- .../application/user/commands/delete_user.py | 8 ++-- .../user/commands/set_user_full_name.py | 10 ++--- .../user/commands/set_user_username.py | 8 ++-- .../application/user/dto/__init__.py | 0 .../application/user/dto/deleted_user.py | 2 +- .../application/user/dto/user.py | 2 +- .../application/user/dto/users.py | 2 +- .../application/user/events/__init__.py | 0 .../application/user/exceptions.py | 2 +- .../application/user/interfaces/__init__.py | 0 .../user/interfaces/persistence/__init__.py | 0 .../user/interfaces/persistence/reader.py | 6 +-- .../user/interfaces/persistence/repo.py | 4 +- .../application/user/queries/__init__.py | 0 .../user/queries/get_user_by_id.py | 6 +-- .../user/queries/get_user_by_username.py | 6 +-- .../application/user/queries/get_users.py | 10 ++--- src/{ => user_service}/domain/__init__.py | 0 .../domain/common/__init__.py | 0 .../domain/common/constants.py | 0 .../domain/common/entities/__init__.py | 0 .../domain/common/entities/aggregate_root.py | 2 +- .../domain/common/entities/entity.py | 0 .../domain/common/events/__init__.py | 0 .../domain/common/events/event.py | 0 .../domain/common/exceptions/__init__.py | 0 .../domain/common/exceptions/base.py | 0 .../domain/common/value_objects/__init__.py | 0 .../domain/common/value_objects/base.py | 0 .../domain/user/__init__.py | 0 .../domain/user/entities/__init__.py | 0 .../domain/user/entities/user.py | 16 ++++---- .../domain/user/events/__init__.py | 0 .../domain/user/events/full_name_updated.py | 2 +- .../domain/user/events/user_created.py | 2 +- .../domain/user/events/user_deleted.py | 2 +- .../domain/user/events/username_updated.py | 2 +- .../domain/user/exceptions.py | 2 +- .../domain/user/value_objects/__init__.py | 0 .../user/value_objects/deleted_status.py | 2 +- .../domain/user/value_objects/full_name.py | 4 +- .../domain/user/value_objects/user_id.py | 2 +- .../domain/user/value_objects/username.py | 4 +- .../infrastructure/__init__.py | 0 .../infrastructure/config_loader.py | 0 .../infrastructure/db/__init__.py | 0 .../infrastructure/db/config.py | 0 .../infrastructure/db/converters.py | 12 +++--- .../infrastructure/db/exception_mapper.py | 2 +- .../infrastructure/db/main.py | 0 .../infrastructure/db/migrations/README | 0 .../infrastructure/db/migrations/__init__.py | 0 .../infrastructure/db/migrations/env.py | 6 +-- .../db/migrations/script.py.mako | 0 ...204-105454_2d79505fb3d2_uuid_generation.py | 0 ...20230204-110323_f78150d890d1_user_table.py | 0 ...0_add_middle_name_column_to_users_table.py | 0 ...dd_deleted_at_column_to_the_users_table.py | 0 .../db/migrations/versions/__init__.py | 0 .../infrastructure/db/models/__init__.py | 0 .../infrastructure/db/models/base.py | 0 .../infrastructure/db/models/user.py | 0 .../db/repositories/__init__.py | 0 .../infrastructure/db/repositories/base.py | 0 .../infrastructure/db/repositories/user.py | 30 ++++++++------- .../infrastructure/db/uow.py | 4 +- .../infrastructure/di/__init__.py | 0 .../infrastructure/di/constants.py | 0 .../infrastructure/di/main.py | 22 +++++------ .../infrastructure/event_bus/__init__.py | 0 .../infrastructure/event_bus/converters.py | 6 +-- .../infrastructure/event_bus/event_bus.py | 4 +- .../infrastructure/event_bus/event_handler.py | 10 ++--- .../event_bus/events/__init__.py | 0 .../infrastructure/event_bus/events/base.py | 0 .../event_bus/events/full_name_updated.py | 2 +- .../event_bus/events/user_created.py | 2 +- .../event_bus/events/user_deleted.py | 2 +- .../event_bus/events/username_updated.py | 2 +- .../infrastructure/event_bus/exchanges.py | 2 +- .../infrastructure/log/__init__.py | 0 .../infrastructure/log/config.py | 0 .../infrastructure/log/event_handler.py | 4 +- .../infrastructure/log/main.py | 0 .../infrastructure/log/processors.py | 0 .../infrastructure/mediator/__init__.py | 0 .../infrastructure/mediator/main.py | 16 ++++---- .../infrastructure/mediator/utils.py | 0 .../infrastructure/message_broker/__init__.py | 0 .../infrastructure/message_broker/config.py | 0 .../message_broker/factories.py | 0 .../message_broker/interface.py | 0 .../infrastructure/message_broker/main.py | 2 +- .../infrastructure/message_broker/message.py | 0 .../message_broker/message_broker.py | 0 .../infrastructure/message_broker/uow.py | 4 +- src/{ => user_service}/infrastructure/uow.py | 6 +-- .../presentation/__init__.py | 0 .../presentation/api/__init__.py | 0 .../presentation/api/__main__.py | 12 +++--- .../presentation/api/config.py | 8 ++-- .../presentation/api/controllers/__init__.py | 0 .../presentation/api/controllers/default.py | 0 .../api/controllers/exceptions.py | 20 ++++++---- .../api/controllers/healthcheck.py | 0 .../presentation/api/controllers/main.py | 0 .../api/controllers/requests/__init__.py | 0 .../api/controllers/requests/user.py | 0 .../api/controllers/responses/__init__.py | 0 .../api/controllers/responses/base.py | 0 .../api/controllers/responses/orjson.py | 0 .../presentation/api/controllers/user.py | 38 +++++++++++-------- .../presentation/api/main.py | 6 +-- .../presentation/api/middlewares/__init__.py | 0 .../presentation/api/middlewares/context.py | 0 .../presentation/api/middlewares/main.py | 0 .../presentation/api/middlewares/structlog.py | 0 .../presentation/api/providers/__init__.py | 0 .../presentation/api/providers/di.py | 2 +- .../presentation/api/providers/main.py | 0 .../presentation/api/providers/mediator.py | 2 +- .../presentation/api/providers/stub.py | 0 tests/mocks/uow.py | 2 +- tests/mocks/user_reader.py | 12 +++--- tests/mocks/user_repo.py | 8 ++-- .../application/commands/test_create_user.py | 12 +++--- .../application/commands/test_delete_user.py | 14 +++---- .../commands/test_set_user_full_name.py | 12 +++--- .../commands/test_set_user_username.py | 12 +++--- .../queries/test_get_user_by_id.py | 6 +-- .../queries/test_get_user_by_username.py | 6 +-- .../application/queries/test_get_users.py | 10 ++--- 149 files changed, 233 insertions(+), 217 deletions(-) rename src/{ => user_service}/__init__.py (100%) rename src/{ => user_service}/__main__.py (54%) rename src/{ => user_service}/application/__init__.py (100%) rename src/{ => user_service}/application/common/__init__.py (100%) rename src/{ => user_service}/application/common/command.py (100%) rename src/{ => user_service}/application/common/dto.py (100%) rename src/{ => user_service}/application/common/event.py (75%) rename src/{ => user_service}/application/common/exceptions.py (90%) rename src/{ => user_service}/application/common/interfaces/__init__.py (100%) rename src/{ => user_service}/application/common/interfaces/uow.py (100%) rename src/{ => user_service}/application/common/pagination/__init__.py (100%) rename src/{ => user_service}/application/common/pagination/dto.py (89%) rename src/{ => user_service}/application/common/query.py (100%) rename src/{ => user_service}/application/user/__init__.py (100%) rename src/{ => user_service}/application/user/commands/__init__.py (100%) rename src/{ => user_service}/application/user/commands/create_user.py (78%) rename src/{ => user_service}/application/user/commands/delete_user.py (77%) rename src/{ => user_service}/application/user/commands/set_user_full_name.py (77%) rename src/{ => user_service}/application/user/commands/set_user_username.py (78%) rename src/{ => user_service}/application/user/dto/__init__.py (100%) rename src/{ => user_service}/application/user/dto/deleted_user.py (90%) rename src/{ => user_service}/application/user/dto/user.py (89%) rename src/{ => user_service}/application/user/dto/users.py (70%) rename src/{ => user_service}/application/user/events/__init__.py (100%) rename src/{ => user_service}/application/user/exceptions.py (90%) rename src/{ => user_service}/application/user/interfaces/__init__.py (100%) rename src/{ => user_service}/application/user/interfaces/persistence/__init__.py (100%) rename src/{ => user_service}/application/user/interfaces/persistence/reader.py (75%) rename src/{ => user_service}/application/user/interfaces/persistence/repo.py (83%) rename src/{ => user_service}/application/user/queries/__init__.py (100%) rename src/{ => user_service}/application/user/queries/get_user_by_id.py (77%) rename src/{ => user_service}/application/user/queries/get_user_by_username.py (77%) rename src/{ => user_service}/application/user/queries/get_users.py (65%) rename src/{ => user_service}/domain/__init__.py (100%) rename src/{ => user_service}/domain/common/__init__.py (100%) rename src/{ => user_service}/domain/common/constants.py (100%) rename src/{ => user_service}/domain/common/entities/__init__.py (100%) rename src/{ => user_service}/domain/common/entities/aggregate_root.py (91%) rename src/{ => user_service}/domain/common/entities/entity.py (100%) rename src/{ => user_service}/domain/common/events/__init__.py (100%) rename src/{ => user_service}/domain/common/events/event.py (100%) rename src/{ => user_service}/domain/common/exceptions/__init__.py (100%) rename src/{ => user_service}/domain/common/exceptions/base.py (100%) rename src/{ => user_service}/domain/common/value_objects/__init__.py (100%) rename src/{ => user_service}/domain/common/value_objects/base.py (100%) rename src/{ => user_service}/domain/user/__init__.py (100%) rename src/{ => user_service}/domain/user/entities/__init__.py (100%) rename src/{ => user_service}/domain/user/entities/user.py (79%) rename src/{ => user_service}/domain/user/events/__init__.py (100%) rename src/{ => user_service}/domain/user/events/full_name_updated.py (77%) rename src/{ => user_service}/domain/user/events/user_created.py (78%) rename src/{ => user_service}/domain/user/events/user_deleted.py (68%) rename src/{ => user_service}/domain/user/events/username_updated.py (71%) rename src/{ => user_service}/domain/user/exceptions.py (90%) rename src/{ => user_service}/domain/user/value_objects/__init__.py (100%) rename src/{ => user_service}/domain/user/value_objects/deleted_status.py (85%) rename src/{ => user_service}/domain/user/value_objects/full_name.py (94%) rename src/{ => user_service}/domain/user/value_objects/user_id.py (65%) rename src/{ => user_service}/domain/user/value_objects/username.py (90%) rename src/{ => user_service}/infrastructure/__init__.py (100%) rename src/{ => user_service}/infrastructure/config_loader.py (100%) rename src/{ => user_service}/infrastructure/db/__init__.py (100%) rename src/{ => user_service}/infrastructure/db/config.py (100%) rename src/{ => user_service}/infrastructure/db/converters.py (84%) rename src/{ => user_service}/infrastructure/db/exception_mapper.py (90%) rename src/{ => user_service}/infrastructure/db/main.py (100%) rename src/{ => user_service}/infrastructure/db/migrations/README (100%) rename src/{ => user_service}/infrastructure/db/migrations/__init__.py (100%) rename src/{ => user_service}/infrastructure/db/migrations/env.py (92%) rename src/{ => user_service}/infrastructure/db/migrations/script.py.mako (100%) rename src/{ => user_service}/infrastructure/db/migrations/versions/20230204-105454_2d79505fb3d2_uuid_generation.py (100%) rename src/{ => user_service}/infrastructure/db/migrations/versions/20230204-110323_f78150d890d1_user_table.py (100%) rename src/{ => user_service}/infrastructure/db/migrations/versions/20230604-083729_adbf32e55780_add_middle_name_column_to_users_table.py (100%) rename src/{ => user_service}/infrastructure/db/migrations/versions/20231018-144621_36112198c668_add_deleted_at_column_to_the_users_table.py (100%) rename src/{ => user_service}/infrastructure/db/migrations/versions/__init__.py (100%) rename src/{ => user_service}/infrastructure/db/models/__init__.py (100%) rename src/{ => user_service}/infrastructure/db/models/base.py (100%) rename src/{ => user_service}/infrastructure/db/models/user.py (100%) rename src/{ => user_service}/infrastructure/db/repositories/__init__.py (100%) rename src/{ => user_service}/infrastructure/db/repositories/base.py (100%) rename src/{ => user_service}/infrastructure/db/repositories/user.py (81%) rename src/{ => user_service}/infrastructure/db/uow.py (78%) rename src/{ => user_service}/infrastructure/di/__init__.py (100%) rename src/{ => user_service}/infrastructure/di/constants.py (100%) rename src/{ => user_service}/infrastructure/di/main.py (80%) rename src/{ => user_service}/infrastructure/event_bus/__init__.py (100%) rename src/{ => user_service}/infrastructure/event_bus/converters.py (87%) rename src/{ => user_service}/infrastructure/event_bus/event_bus.py (83%) rename src/{ => user_service}/infrastructure/event_bus/event_handler.py (52%) rename src/{ => user_service}/infrastructure/event_bus/events/__init__.py (100%) rename src/{ => user_service}/infrastructure/event_bus/events/base.py (100%) rename src/{ => user_service}/infrastructure/event_bus/events/full_name_updated.py (81%) rename src/{ => user_service}/infrastructure/event_bus/events/user_created.py (80%) rename src/{ => user_service}/infrastructure/event_bus/events/user_deleted.py (77%) rename src/{ => user_service}/infrastructure/event_bus/events/username_updated.py (78%) rename src/{ => user_service}/infrastructure/event_bus/exchanges.py (68%) rename src/{ => user_service}/infrastructure/log/__init__.py (100%) rename src/{ => user_service}/infrastructure/log/config.py (100%) rename src/{ => user_service}/infrastructure/log/event_handler.py (65%) rename src/{ => user_service}/infrastructure/log/main.py (100%) rename src/{ => user_service}/infrastructure/log/processors.py (100%) rename src/{ => user_service}/infrastructure/mediator/__init__.py (100%) rename src/{ => user_service}/infrastructure/mediator/main.py (73%) rename src/{ => user_service}/infrastructure/mediator/utils.py (100%) rename src/{ => user_service}/infrastructure/message_broker/__init__.py (100%) rename src/{ => user_service}/infrastructure/message_broker/config.py (100%) rename src/{ => user_service}/infrastructure/message_broker/factories.py (100%) rename src/{ => user_service}/infrastructure/message_broker/interface.py (100%) rename src/{ => user_service}/infrastructure/message_broker/main.py (92%) rename src/{ => user_service}/infrastructure/message_broker/message.py (100%) rename src/{ => user_service}/infrastructure/message_broker/message_broker.py (100%) rename src/{ => user_service}/infrastructure/message_broker/uow.py (78%) rename src/{ => user_service}/infrastructure/uow.py (71%) rename src/{ => user_service}/presentation/__init__.py (100%) rename src/{ => user_service}/presentation/api/__init__.py (100%) rename src/{ => user_service}/presentation/api/__main__.py (68%) rename src/{ => user_service}/presentation/api/config.py (82%) rename src/{ => user_service}/presentation/api/controllers/__init__.py (100%) rename src/{ => user_service}/presentation/api/controllers/default.py (100%) rename src/{ => user_service}/presentation/api/controllers/exceptions.py (76%) rename src/{ => user_service}/presentation/api/controllers/healthcheck.py (100%) rename src/{ => user_service}/presentation/api/controllers/main.py (100%) rename src/{ => user_service}/presentation/api/controllers/requests/__init__.py (100%) rename src/{ => user_service}/presentation/api/controllers/requests/user.py (100%) rename src/{ => user_service}/presentation/api/controllers/responses/__init__.py (100%) rename src/{ => user_service}/presentation/api/controllers/responses/base.py (100%) rename src/{ => user_service}/presentation/api/controllers/responses/orjson.py (100%) rename src/{ => user_service}/presentation/api/controllers/user.py (78%) rename src/{ => user_service}/presentation/api/main.py (83%) rename src/{ => user_service}/presentation/api/middlewares/__init__.py (100%) rename src/{ => user_service}/presentation/api/middlewares/context.py (100%) rename src/{ => user_service}/presentation/api/middlewares/main.py (100%) rename src/{ => user_service}/presentation/api/middlewares/structlog.py (100%) rename src/{ => user_service}/presentation/api/providers/__init__.py (100%) rename src/{ => user_service}/presentation/api/providers/di.py (92%) rename src/{ => user_service}/presentation/api/providers/main.py (100%) rename src/{ => user_service}/presentation/api/providers/mediator.py (89%) rename src/{ => user_service}/presentation/api/providers/stub.py (100%) diff --git a/Dockerfile b/Dockerfile index 7b17def..26f6a66 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,5 +33,5 @@ RUN apt-get update \ && rm -rf /var/lib/apt/lists/* WORKDIR /app -COPY ./src /app/src -CMD ["python", "-Om", "src"] +COPY _src/user_service /app/user_service +CMD ["python", "-Om", "user_service"] diff --git a/src/__init__.py b/src/user_service/__init__.py similarity index 100% rename from src/__init__.py rename to src/user_service/__init__.py diff --git a/src/__main__.py b/src/user_service/__main__.py similarity index 54% rename from src/__main__.py rename to src/user_service/__main__.py index cf29a81..f65e62a 100644 --- a/src/__main__.py +++ b/src/user_service/__main__.py @@ -1,6 +1,6 @@ import asyncio -from src.presentation.api.__main__ import main +from user_service.presentation.api.__main__ import main if __name__ == "__main__": asyncio.run(main()) diff --git a/src/application/__init__.py b/src/user_service/application/__init__.py similarity index 100% rename from src/application/__init__.py rename to src/user_service/application/__init__.py diff --git a/src/application/common/__init__.py b/src/user_service/application/common/__init__.py similarity index 100% rename from src/application/common/__init__.py rename to src/user_service/application/common/__init__.py diff --git a/src/application/common/command.py b/src/user_service/application/common/command.py similarity index 100% rename from src/application/common/command.py rename to src/user_service/application/common/command.py diff --git a/src/application/common/dto.py b/src/user_service/application/common/dto.py similarity index 100% rename from src/application/common/dto.py rename to src/user_service/application/common/dto.py diff --git a/src/application/common/event.py b/src/user_service/application/common/event.py similarity index 75% rename from src/application/common/event.py rename to src/user_service/application/common/event.py index eca5e2b..82fb123 100644 --- a/src/application/common/event.py +++ b/src/user_service/application/common/event.py @@ -3,7 +3,7 @@ import didiator -from src.domain.common.events.event import Event +from user_service.domain.common.events.event import Event E = TypeVar("E", bound=Event) diff --git a/src/application/common/exceptions.py b/src/user_service/application/common/exceptions.py similarity index 90% rename from src/application/common/exceptions.py rename to src/user_service/application/common/exceptions.py index 3927ed2..bcb1141 100644 --- a/src/application/common/exceptions.py +++ b/src/user_service/application/common/exceptions.py @@ -1,6 +1,6 @@ from dataclasses import dataclass -from src.domain.common.exceptions import AppError +from user_service.domain.common.exceptions import AppError class ApplicationError(AppError): diff --git a/src/application/common/interfaces/__init__.py b/src/user_service/application/common/interfaces/__init__.py similarity index 100% rename from src/application/common/interfaces/__init__.py rename to src/user_service/application/common/interfaces/__init__.py diff --git a/src/application/common/interfaces/uow.py b/src/user_service/application/common/interfaces/uow.py similarity index 100% rename from src/application/common/interfaces/uow.py rename to src/user_service/application/common/interfaces/uow.py diff --git a/src/application/common/pagination/__init__.py b/src/user_service/application/common/pagination/__init__.py similarity index 100% rename from src/application/common/pagination/__init__.py rename to src/user_service/application/common/pagination/__init__.py diff --git a/src/application/common/pagination/dto.py b/src/user_service/application/common/pagination/dto.py similarity index 89% rename from src/application/common/pagination/dto.py rename to src/user_service/application/common/pagination/dto.py index 923c216..9d701fb 100644 --- a/src/application/common/pagination/dto.py +++ b/src/user_service/application/common/pagination/dto.py @@ -2,8 +2,8 @@ from enum import Enum from typing import Generic, TypeVar -from src.application.common.dto import DTO -from src.domain.common.constants import Empty +from user_service.application.common.dto import DTO +from user_service.domain.common.constants import Empty Item = TypeVar("Item") diff --git a/src/application/common/query.py b/src/user_service/application/common/query.py similarity index 100% rename from src/application/common/query.py rename to src/user_service/application/common/query.py diff --git a/src/application/user/__init__.py b/src/user_service/application/user/__init__.py similarity index 100% rename from src/application/user/__init__.py rename to src/user_service/application/user/__init__.py diff --git a/src/application/user/commands/__init__.py b/src/user_service/application/user/commands/__init__.py similarity index 100% rename from src/application/user/commands/__init__.py rename to src/user_service/application/user/commands/__init__.py diff --git a/src/application/user/commands/create_user.py b/src/user_service/application/user/commands/create_user.py similarity index 78% rename from src/application/user/commands/create_user.py rename to src/user_service/application/user/commands/create_user.py index d7b1d4b..8ce278d 100644 --- a/src/application/user/commands/create_user.py +++ b/src/user_service/application/user/commands/create_user.py @@ -4,11 +4,11 @@ from didiator import EventMediator -from src.application.common.command import Command, CommandHandler -from src.application.common.interfaces.uow import UnitOfWork -from src.application.user.interfaces import UserRepo -from src.domain.user.entities import User -from src.domain.user.value_objects import FullName, UserId, Username +from user_service.application.common.command import Command, CommandHandler +from user_service.application.common.interfaces.uow import UnitOfWork +from user_service.application.user.interfaces import UserRepo +from user_service.domain.user.entities import User +from user_service.domain.user.value_objects import FullName, UserId, Username logger = logging.getLogger(__name__) diff --git a/src/application/user/commands/delete_user.py b/src/user_service/application/user/commands/delete_user.py similarity index 77% rename from src/application/user/commands/delete_user.py rename to src/user_service/application/user/commands/delete_user.py index acc1f7f..901463d 100644 --- a/src/application/user/commands/delete_user.py +++ b/src/user_service/application/user/commands/delete_user.py @@ -4,10 +4,10 @@ from didiator import EventMediator -from src.application.common.command import Command, CommandHandler -from src.application.common.interfaces.uow import UnitOfWork -from src.application.user.interfaces import UserRepo -from src.domain.user.value_objects import UserId +from user_service.application.common.command import Command, CommandHandler +from user_service.application.common.interfaces.uow import UnitOfWork +from user_service.application.user.interfaces import UserRepo +from user_service.domain.user.value_objects import UserId logger = logging.getLogger(__name__) diff --git a/src/application/user/commands/set_user_full_name.py b/src/user_service/application/user/commands/set_user_full_name.py similarity index 77% rename from src/application/user/commands/set_user_full_name.py rename to src/user_service/application/user/commands/set_user_full_name.py index d0842f3..f52473d 100644 --- a/src/application/user/commands/set_user_full_name.py +++ b/src/user_service/application/user/commands/set_user_full_name.py @@ -4,11 +4,11 @@ from didiator import EventMediator -from src.application.common.command import Command, CommandHandler -from src.application.common.interfaces.uow import UnitOfWork -from src.application.user import dto -from src.application.user.interfaces import UserRepo -from src.domain.user.value_objects import FullName, UserId +from user_service.application.common.command import Command, CommandHandler +from user_service.application.common.interfaces.uow import UnitOfWork +from user_service.application.user import dto +from user_service.application.user.interfaces import UserRepo +from user_service.domain.user.value_objects import FullName, UserId logger = logging.getLogger(__name__) diff --git a/src/application/user/commands/set_user_username.py b/src/user_service/application/user/commands/set_user_username.py similarity index 78% rename from src/application/user/commands/set_user_username.py rename to src/user_service/application/user/commands/set_user_username.py index 9aec034..ef27843 100644 --- a/src/application/user/commands/set_user_username.py +++ b/src/user_service/application/user/commands/set_user_username.py @@ -4,10 +4,10 @@ from didiator import EventMediator -from src.application.common.command import Command, CommandHandler -from src.application.common.interfaces.uow import UnitOfWork -from src.application.user.interfaces import UserRepo -from src.domain.user.value_objects import UserId, Username +from user_service.application.common.command import Command, CommandHandler +from user_service.application.common.interfaces.uow import UnitOfWork +from user_service.application.user.interfaces import UserRepo +from user_service.domain.user.value_objects import UserId, Username logger = logging.getLogger(__name__) diff --git a/src/application/user/dto/__init__.py b/src/user_service/application/user/dto/__init__.py similarity index 100% rename from src/application/user/dto/__init__.py rename to src/user_service/application/user/dto/__init__.py diff --git a/src/application/user/dto/deleted_user.py b/src/user_service/application/user/dto/deleted_user.py similarity index 90% rename from src/application/user/dto/deleted_user.py rename to src/user_service/application/user/dto/deleted_user.py index 236f682..746e1db 100644 --- a/src/application/user/dto/deleted_user.py +++ b/src/user_service/application/user/dto/deleted_user.py @@ -2,7 +2,7 @@ from datetime import datetime from uuid import UUID -from src.application.common.dto import DTO +from user_service.application.common.dto import DTO @dataclass(frozen=True) diff --git a/src/application/user/dto/user.py b/src/user_service/application/user/dto/user.py similarity index 89% rename from src/application/user/dto/user.py rename to src/user_service/application/user/dto/user.py index 818d75e..2a6cf4b 100644 --- a/src/application/user/dto/user.py +++ b/src/user_service/application/user/dto/user.py @@ -1,7 +1,7 @@ from dataclasses import dataclass, field from uuid import UUID -from src.application.common.dto import DTO +from user_service.application.common.dto import DTO @dataclass(frozen=True) diff --git a/src/application/user/dto/users.py b/src/user_service/application/user/dto/users.py similarity index 70% rename from src/application/user/dto/users.py rename to src/user_service/application/user/dto/users.py index fb76c89..7c7ef48 100644 --- a/src/application/user/dto/users.py +++ b/src/user_service/application/user/dto/users.py @@ -1,6 +1,6 @@ from typing import TypeAlias -from src.application.common.pagination.dto import PaginatedItemsDTO +from user_service.application.common.pagination.dto import PaginatedItemsDTO from .deleted_user import DeletedUser from .user import User diff --git a/src/application/user/events/__init__.py b/src/user_service/application/user/events/__init__.py similarity index 100% rename from src/application/user/events/__init__.py rename to src/user_service/application/user/events/__init__.py diff --git a/src/application/user/exceptions.py b/src/user_service/application/user/exceptions.py similarity index 90% rename from src/application/user/exceptions.py rename to src/user_service/application/user/exceptions.py index 32b69e2..7d98849 100644 --- a/src/application/user/exceptions.py +++ b/src/user_service/application/user/exceptions.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from uuid import UUID -from src.application.common.exceptions import ApplicationError +from user_service.application.common.exceptions import ApplicationError @dataclass(eq=False) diff --git a/src/application/user/interfaces/__init__.py b/src/user_service/application/user/interfaces/__init__.py similarity index 100% rename from src/application/user/interfaces/__init__.py rename to src/user_service/application/user/interfaces/__init__.py diff --git a/src/application/user/interfaces/persistence/__init__.py b/src/user_service/application/user/interfaces/persistence/__init__.py similarity index 100% rename from src/application/user/interfaces/persistence/__init__.py rename to src/user_service/application/user/interfaces/persistence/__init__.py diff --git a/src/application/user/interfaces/persistence/reader.py b/src/user_service/application/user/interfaces/persistence/reader.py similarity index 75% rename from src/application/user/interfaces/persistence/reader.py rename to src/user_service/application/user/interfaces/persistence/reader.py index 65eab2d..01e9bfe 100644 --- a/src/application/user/interfaces/persistence/reader.py +++ b/src/user_service/application/user/interfaces/persistence/reader.py @@ -2,9 +2,9 @@ from typing import Protocol from uuid import UUID -from src.application.common.pagination.dto import Pagination -from src.application.user import dto -from src.domain.common.constants import Empty +from user_service.application.common.pagination.dto import Pagination +from user_service.application.user import dto +from user_service.domain.common.constants import Empty @dataclass(frozen=True) diff --git a/src/application/user/interfaces/persistence/repo.py b/src/user_service/application/user/interfaces/persistence/repo.py similarity index 83% rename from src/application/user/interfaces/persistence/repo.py rename to src/user_service/application/user/interfaces/persistence/repo.py index df59be3..57e4a5f 100644 --- a/src/application/user/interfaces/persistence/repo.py +++ b/src/user_service/application/user/interfaces/persistence/repo.py @@ -1,8 +1,8 @@ import abc from typing import Protocol -from src.domain.user import entities -from src.domain.user.value_objects import UserId, Username +from user_service.domain.user import entities +from user_service.domain.user.value_objects import UserId, Username class UserRepo(Protocol): diff --git a/src/application/user/queries/__init__.py b/src/user_service/application/user/queries/__init__.py similarity index 100% rename from src/application/user/queries/__init__.py rename to src/user_service/application/user/queries/__init__.py diff --git a/src/application/user/queries/get_user_by_id.py b/src/user_service/application/user/queries/get_user_by_id.py similarity index 77% rename from src/application/user/queries/get_user_by_id.py rename to src/user_service/application/user/queries/get_user_by_id.py index f2ecbed..fa686d8 100644 --- a/src/application/user/queries/get_user_by_id.py +++ b/src/user_service/application/user/queries/get_user_by_id.py @@ -2,9 +2,9 @@ from dataclasses import dataclass from uuid import UUID -from src.application.common.query import Query, QueryHandler -from src.application.user import dto -from src.application.user.interfaces import UserReader +from user_service.application.common.query import Query, QueryHandler +from user_service.application.user import dto +from user_service.application.user.interfaces import UserReader logger = logging.getLogger(__name__) diff --git a/src/application/user/queries/get_user_by_username.py b/src/user_service/application/user/queries/get_user_by_username.py similarity index 77% rename from src/application/user/queries/get_user_by_username.py rename to src/user_service/application/user/queries/get_user_by_username.py index 170847e..c6797ef 100644 --- a/src/application/user/queries/get_user_by_username.py +++ b/src/user_service/application/user/queries/get_user_by_username.py @@ -1,9 +1,9 @@ import logging from dataclasses import dataclass -from src.application.common.query import Query, QueryHandler -from src.application.user import dto -from src.application.user.interfaces import UserReader +from user_service.application.common.query import Query, QueryHandler +from user_service.application.user import dto +from user_service.application.user.interfaces import UserReader logger = logging.getLogger(__name__) diff --git a/src/application/user/queries/get_users.py b/src/user_service/application/user/queries/get_users.py similarity index 65% rename from src/application/user/queries/get_users.py rename to src/user_service/application/user/queries/get_users.py index 1473a99..48e59aa 100644 --- a/src/application/user/queries/get_users.py +++ b/src/user_service/application/user/queries/get_users.py @@ -1,11 +1,11 @@ import logging from dataclasses import dataclass -from src.application.common.pagination.dto import Pagination -from src.application.common.query import Query, QueryHandler -from src.application.user import dto -from src.application.user.interfaces import UserReader -from src.application.user.interfaces.persistence import GetUsersFilters +from user_service.application.common.pagination.dto import Pagination +from user_service.application.common.query import Query, QueryHandler +from user_service.application.user import dto +from user_service.application.user.interfaces import UserReader +from user_service.application.user.interfaces.persistence import GetUsersFilters logger = logging.getLogger(__name__) diff --git a/src/domain/__init__.py b/src/user_service/domain/__init__.py similarity index 100% rename from src/domain/__init__.py rename to src/user_service/domain/__init__.py diff --git a/src/domain/common/__init__.py b/src/user_service/domain/common/__init__.py similarity index 100% rename from src/domain/common/__init__.py rename to src/user_service/domain/common/__init__.py diff --git a/src/domain/common/constants.py b/src/user_service/domain/common/constants.py similarity index 100% rename from src/domain/common/constants.py rename to src/user_service/domain/common/constants.py diff --git a/src/domain/common/entities/__init__.py b/src/user_service/domain/common/entities/__init__.py similarity index 100% rename from src/domain/common/entities/__init__.py rename to src/user_service/domain/common/entities/__init__.py diff --git a/src/domain/common/entities/aggregate_root.py b/src/user_service/domain/common/entities/aggregate_root.py similarity index 91% rename from src/domain/common/entities/aggregate_root.py rename to src/user_service/domain/common/entities/aggregate_root.py index 6c94d87..bca9915 100644 --- a/src/domain/common/entities/aggregate_root.py +++ b/src/user_service/domain/common/entities/aggregate_root.py @@ -1,7 +1,7 @@ from abc import ABC from dataclasses import dataclass, field -from src.domain.common.events.event import Event +from user_service.domain.common.events.event import Event from .entity import Entity diff --git a/src/domain/common/entities/entity.py b/src/user_service/domain/common/entities/entity.py similarity index 100% rename from src/domain/common/entities/entity.py rename to src/user_service/domain/common/entities/entity.py diff --git a/src/domain/common/events/__init__.py b/src/user_service/domain/common/events/__init__.py similarity index 100% rename from src/domain/common/events/__init__.py rename to src/user_service/domain/common/events/__init__.py diff --git a/src/domain/common/events/event.py b/src/user_service/domain/common/events/event.py similarity index 100% rename from src/domain/common/events/event.py rename to src/user_service/domain/common/events/event.py diff --git a/src/domain/common/exceptions/__init__.py b/src/user_service/domain/common/exceptions/__init__.py similarity index 100% rename from src/domain/common/exceptions/__init__.py rename to src/user_service/domain/common/exceptions/__init__.py diff --git a/src/domain/common/exceptions/base.py b/src/user_service/domain/common/exceptions/base.py similarity index 100% rename from src/domain/common/exceptions/base.py rename to src/user_service/domain/common/exceptions/base.py diff --git a/src/domain/common/value_objects/__init__.py b/src/user_service/domain/common/value_objects/__init__.py similarity index 100% rename from src/domain/common/value_objects/__init__.py rename to src/user_service/domain/common/value_objects/__init__.py diff --git a/src/domain/common/value_objects/base.py b/src/user_service/domain/common/value_objects/base.py similarity index 100% rename from src/domain/common/value_objects/base.py rename to src/user_service/domain/common/value_objects/base.py diff --git a/src/domain/user/__init__.py b/src/user_service/domain/user/__init__.py similarity index 100% rename from src/domain/user/__init__.py rename to src/user_service/domain/user/__init__.py diff --git a/src/domain/user/entities/__init__.py b/src/user_service/domain/user/entities/__init__.py similarity index 100% rename from src/domain/user/entities/__init__.py rename to src/user_service/domain/user/entities/__init__.py diff --git a/src/domain/user/entities/user.py b/src/user_service/domain/user/entities/user.py similarity index 79% rename from src/domain/user/entities/user.py rename to src/user_service/domain/user/entities/user.py index ae920f3..3aeaa16 100644 --- a/src/domain/user/entities/user.py +++ b/src/user_service/domain/user/entities/user.py @@ -1,14 +1,14 @@ import dataclasses from typing import Self -from src.domain.common.entities.aggregate_root import AggregateRoot -from src.domain.user.events import FullNameUpdated -from src.domain.user.events.user_created import UserCreated -from src.domain.user.events.user_deleted import UserDeleted -from src.domain.user.events.username_updated import UsernameUpdated -from src.domain.user.exceptions import UserIsDeletedError, UsernameAlreadyExistsError -from src.domain.user.value_objects import FullName, UserId, Username -from src.domain.user.value_objects.deleted_status import DeletionTime +from user_service.domain.common.entities.aggregate_root import AggregateRoot +from user_service.domain.user.events import FullNameUpdated +from user_service.domain.user.events.user_created import UserCreated +from user_service.domain.user.events.user_deleted import UserDeleted +from user_service.domain.user.events.username_updated import UsernameUpdated +from user_service.domain.user.exceptions import UserIsDeletedError, UsernameAlreadyExistsError +from user_service.domain.user.value_objects import FullName, UserId, Username +from user_service.domain.user.value_objects.deleted_status import DeletionTime @dataclasses.dataclass diff --git a/src/domain/user/events/__init__.py b/src/user_service/domain/user/events/__init__.py similarity index 100% rename from src/domain/user/events/__init__.py rename to src/user_service/domain/user/events/__init__.py diff --git a/src/domain/user/events/full_name_updated.py b/src/user_service/domain/user/events/full_name_updated.py similarity index 77% rename from src/domain/user/events/full_name_updated.py rename to src/user_service/domain/user/events/full_name_updated.py index cb047a2..d9341bb 100644 --- a/src/domain/user/events/full_name_updated.py +++ b/src/user_service/domain/user/events/full_name_updated.py @@ -1,7 +1,7 @@ import dataclasses from uuid import UUID -from src.domain.common.events.event import Event +from user_service.domain.common.events.event import Event @dataclasses.dataclass(frozen=True) diff --git a/src/domain/user/events/user_created.py b/src/user_service/domain/user/events/user_created.py similarity index 78% rename from src/domain/user/events/user_created.py rename to src/user_service/domain/user/events/user_created.py index a8d28fb..e9d562b 100644 --- a/src/domain/user/events/user_created.py +++ b/src/user_service/domain/user/events/user_created.py @@ -1,7 +1,7 @@ import dataclasses from uuid import UUID -from src.domain.common.events.event import Event +from user_service.domain.common.events.event import Event @dataclasses.dataclass(frozen=True) diff --git a/src/domain/user/events/user_deleted.py b/src/user_service/domain/user/events/user_deleted.py similarity index 68% rename from src/domain/user/events/user_deleted.py rename to src/user_service/domain/user/events/user_deleted.py index 58c87f2..9a2a948 100644 --- a/src/domain/user/events/user_deleted.py +++ b/src/user_service/domain/user/events/user_deleted.py @@ -1,7 +1,7 @@ import dataclasses from uuid import UUID -from src.domain.common.events.event import Event +from user_service.domain.common.events.event import Event @dataclasses.dataclass(frozen=True) diff --git a/src/domain/user/events/username_updated.py b/src/user_service/domain/user/events/username_updated.py similarity index 71% rename from src/domain/user/events/username_updated.py rename to src/user_service/domain/user/events/username_updated.py index f4971a5..7928629 100644 --- a/src/domain/user/events/username_updated.py +++ b/src/user_service/domain/user/events/username_updated.py @@ -1,7 +1,7 @@ import dataclasses from uuid import UUID -from src.domain.common.events.event import Event +from user_service.domain.common.events.event import Event @dataclasses.dataclass(frozen=True) diff --git a/src/domain/user/exceptions.py b/src/user_service/domain/user/exceptions.py similarity index 90% rename from src/domain/user/exceptions.py rename to src/user_service/domain/user/exceptions.py index 38431f6..490afaf 100644 --- a/src/domain/user/exceptions.py +++ b/src/user_service/domain/user/exceptions.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from uuid import UUID -from src.domain.common.exceptions import DomainError +from user_service.domain.common.exceptions import DomainError @dataclass(eq=False) diff --git a/src/domain/user/value_objects/__init__.py b/src/user_service/domain/user/value_objects/__init__.py similarity index 100% rename from src/domain/user/value_objects/__init__.py rename to src/user_service/domain/user/value_objects/__init__.py diff --git a/src/domain/user/value_objects/deleted_status.py b/src/user_service/domain/user/value_objects/deleted_status.py similarity index 85% rename from src/domain/user/value_objects/deleted_status.py rename to src/user_service/domain/user/value_objects/deleted_status.py index dbc00ae..0520334 100644 --- a/src/domain/user/value_objects/deleted_status.py +++ b/src/user_service/domain/user/value_objects/deleted_status.py @@ -1,7 +1,7 @@ from datetime import UTC, datetime from typing import Self -from src.domain.common.value_objects import ValueObject +from user_service.domain.common.value_objects import ValueObject class DeletionTime(ValueObject[datetime | None]): diff --git a/src/domain/user/value_objects/full_name.py b/src/user_service/domain/user/value_objects/full_name.py similarity index 94% rename from src/domain/user/value_objects/full_name.py rename to src/user_service/domain/user/value_objects/full_name.py index ff21649..7e2eed7 100644 --- a/src/domain/user/value_objects/full_name.py +++ b/src/user_service/domain/user/value_objects/full_name.py @@ -1,8 +1,8 @@ import re from dataclasses import dataclass -from src.domain.common.exceptions import DomainError -from src.domain.common.value_objects.base import BaseValueObject +from user_service.domain.common.exceptions import DomainError +from user_service.domain.common.value_objects.base import BaseValueObject MAX_NAME_LENGTH = 32 NAME_PATTERN = re.compile(r"[A-Za-z]+") diff --git a/src/domain/user/value_objects/user_id.py b/src/user_service/domain/user/value_objects/user_id.py similarity index 65% rename from src/domain/user/value_objects/user_id.py rename to src/user_service/domain/user/value_objects/user_id.py index 4ee81bd..09de87a 100644 --- a/src/domain/user/value_objects/user_id.py +++ b/src/user_service/domain/user/value_objects/user_id.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from uuid import UUID -from src.domain.common.value_objects.base import ValueObject +from user_service.domain.common.value_objects.base import ValueObject @dataclass(frozen=True) diff --git a/src/domain/user/value_objects/username.py b/src/user_service/domain/user/value_objects/username.py similarity index 90% rename from src/domain/user/value_objects/username.py rename to src/user_service/domain/user/value_objects/username.py index 62e871e..9f2d0f0 100644 --- a/src/domain/user/value_objects/username.py +++ b/src/user_service/domain/user/value_objects/username.py @@ -1,8 +1,8 @@ import re from dataclasses import dataclass -from src.domain.common.exceptions import DomainError -from src.domain.common.value_objects.base import ValueObject +from user_service.domain.common.exceptions import DomainError +from user_service.domain.common.value_objects.base import ValueObject MAX_USERNAME_LENGTH = 32 USERNAME_PATTERN = re.compile(r"[A-Za-z][A-Za-z1-9_]+") diff --git a/src/infrastructure/__init__.py b/src/user_service/infrastructure/__init__.py similarity index 100% rename from src/infrastructure/__init__.py rename to src/user_service/infrastructure/__init__.py diff --git a/src/infrastructure/config_loader.py b/src/user_service/infrastructure/config_loader.py similarity index 100% rename from src/infrastructure/config_loader.py rename to src/user_service/infrastructure/config_loader.py diff --git a/src/infrastructure/db/__init__.py b/src/user_service/infrastructure/db/__init__.py similarity index 100% rename from src/infrastructure/db/__init__.py rename to src/user_service/infrastructure/db/__init__.py diff --git a/src/infrastructure/db/config.py b/src/user_service/infrastructure/db/config.py similarity index 100% rename from src/infrastructure/db/config.py rename to src/user_service/infrastructure/db/config.py diff --git a/src/infrastructure/db/converters.py b/src/user_service/infrastructure/db/converters.py similarity index 84% rename from src/infrastructure/db/converters.py rename to src/user_service/infrastructure/db/converters.py index 4637807..296ddae 100644 --- a/src/infrastructure/db/converters.py +++ b/src/user_service/infrastructure/db/converters.py @@ -1,12 +1,12 @@ from datetime import datetime from typing import cast -from src.application.common.exceptions import MappingError -from src.application.user import dto -from src.domain.user import entities, value_objects as vo -from src.domain.user.value_objects import Username -from src.domain.user.value_objects.deleted_status import DeletionTime -from src.infrastructure.db import models +from user_service.application.common.exceptions import MappingError +from user_service.application.user import dto +from user_service.domain.user import entities, value_objects as vo +from user_service.domain.user.value_objects import Username +from user_service.domain.user.value_objects.deleted_status import DeletionTime +from user_service.infrastructure.db import models def convert_user_entity_to_db_model(user: entities.User) -> models.User: diff --git a/src/infrastructure/db/exception_mapper.py b/src/user_service/infrastructure/db/exception_mapper.py similarity index 90% rename from src/infrastructure/db/exception_mapper.py rename to src/user_service/infrastructure/db/exception_mapper.py index dad3429..7669239 100644 --- a/src/infrastructure/db/exception_mapper.py +++ b/src/user_service/infrastructure/db/exception_mapper.py @@ -4,7 +4,7 @@ from sqlalchemy.exc import SQLAlchemyError -from src.application.common.exceptions import RepoError +from user_service.application.common.exceptions import RepoError Param = ParamSpec("Param") ReturnType = TypeVar("ReturnType") diff --git a/src/infrastructure/db/main.py b/src/user_service/infrastructure/db/main.py similarity index 100% rename from src/infrastructure/db/main.py rename to src/user_service/infrastructure/db/main.py diff --git a/src/infrastructure/db/migrations/README b/src/user_service/infrastructure/db/migrations/README similarity index 100% rename from src/infrastructure/db/migrations/README rename to src/user_service/infrastructure/db/migrations/README diff --git a/src/infrastructure/db/migrations/__init__.py b/src/user_service/infrastructure/db/migrations/__init__.py similarity index 100% rename from src/infrastructure/db/migrations/__init__.py rename to src/user_service/infrastructure/db/migrations/__init__.py diff --git a/src/infrastructure/db/migrations/env.py b/src/user_service/infrastructure/db/migrations/env.py similarity index 92% rename from src/infrastructure/db/migrations/env.py rename to src/user_service/infrastructure/db/migrations/env.py index f830de8..ad96402 100644 --- a/src/infrastructure/db/migrations/env.py +++ b/src/user_service/infrastructure/db/migrations/env.py @@ -6,9 +6,9 @@ from sqlalchemy.engine import Connection from sqlalchemy.ext.asyncio import AsyncEngine -from src.infrastructure.config_loader import load_config -from src.infrastructure.db import DBConfig -from src.infrastructure.db.models.base import BaseModel +from user_service.infrastructure.config_loader import load_config +from user_service.infrastructure.db import DBConfig +from user_service.infrastructure.db.models.base import BaseModel # this is the Alembic Config object, which provides # access to the values within the .ini file in use. diff --git a/src/infrastructure/db/migrations/script.py.mako b/src/user_service/infrastructure/db/migrations/script.py.mako similarity index 100% rename from src/infrastructure/db/migrations/script.py.mako rename to src/user_service/infrastructure/db/migrations/script.py.mako diff --git a/src/infrastructure/db/migrations/versions/20230204-105454_2d79505fb3d2_uuid_generation.py b/src/user_service/infrastructure/db/migrations/versions/20230204-105454_2d79505fb3d2_uuid_generation.py similarity index 100% rename from src/infrastructure/db/migrations/versions/20230204-105454_2d79505fb3d2_uuid_generation.py rename to src/user_service/infrastructure/db/migrations/versions/20230204-105454_2d79505fb3d2_uuid_generation.py diff --git a/src/infrastructure/db/migrations/versions/20230204-110323_f78150d890d1_user_table.py b/src/user_service/infrastructure/db/migrations/versions/20230204-110323_f78150d890d1_user_table.py similarity index 100% rename from src/infrastructure/db/migrations/versions/20230204-110323_f78150d890d1_user_table.py rename to src/user_service/infrastructure/db/migrations/versions/20230204-110323_f78150d890d1_user_table.py diff --git a/src/infrastructure/db/migrations/versions/20230604-083729_adbf32e55780_add_middle_name_column_to_users_table.py b/src/user_service/infrastructure/db/migrations/versions/20230604-083729_adbf32e55780_add_middle_name_column_to_users_table.py similarity index 100% rename from src/infrastructure/db/migrations/versions/20230604-083729_adbf32e55780_add_middle_name_column_to_users_table.py rename to src/user_service/infrastructure/db/migrations/versions/20230604-083729_adbf32e55780_add_middle_name_column_to_users_table.py diff --git a/src/infrastructure/db/migrations/versions/20231018-144621_36112198c668_add_deleted_at_column_to_the_users_table.py b/src/user_service/infrastructure/db/migrations/versions/20231018-144621_36112198c668_add_deleted_at_column_to_the_users_table.py similarity index 100% rename from src/infrastructure/db/migrations/versions/20231018-144621_36112198c668_add_deleted_at_column_to_the_users_table.py rename to src/user_service/infrastructure/db/migrations/versions/20231018-144621_36112198c668_add_deleted_at_column_to_the_users_table.py diff --git a/src/infrastructure/db/migrations/versions/__init__.py b/src/user_service/infrastructure/db/migrations/versions/__init__.py similarity index 100% rename from src/infrastructure/db/migrations/versions/__init__.py rename to src/user_service/infrastructure/db/migrations/versions/__init__.py diff --git a/src/infrastructure/db/models/__init__.py b/src/user_service/infrastructure/db/models/__init__.py similarity index 100% rename from src/infrastructure/db/models/__init__.py rename to src/user_service/infrastructure/db/models/__init__.py diff --git a/src/infrastructure/db/models/base.py b/src/user_service/infrastructure/db/models/base.py similarity index 100% rename from src/infrastructure/db/models/base.py rename to src/user_service/infrastructure/db/models/base.py diff --git a/src/infrastructure/db/models/user.py b/src/user_service/infrastructure/db/models/user.py similarity index 100% rename from src/infrastructure/db/models/user.py rename to src/user_service/infrastructure/db/models/user.py diff --git a/src/infrastructure/db/repositories/__init__.py b/src/user_service/infrastructure/db/repositories/__init__.py similarity index 100% rename from src/infrastructure/db/repositories/__init__.py rename to src/user_service/infrastructure/db/repositories/__init__.py diff --git a/src/infrastructure/db/repositories/base.py b/src/user_service/infrastructure/db/repositories/base.py similarity index 100% rename from src/infrastructure/db/repositories/base.py rename to src/user_service/infrastructure/db/repositories/base.py diff --git a/src/infrastructure/db/repositories/user.py b/src/user_service/infrastructure/db/repositories/user.py similarity index 81% rename from src/infrastructure/db/repositories/user.py rename to src/user_service/infrastructure/db/repositories/user.py index f092ef6..4dda3f0 100644 --- a/src/infrastructure/db/repositories/user.py +++ b/src/user_service/infrastructure/db/repositories/user.py @@ -5,24 +5,28 @@ from sqlalchemy import func, select from sqlalchemy.exc import DBAPIError, IntegrityError -from src.application.common.exceptions import RepoError -from src.application.common.pagination.dto import Pagination, PaginationResult, SortOrder -from src.application.user import dto -from src.application.user.exceptions import UserIdAlreadyExistsError, UserIdNotExistError, UsernameNotExistError -from src.application.user.interfaces.persistence import GetUsersFilters, UserReader, UserRepo -from src.domain.common.constants import Empty -from src.domain.user import entities -from src.domain.user.exceptions import UsernameAlreadyExistsError -from src.domain.user.value_objects import UserId, Username -from src.infrastructure.db.converters import ( +from user_service.application.common.exceptions import RepoError +from user_service.application.common.pagination.dto import Pagination, PaginationResult, SortOrder +from user_service.application.user import dto +from user_service.application.user.exceptions import ( + UserIdAlreadyExistsError, + UserIdNotExistError, + UsernameNotExistError, +) +from user_service.application.user.interfaces.persistence import GetUsersFilters, UserReader, UserRepo +from user_service.domain.common.constants import Empty +from user_service.domain.user import entities +from user_service.domain.user.exceptions import UsernameAlreadyExistsError +from user_service.domain.user.value_objects import UserId, Username +from user_service.infrastructure.db.converters import ( convert_db_model_to_active_user_dto, convert_db_model_to_user_dto, convert_db_model_to_user_entity, convert_user_entity_to_db_model, ) -from src.infrastructure.db.exception_mapper import exception_mapper -from src.infrastructure.db.models.user import User -from src.infrastructure.db.repositories.base import SQLAlchemyRepo +from user_service.infrastructure.db.exception_mapper import exception_mapper +from user_service.infrastructure.db.models.user import User +from user_service.infrastructure.db.repositories.base import SQLAlchemyRepo class UserReaderImpl(SQLAlchemyRepo, UserReader): diff --git a/src/infrastructure/db/uow.py b/src/user_service/infrastructure/db/uow.py similarity index 78% rename from src/infrastructure/db/uow.py rename to src/user_service/infrastructure/db/uow.py index 01bdfca..7952875 100644 --- a/src/infrastructure/db/uow.py +++ b/src/user_service/infrastructure/db/uow.py @@ -1,8 +1,8 @@ from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.ext.asyncio import AsyncSession -from src.application.common.exceptions import CommitError, RollbackError -from src.application.common.interfaces.uow import UnitOfWork +from user_service.application.common.exceptions import CommitError, RollbackError +from user_service.application.common.interfaces.uow import UnitOfWork class SQLAlchemyUoW(UnitOfWork): diff --git a/src/infrastructure/di/__init__.py b/src/user_service/infrastructure/di/__init__.py similarity index 100% rename from src/infrastructure/di/__init__.py rename to src/user_service/infrastructure/di/__init__.py diff --git a/src/infrastructure/di/constants.py b/src/user_service/infrastructure/di/constants.py similarity index 100% rename from src/infrastructure/di/constants.py rename to src/user_service/infrastructure/di/constants.py diff --git a/src/infrastructure/di/main.py b/src/user_service/infrastructure/di/main.py similarity index 80% rename from src/infrastructure/di/main.py rename to src/user_service/infrastructure/di/main.py index e4266e5..0dccdb1 100644 --- a/src/infrastructure/di/main.py +++ b/src/user_service/infrastructure/di/main.py @@ -9,22 +9,22 @@ from didiator.utils.di_builder import DiBuilderImpl from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker -from src.application.common.interfaces.uow import UnitOfWork -from src.application.user.interfaces.persistence import UserReader, UserRepo -from src.infrastructure.db.main import build_sa_engine, build_sa_session, build_sa_session_factory -from src.infrastructure.db.repositories.user import UserReaderImpl, UserRepoImpl -from src.infrastructure.di import DiScope -from src.infrastructure.event_bus.event_bus import EventBusImpl -from src.infrastructure.mediator import get_mediator -from src.infrastructure.message_broker.interface import MessageBroker -from src.infrastructure.message_broker.main import ( +from user_service.application.common.interfaces.uow import UnitOfWork +from user_service.application.user.interfaces.persistence import UserReader, UserRepo +from user_service.infrastructure.db.main import build_sa_engine, build_sa_session, build_sa_session_factory +from user_service.infrastructure.db.repositories.user import UserReaderImpl, UserRepoImpl +from user_service.infrastructure.di import DiScope +from user_service.infrastructure.event_bus.event_bus import EventBusImpl +from user_service.infrastructure.mediator import get_mediator +from user_service.infrastructure.message_broker.interface import MessageBroker +from user_service.infrastructure.message_broker.main import ( build_rq_channel, build_rq_channel_pool, build_rq_connection_pool, build_rq_transaction, ) -from src.infrastructure.message_broker.message_broker import MessageBrokerImpl -from src.infrastructure.uow import build_uow +from user_service.infrastructure.message_broker.message_broker import MessageBrokerImpl +from user_service.infrastructure.uow import build_uow def init_di_builder() -> DiBuilder: diff --git a/src/infrastructure/event_bus/__init__.py b/src/user_service/infrastructure/event_bus/__init__.py similarity index 100% rename from src/infrastructure/event_bus/__init__.py rename to src/user_service/infrastructure/event_bus/__init__.py diff --git a/src/infrastructure/event_bus/converters.py b/src/user_service/infrastructure/event_bus/converters.py similarity index 87% rename from src/infrastructure/event_bus/converters.py rename to src/user_service/infrastructure/event_bus/converters.py index 45ca30e..3760dff 100644 --- a/src/infrastructure/event_bus/converters.py +++ b/src/user_service/infrastructure/event_bus/converters.py @@ -1,6 +1,6 @@ -from src.application.common.exceptions import MappingError -from src.domain.user.events import FullNameUpdated, UserCreated, UserDeleted, UsernameUpdated -from src.infrastructure.event_bus import events as integration_events +from user_service.application.common.exceptions import MappingError +from user_service.domain.user.events import FullNameUpdated, UserCreated, UserDeleted, UsernameUpdated +from user_service.infrastructure.event_bus import events as integration_events DomainEvents = UserCreated | UsernameUpdated | FullNameUpdated | UserDeleted diff --git a/src/infrastructure/event_bus/event_bus.py b/src/user_service/infrastructure/event_bus/event_bus.py similarity index 83% rename from src/infrastructure/event_bus/event_bus.py rename to src/user_service/infrastructure/event_bus/event_bus.py index 70c7367..e11d509 100644 --- a/src/infrastructure/event_bus/event_bus.py +++ b/src/user_service/infrastructure/event_bus/event_bus.py @@ -2,8 +2,8 @@ import orjson -from src.infrastructure.log.processors import additionally_serialize -from src.infrastructure.message_broker import Message, MessageBroker +from user_service.infrastructure.log.processors import additionally_serialize +from user_service.infrastructure.message_broker import Message, MessageBroker from .events.base import IntegrationEvent diff --git a/src/infrastructure/event_bus/event_handler.py b/src/user_service/infrastructure/event_bus/event_handler.py similarity index 52% rename from src/infrastructure/event_bus/event_handler.py rename to src/user_service/infrastructure/event_bus/event_handler.py index 1992cf2..2d0ebdf 100644 --- a/src/infrastructure/event_bus/event_handler.py +++ b/src/user_service/infrastructure/event_bus/event_handler.py @@ -1,8 +1,8 @@ -from src.application.common.event import EventHandler -from src.application.common.exceptions import MappingError -from src.domain.common.events import Event -from src.infrastructure.event_bus.converters import convert_domain_event_to_integration -from src.infrastructure.event_bus.event_bus import EventBusImpl +from user_service.application.common.event import EventHandler +from user_service.application.common.exceptions import MappingError +from user_service.domain.common.events import Event +from user_service.infrastructure.event_bus.converters import convert_domain_event_to_integration +from user_service.infrastructure.event_bus.event_bus import EventBusImpl class EventHandlerPublisher(EventHandler[Event]): diff --git a/src/infrastructure/event_bus/events/__init__.py b/src/user_service/infrastructure/event_bus/events/__init__.py similarity index 100% rename from src/infrastructure/event_bus/events/__init__.py rename to src/user_service/infrastructure/event_bus/events/__init__.py diff --git a/src/infrastructure/event_bus/events/base.py b/src/user_service/infrastructure/event_bus/events/base.py similarity index 100% rename from src/infrastructure/event_bus/events/base.py rename to src/user_service/infrastructure/event_bus/events/base.py diff --git a/src/infrastructure/event_bus/events/full_name_updated.py b/src/user_service/infrastructure/event_bus/events/full_name_updated.py similarity index 81% rename from src/infrastructure/event_bus/events/full_name_updated.py rename to src/user_service/infrastructure/event_bus/events/full_name_updated.py index a44988c..9a64b25 100644 --- a/src/infrastructure/event_bus/events/full_name_updated.py +++ b/src/user_service/infrastructure/event_bus/events/full_name_updated.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from uuid import UUID -from src.infrastructure.event_bus.exchanges import USER_EXCHANGE +from user_service.infrastructure.event_bus.exchanges import USER_EXCHANGE from .base import IntegrationEvent, integration_event diff --git a/src/infrastructure/event_bus/events/user_created.py b/src/user_service/infrastructure/event_bus/events/user_created.py similarity index 80% rename from src/infrastructure/event_bus/events/user_created.py rename to src/user_service/infrastructure/event_bus/events/user_created.py index d09e635..bea9d4d 100644 --- a/src/infrastructure/event_bus/events/user_created.py +++ b/src/user_service/infrastructure/event_bus/events/user_created.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from uuid import UUID -from src.infrastructure.event_bus.exchanges import USER_EXCHANGE +from user_service.infrastructure.event_bus.exchanges import USER_EXCHANGE from .base import IntegrationEvent, integration_event diff --git a/src/infrastructure/event_bus/events/user_deleted.py b/src/user_service/infrastructure/event_bus/events/user_deleted.py similarity index 77% rename from src/infrastructure/event_bus/events/user_deleted.py rename to src/user_service/infrastructure/event_bus/events/user_deleted.py index 30b8c71..8a00f59 100644 --- a/src/infrastructure/event_bus/events/user_deleted.py +++ b/src/user_service/infrastructure/event_bus/events/user_deleted.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from uuid import UUID -from src.infrastructure.event_bus.exchanges import USER_EXCHANGE +from user_service.infrastructure.event_bus.exchanges import USER_EXCHANGE from .base import IntegrationEvent, integration_event diff --git a/src/infrastructure/event_bus/events/username_updated.py b/src/user_service/infrastructure/event_bus/events/username_updated.py similarity index 78% rename from src/infrastructure/event_bus/events/username_updated.py rename to src/user_service/infrastructure/event_bus/events/username_updated.py index 3750952..3efa69f 100644 --- a/src/infrastructure/event_bus/events/username_updated.py +++ b/src/user_service/infrastructure/event_bus/events/username_updated.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from uuid import UUID -from src.infrastructure.event_bus.exchanges import USER_EXCHANGE +from user_service.infrastructure.event_bus.exchanges import USER_EXCHANGE from .base import IntegrationEvent, integration_event diff --git a/src/infrastructure/event_bus/exchanges.py b/src/user_service/infrastructure/event_bus/exchanges.py similarity index 68% rename from src/infrastructure/event_bus/exchanges.py rename to src/user_service/infrastructure/event_bus/exchanges.py index b9751f1..812da98 100644 --- a/src/infrastructure/event_bus/exchanges.py +++ b/src/user_service/infrastructure/event_bus/exchanges.py @@ -1,4 +1,4 @@ -from src.infrastructure.message_broker import MessageBroker +from user_service.infrastructure.message_broker import MessageBroker USER_EXCHANGE = "users" diff --git a/src/infrastructure/log/__init__.py b/src/user_service/infrastructure/log/__init__.py similarity index 100% rename from src/infrastructure/log/__init__.py rename to src/user_service/infrastructure/log/__init__.py diff --git a/src/infrastructure/log/config.py b/src/user_service/infrastructure/log/config.py similarity index 100% rename from src/infrastructure/log/config.py rename to src/user_service/infrastructure/log/config.py diff --git a/src/infrastructure/log/event_handler.py b/src/user_service/infrastructure/log/event_handler.py similarity index 65% rename from src/infrastructure/log/event_handler.py rename to src/user_service/infrastructure/log/event_handler.py index e0d212c..94f09c9 100644 --- a/src/infrastructure/log/event_handler.py +++ b/src/user_service/infrastructure/log/event_handler.py @@ -1,7 +1,7 @@ import logging -from src.application.common.event import EventHandler -from src.domain.common.events import Event +from user_service.application.common.event import EventHandler +from user_service.domain.common.events import Event logger = logging.getLogger(__name__) diff --git a/src/infrastructure/log/main.py b/src/user_service/infrastructure/log/main.py similarity index 100% rename from src/infrastructure/log/main.py rename to src/user_service/infrastructure/log/main.py diff --git a/src/infrastructure/log/processors.py b/src/user_service/infrastructure/log/processors.py similarity index 100% rename from src/infrastructure/log/processors.py rename to src/user_service/infrastructure/log/processors.py diff --git a/src/infrastructure/mediator/__init__.py b/src/user_service/infrastructure/mediator/__init__.py similarity index 100% rename from src/infrastructure/mediator/__init__.py rename to src/user_service/infrastructure/mediator/__init__.py diff --git a/src/infrastructure/mediator/main.py b/src/user_service/infrastructure/mediator/main.py similarity index 73% rename from src/infrastructure/mediator/main.py rename to src/user_service/infrastructure/mediator/main.py index f4b9c2d..2e0c964 100644 --- a/src/infrastructure/mediator/main.py +++ b/src/user_service/infrastructure/mediator/main.py @@ -5,7 +5,7 @@ from didiator.middlewares.di import DiMiddleware, DiScopes from didiator.middlewares.logging import LoggingMiddleware -from src.application.user.commands import ( +from user_service.application.user.commands import ( CreateUser, CreateUserHandler, DeleteUser, @@ -15,13 +15,13 @@ SetUserUsername, SetUserUsernameHandler, ) -from src.application.user.queries.get_user_by_id import GetUserById, GetUserByIdHandler -from src.application.user.queries.get_user_by_username import GetUserByUsername, GetUserByUsernameHandler -from src.application.user.queries.get_users import GetUsers, GetUsersHandler -from src.domain.common.events import Event -from src.infrastructure.di import DiScope -from src.infrastructure.event_bus.event_handler import EventHandlerPublisher -from src.infrastructure.log.event_handler import EventLogger +from user_service.application.user.queries.get_user_by_id import GetUserById, GetUserByIdHandler +from user_service.application.user.queries.get_user_by_username import GetUserByUsername, GetUserByUsernameHandler +from user_service.application.user.queries.get_users import GetUsers, GetUsersHandler +from user_service.domain.common.events import Event +from user_service.infrastructure.di import DiScope +from user_service.infrastructure.event_bus.event_handler import EventHandlerPublisher +from user_service.infrastructure.log.event_handler import EventLogger def init_mediator(di_builder: DiBuilder) -> Mediator: diff --git a/src/infrastructure/mediator/utils.py b/src/user_service/infrastructure/mediator/utils.py similarity index 100% rename from src/infrastructure/mediator/utils.py rename to src/user_service/infrastructure/mediator/utils.py diff --git a/src/infrastructure/message_broker/__init__.py b/src/user_service/infrastructure/message_broker/__init__.py similarity index 100% rename from src/infrastructure/message_broker/__init__.py rename to src/user_service/infrastructure/message_broker/__init__.py diff --git a/src/infrastructure/message_broker/config.py b/src/user_service/infrastructure/message_broker/config.py similarity index 100% rename from src/infrastructure/message_broker/config.py rename to src/user_service/infrastructure/message_broker/config.py diff --git a/src/infrastructure/message_broker/factories.py b/src/user_service/infrastructure/message_broker/factories.py similarity index 100% rename from src/infrastructure/message_broker/factories.py rename to src/user_service/infrastructure/message_broker/factories.py diff --git a/src/infrastructure/message_broker/interface.py b/src/user_service/infrastructure/message_broker/interface.py similarity index 100% rename from src/infrastructure/message_broker/interface.py rename to src/user_service/infrastructure/message_broker/interface.py diff --git a/src/infrastructure/message_broker/main.py b/src/user_service/infrastructure/message_broker/main.py similarity index 92% rename from src/infrastructure/message_broker/main.py rename to src/user_service/infrastructure/message_broker/main.py index 9c67c71..7e6dbad 100644 --- a/src/infrastructure/message_broker/main.py +++ b/src/user_service/infrastructure/message_broker/main.py @@ -3,7 +3,7 @@ import aio_pika from aio_pika.pool import Pool -from src.infrastructure.message_broker.factories import ChannelFactory, ConnectionFactory +from user_service.infrastructure.message_broker.factories import ChannelFactory, ConnectionFactory from .config import EventBusConfig diff --git a/src/infrastructure/message_broker/message.py b/src/user_service/infrastructure/message_broker/message.py similarity index 100% rename from src/infrastructure/message_broker/message.py rename to src/user_service/infrastructure/message_broker/message.py diff --git a/src/infrastructure/message_broker/message_broker.py b/src/user_service/infrastructure/message_broker/message_broker.py similarity index 100% rename from src/infrastructure/message_broker/message_broker.py rename to src/user_service/infrastructure/message_broker/message_broker.py diff --git a/src/infrastructure/message_broker/uow.py b/src/user_service/infrastructure/message_broker/uow.py similarity index 78% rename from src/infrastructure/message_broker/uow.py rename to src/user_service/infrastructure/message_broker/uow.py index 59f7be3..0a1d590 100644 --- a/src/infrastructure/message_broker/uow.py +++ b/src/user_service/infrastructure/message_broker/uow.py @@ -1,8 +1,8 @@ import aio_pika from aiormq import AMQPError -from src.application.common.exceptions import CommitError, RollbackError -from src.application.common.interfaces.uow import UnitOfWork +from user_service.application.common.exceptions import CommitError, RollbackError +from user_service.application.common.interfaces.uow import UnitOfWork class RabbitMQUoW(UnitOfWork): diff --git a/src/infrastructure/uow.py b/src/user_service/infrastructure/uow.py similarity index 71% rename from src/infrastructure/uow.py rename to src/user_service/infrastructure/uow.py index 6f84573..7d86816 100644 --- a/src/infrastructure/uow.py +++ b/src/user_service/infrastructure/uow.py @@ -1,8 +1,8 @@ from collections.abc import Sequence -from src.application.common.interfaces.uow import UnitOfWork -from src.infrastructure.db.uow import SQLAlchemyUoW -from src.infrastructure.message_broker.uow import RabbitMQUoW +from user_service.application.common.interfaces.uow import UnitOfWork +from user_service.infrastructure.db.uow import SQLAlchemyUoW +from user_service.infrastructure.message_broker.uow import RabbitMQUoW def build_uow(db_uow: SQLAlchemyUoW, rq_uow: RabbitMQUoW) -> UnitOfWork: diff --git a/src/presentation/__init__.py b/src/user_service/presentation/__init__.py similarity index 100% rename from src/presentation/__init__.py rename to src/user_service/presentation/__init__.py diff --git a/src/presentation/api/__init__.py b/src/user_service/presentation/api/__init__.py similarity index 100% rename from src/presentation/api/__init__.py rename to src/user_service/presentation/api/__init__.py diff --git a/src/presentation/api/__main__.py b/src/user_service/presentation/api/__main__.py similarity index 68% rename from src/presentation/api/__main__.py rename to src/user_service/presentation/api/__main__.py index 874488c..61a50a6 100644 --- a/src/presentation/api/__main__.py +++ b/src/user_service/presentation/api/__main__.py @@ -1,12 +1,12 @@ import asyncio import logging -from src.infrastructure.config_loader import load_config -from src.infrastructure.di import DiScope, init_di_builder, setup_di_builder -from src.infrastructure.event_bus.exchanges import declare_exchanges -from src.infrastructure.log import configure_logging -from src.infrastructure.mediator import init_mediator, setup_mediator -from src.presentation.api.main import init_api, run_api +from user_service.infrastructure.config_loader import load_config +from user_service.infrastructure.di import DiScope, init_di_builder, setup_di_builder +from user_service.infrastructure.event_bus.exchanges import declare_exchanges +from user_service.infrastructure.log import configure_logging +from user_service.infrastructure.mediator import init_mediator, setup_mediator +from user_service.presentation.api.main import init_api, run_api from .config import Config, setup_di_builder_config diff --git a/src/presentation/api/config.py b/src/user_service/presentation/api/config.py similarity index 82% rename from src/presentation/api/config.py rename to src/user_service/presentation/api/config.py index ddd56e2..a10bf26 100644 --- a/src/presentation/api/config.py +++ b/src/user_service/presentation/api/config.py @@ -4,10 +4,10 @@ from di.dependent import Dependent from didiator.interface.utils.di_builder import DiBuilder -from src.infrastructure.db import DBConfig -from src.infrastructure.di import DiScope -from src.infrastructure.log import LoggingConfig -from src.infrastructure.message_broker.config import EventBusConfig +from user_service.infrastructure.db import DBConfig +from user_service.infrastructure.di import DiScope +from user_service.infrastructure.log import LoggingConfig +from user_service.infrastructure.message_broker.config import EventBusConfig @dataclass diff --git a/src/presentation/api/controllers/__init__.py b/src/user_service/presentation/api/controllers/__init__.py similarity index 100% rename from src/presentation/api/controllers/__init__.py rename to src/user_service/presentation/api/controllers/__init__.py diff --git a/src/presentation/api/controllers/default.py b/src/user_service/presentation/api/controllers/default.py similarity index 100% rename from src/presentation/api/controllers/default.py rename to src/user_service/presentation/api/controllers/default.py diff --git a/src/presentation/api/controllers/exceptions.py b/src/user_service/presentation/api/controllers/exceptions.py similarity index 76% rename from src/presentation/api/controllers/exceptions.py rename to src/user_service/presentation/api/controllers/exceptions.py index e31329a..6363198 100644 --- a/src/presentation/api/controllers/exceptions.py +++ b/src/user_service/presentation/api/controllers/exceptions.py @@ -6,14 +6,18 @@ from starlette import status from starlette.requests import Request -from src.application.user.exceptions import UserIdAlreadyExistsError, UserIdNotExistError, UsernameNotExistError -from src.domain.common.exceptions import AppError -from src.domain.user.exceptions import UserIsDeletedError, UsernameAlreadyExistsError -from src.domain.user.value_objects.full_name import WrongNameValueError -from src.domain.user.value_objects.username import WrongUsernameValueError -from src.presentation.api.controllers.responses import ErrorResponse -from src.presentation.api.controllers.responses.base import ErrorData -from src.presentation.api.controllers.responses.orjson import ORJSONResponse +from user_service.application.user.exceptions import ( + UserIdAlreadyExistsError, + UserIdNotExistError, + UsernameNotExistError, +) +from user_service.domain.common.exceptions import AppError +from user_service.domain.user.exceptions import UserIsDeletedError, UsernameAlreadyExistsError +from user_service.domain.user.value_objects.full_name import WrongNameValueError +from user_service.domain.user.value_objects.username import WrongUsernameValueError +from user_service.presentation.api.controllers.responses import ErrorResponse +from user_service.presentation.api.controllers.responses.base import ErrorData +from user_service.presentation.api.controllers.responses.orjson import ORJSONResponse logger = logging.getLogger(__name__) diff --git a/src/presentation/api/controllers/healthcheck.py b/src/user_service/presentation/api/controllers/healthcheck.py similarity index 100% rename from src/presentation/api/controllers/healthcheck.py rename to src/user_service/presentation/api/controllers/healthcheck.py diff --git a/src/presentation/api/controllers/main.py b/src/user_service/presentation/api/controllers/main.py similarity index 100% rename from src/presentation/api/controllers/main.py rename to src/user_service/presentation/api/controllers/main.py diff --git a/src/presentation/api/controllers/requests/__init__.py b/src/user_service/presentation/api/controllers/requests/__init__.py similarity index 100% rename from src/presentation/api/controllers/requests/__init__.py rename to src/user_service/presentation/api/controllers/requests/__init__.py diff --git a/src/presentation/api/controllers/requests/user.py b/src/user_service/presentation/api/controllers/requests/user.py similarity index 100% rename from src/presentation/api/controllers/requests/user.py rename to src/user_service/presentation/api/controllers/requests/user.py diff --git a/src/presentation/api/controllers/responses/__init__.py b/src/user_service/presentation/api/controllers/responses/__init__.py similarity index 100% rename from src/presentation/api/controllers/responses/__init__.py rename to src/user_service/presentation/api/controllers/responses/__init__.py diff --git a/src/presentation/api/controllers/responses/base.py b/src/user_service/presentation/api/controllers/responses/base.py similarity index 100% rename from src/presentation/api/controllers/responses/base.py rename to src/user_service/presentation/api/controllers/responses/base.py diff --git a/src/presentation/api/controllers/responses/orjson.py b/src/user_service/presentation/api/controllers/responses/orjson.py similarity index 100% rename from src/presentation/api/controllers/responses/orjson.py rename to src/user_service/presentation/api/controllers/responses/orjson.py diff --git a/src/presentation/api/controllers/user.py b/src/user_service/presentation/api/controllers/user.py similarity index 78% rename from src/presentation/api/controllers/user.py rename to src/user_service/presentation/api/controllers/user.py index adddc0a..8a0f668 100644 --- a/src/presentation/api/controllers/user.py +++ b/src/user_service/presentation/api/controllers/user.py @@ -4,21 +4,29 @@ from didiator import CommandMediator, Mediator, QueryMediator from fastapi import APIRouter, Depends, Query, status -from src.application.common.pagination.dto import Pagination, SortOrder -from src.application.user import dto -from src.application.user.commands import CreateUser, DeleteUser, SetUserFullName -from src.application.user.commands.set_user_username import SetUserUsername -from src.application.user.exceptions import UserIdAlreadyExistsError, UserIdNotExistError, UsernameNotExistError -from src.application.user.interfaces.persistence import GetUsersFilters -from src.application.user.queries import GetUserById, GetUserByUsername, GetUsers -from src.domain.common.constants import Empty -from src.domain.user.exceptions import UserIsDeletedError, UsernameAlreadyExistsError -from src.domain.user.value_objects.full_name import EmptyNameError, TooLongNameError, WrongNameFormatError -from src.domain.user.value_objects.username import EmptyUsernameError, TooLongUsernameError, WrongUsernameFormatError -from src.presentation.api.controllers import requests -from src.presentation.api.controllers.responses import ErrorResponse -from src.presentation.api.controllers.responses.base import OkResponse -from src.presentation.api.providers.stub import Stub +from user_service.application.common.pagination.dto import Pagination, SortOrder +from user_service.application.user import dto +from user_service.application.user.commands import CreateUser, DeleteUser, SetUserFullName +from user_service.application.user.commands.set_user_username import SetUserUsername +from user_service.application.user.exceptions import ( + UserIdAlreadyExistsError, + UserIdNotExistError, + UsernameNotExistError, +) +from user_service.application.user.interfaces.persistence import GetUsersFilters +from user_service.application.user.queries import GetUserById, GetUserByUsername, GetUsers +from user_service.domain.common.constants import Empty +from user_service.domain.user.exceptions import UserIsDeletedError, UsernameAlreadyExistsError +from user_service.domain.user.value_objects.full_name import EmptyNameError, TooLongNameError, WrongNameFormatError +from user_service.domain.user.value_objects.username import ( + EmptyUsernameError, + TooLongUsernameError, + WrongUsernameFormatError, +) +from user_service.presentation.api.controllers import requests +from user_service.presentation.api.controllers.responses import ErrorResponse +from user_service.presentation.api.controllers.responses.base import OkResponse +from user_service.presentation.api.providers.stub import Stub user_router = APIRouter( prefix="/users", diff --git a/src/presentation/api/main.py b/src/user_service/presentation/api/main.py similarity index 83% rename from src/presentation/api/main.py rename to src/user_service/presentation/api/main.py index bccc427..6273884 100644 --- a/src/presentation/api/main.py +++ b/src/user_service/presentation/api/main.py @@ -7,9 +7,9 @@ from fastapi import FastAPI from fastapi.responses import ORJSONResponse -from src.presentation.api.controllers import setup_controllers -from src.presentation.api.middlewares import setup_middlewares -from src.presentation.api.providers import setup_providers +from user_service.presentation.api.controllers import setup_controllers +from user_service.presentation.api.middlewares import setup_middlewares +from user_service.presentation.api.providers import setup_providers from .config import APIConfig diff --git a/src/presentation/api/middlewares/__init__.py b/src/user_service/presentation/api/middlewares/__init__.py similarity index 100% rename from src/presentation/api/middlewares/__init__.py rename to src/user_service/presentation/api/middlewares/__init__.py diff --git a/src/presentation/api/middlewares/context.py b/src/user_service/presentation/api/middlewares/context.py similarity index 100% rename from src/presentation/api/middlewares/context.py rename to src/user_service/presentation/api/middlewares/context.py diff --git a/src/presentation/api/middlewares/main.py b/src/user_service/presentation/api/middlewares/main.py similarity index 100% rename from src/presentation/api/middlewares/main.py rename to src/user_service/presentation/api/middlewares/main.py diff --git a/src/presentation/api/middlewares/structlog.py b/src/user_service/presentation/api/middlewares/structlog.py similarity index 100% rename from src/presentation/api/middlewares/structlog.py rename to src/user_service/presentation/api/middlewares/structlog.py diff --git a/src/presentation/api/providers/__init__.py b/src/user_service/presentation/api/providers/__init__.py similarity index 100% rename from src/presentation/api/providers/__init__.py rename to src/user_service/presentation/api/providers/__init__.py diff --git a/src/presentation/api/providers/di.py b/src/user_service/presentation/api/providers/di.py similarity index 92% rename from src/presentation/api/providers/di.py rename to src/user_service/presentation/api/providers/di.py index 6fba965..45b5a53 100644 --- a/src/presentation/api/providers/di.py +++ b/src/user_service/presentation/api/providers/di.py @@ -4,7 +4,7 @@ from didiator.interface.utils.di_builder import DiBuilder from fastapi import Depends -from src.infrastructure.di import DiScope +from user_service.infrastructure.di import DiScope def get_di_builder() -> DiBuilder: diff --git a/src/presentation/api/providers/main.py b/src/user_service/presentation/api/providers/main.py similarity index 100% rename from src/presentation/api/providers/main.py rename to src/user_service/presentation/api/providers/main.py diff --git a/src/presentation/api/providers/mediator.py b/src/user_service/presentation/api/providers/mediator.py similarity index 89% rename from src/presentation/api/providers/mediator.py rename to src/user_service/presentation/api/providers/mediator.py index 8666067..82bb234 100644 --- a/src/presentation/api/providers/mediator.py +++ b/src/user_service/presentation/api/providers/mediator.py @@ -4,7 +4,7 @@ from didiator import Mediator from fastapi import Depends -from src.infrastructure.mediator import get_mediator +from user_service.infrastructure.mediator import get_mediator from .di import get_di_state diff --git a/src/presentation/api/providers/stub.py b/src/user_service/presentation/api/providers/stub.py similarity index 100% rename from src/presentation/api/providers/stub.py rename to src/user_service/presentation/api/providers/stub.py diff --git a/tests/mocks/uow.py b/tests/mocks/uow.py index c8c9c81..4c51cf5 100644 --- a/tests/mocks/uow.py +++ b/tests/mocks/uow.py @@ -1,4 +1,4 @@ -from src.application.common.interfaces.uow import UnitOfWork +from user_service.application.common.interfaces.uow import UnitOfWork class UnitOfWorkMock(UnitOfWork): diff --git a/tests/mocks/user_reader.py b/tests/mocks/user_reader.py index 8bdc4cc..5d0862a 100644 --- a/tests/mocks/user_reader.py +++ b/tests/mocks/user_reader.py @@ -1,12 +1,12 @@ from collections.abc import Sequence from uuid import UUID -from src.application.common.pagination.dto import Pagination, PaginationResult, SortOrder -from src.application.user import dto -from src.application.user.exceptions import UserIdNotExistError, UsernameNotExistError -from src.application.user.interfaces import UserReader -from src.application.user.interfaces.persistence import GetUsersFilters -from src.domain.common.constants import Empty +from user_service.application.common.pagination.dto import Pagination, PaginationResult, SortOrder +from user_service.application.user import dto +from user_service.application.user.exceptions import UserIdNotExistError, UsernameNotExistError +from user_service.application.user.interfaces import UserReader +from user_service.application.user.interfaces.persistence import GetUsersFilters +from user_service.domain.common.constants import Empty class UserReaderMock(UserReader): diff --git a/tests/mocks/user_repo.py b/tests/mocks/user_repo.py index da59e34..288def9 100644 --- a/tests/mocks/user_repo.py +++ b/tests/mocks/user_repo.py @@ -1,7 +1,7 @@ -from src.application.user.exceptions import UserIdNotExistError -from src.application.user.interfaces import UserRepo -from src.domain.user import entities -from src.domain.user.value_objects import UserId, Username +from user_service.application.user.exceptions import UserIdNotExistError +from user_service.application.user.interfaces import UserRepo +from user_service.domain.user import entities +from user_service.domain.user.value_objects import UserId, Username class UserRepoMock(UserRepo): diff --git a/tests/unit/application/commands/test_create_user.py b/tests/unit/application/commands/test_create_user.py index e4129c2..f7f380c 100644 --- a/tests/unit/application/commands/test_create_user.py +++ b/tests/unit/application/commands/test_create_user.py @@ -1,13 +1,13 @@ from uuid import UUID import pytest +from user_service.application.user.commands import CreateUser, CreateUserHandler +from user_service.domain.user.entities import User +from user_service.domain.user.events import UserCreated +from user_service.domain.user.exceptions import UsernameAlreadyExistsError +from user_service.domain.user.value_objects import FullName, UserId, Username +from user_service.domain.user.value_objects.deleted_status import DeletionTime -from src.application.user.commands import CreateUser, CreateUserHandler -from src.domain.user.entities import User -from src.domain.user.events import UserCreated -from src.domain.user.exceptions import UsernameAlreadyExistsError -from src.domain.user.value_objects import FullName, UserId, Username -from src.domain.user.value_objects.deleted_status import DeletionTime from tests.mocks import EventMediatorMock, UserRepoMock from tests.mocks.uow import UnitOfWorkMock diff --git a/tests/unit/application/commands/test_delete_user.py b/tests/unit/application/commands/test_delete_user.py index d02f8ec..a795d05 100644 --- a/tests/unit/application/commands/test_delete_user.py +++ b/tests/unit/application/commands/test_delete_user.py @@ -1,14 +1,14 @@ from uuid import UUID import pytest +from user_service.application.user.commands import DeleteUser, DeleteUserHandler +from user_service.application.user.exceptions import UserIdNotExistError +from user_service.domain.user import User +from user_service.domain.user.events import UserDeleted +from user_service.domain.user.exceptions import UserIsDeletedError +from user_service.domain.user.value_objects import FullName, UserId, Username +from user_service.domain.user.value_objects.deleted_status import DeletionTime -from src.application.user.commands import DeleteUser, DeleteUserHandler -from src.application.user.exceptions import UserIdNotExistError -from src.domain.user import User -from src.domain.user.events import UserDeleted -from src.domain.user.exceptions import UserIsDeletedError -from src.domain.user.value_objects import FullName, UserId, Username -from src.domain.user.value_objects.deleted_status import DeletionTime from tests.mocks import EventMediatorMock, UserRepoMock from tests.mocks.uow import UnitOfWorkMock diff --git a/tests/unit/application/commands/test_set_user_full_name.py b/tests/unit/application/commands/test_set_user_full_name.py index 49e0290..8e0ac82 100644 --- a/tests/unit/application/commands/test_set_user_full_name.py +++ b/tests/unit/application/commands/test_set_user_full_name.py @@ -1,13 +1,13 @@ from uuid import UUID import pytest +from user_service.application.user.commands import SetUserFullName, SetUserFullNameHandler +from user_service.application.user.exceptions import UserIdNotExistError +from user_service.domain.user import User +from user_service.domain.user.events import FullNameUpdated +from user_service.domain.user.exceptions import UserIsDeletedError +from user_service.domain.user.value_objects import FullName, UserId, Username -from src.application.user.commands import SetUserFullName, SetUserFullNameHandler -from src.application.user.exceptions import UserIdNotExistError -from src.domain.user import User -from src.domain.user.events import FullNameUpdated -from src.domain.user.exceptions import UserIsDeletedError -from src.domain.user.value_objects import FullName, UserId, Username from tests.mocks import EventMediatorMock, UserRepoMock from tests.mocks.uow import UnitOfWorkMock diff --git a/tests/unit/application/commands/test_set_user_username.py b/tests/unit/application/commands/test_set_user_username.py index 517f965..101abe3 100644 --- a/tests/unit/application/commands/test_set_user_username.py +++ b/tests/unit/application/commands/test_set_user_username.py @@ -1,13 +1,13 @@ from uuid import UUID import pytest +from user_service.application.user.commands import SetUserUsername, SetUserUsernameHandler +from user_service.application.user.exceptions import UserIdNotExistError +from user_service.domain.user import User +from user_service.domain.user.events import UsernameUpdated +from user_service.domain.user.exceptions import UserIsDeletedError, UsernameAlreadyExistsError +from user_service.domain.user.value_objects import FullName, UserId, Username -from src.application.user.commands import SetUserUsername, SetUserUsernameHandler -from src.application.user.exceptions import UserIdNotExistError -from src.domain.user import User -from src.domain.user.events import UsernameUpdated -from src.domain.user.exceptions import UserIsDeletedError, UsernameAlreadyExistsError -from src.domain.user.value_objects import FullName, UserId, Username from tests.mocks import EventMediatorMock, UserRepoMock from tests.mocks.uow import UnitOfWorkMock diff --git a/tests/unit/application/queries/test_get_user_by_id.py b/tests/unit/application/queries/test_get_user_by_id.py index 2e5fca7..62a8160 100644 --- a/tests/unit/application/queries/test_get_user_by_id.py +++ b/tests/unit/application/queries/test_get_user_by_id.py @@ -1,10 +1,10 @@ from uuid import UUID import pytest +from user_service.application.user import dto +from user_service.application.user.exceptions import UserIdNotExistError +from user_service.application.user.queries.get_user_by_id import GetUserById, GetUserByIdHandler -from src.application.user import dto -from src.application.user.exceptions import UserIdNotExistError -from src.application.user.queries.get_user_by_id import GetUserById, GetUserByIdHandler from tests.mocks.user_reader import UserReaderMock diff --git a/tests/unit/application/queries/test_get_user_by_username.py b/tests/unit/application/queries/test_get_user_by_username.py index 551494b..c8f1a80 100644 --- a/tests/unit/application/queries/test_get_user_by_username.py +++ b/tests/unit/application/queries/test_get_user_by_username.py @@ -1,10 +1,10 @@ from uuid import UUID import pytest +from user_service.application.user import dto +from user_service.application.user.exceptions import UsernameNotExistError +from user_service.application.user.queries.get_user_by_username import GetUserByUsername, GetUserByUsernameHandler -from src.application.user import dto -from src.application.user.exceptions import UsernameNotExistError -from src.application.user.queries.get_user_by_username import GetUserByUsername, GetUserByUsernameHandler from tests.mocks.user_reader import UserReaderMock diff --git a/tests/unit/application/queries/test_get_users.py b/tests/unit/application/queries/test_get_users.py index 9d638c5..0048625 100644 --- a/tests/unit/application/queries/test_get_users.py +++ b/tests/unit/application/queries/test_get_users.py @@ -2,12 +2,12 @@ from uuid import UUID import pytest +from user_service.application.common.pagination.dto import Pagination, SortOrder +from user_service.application.user import dto +from user_service.application.user.interfaces.persistence import GetUsersFilters +from user_service.application.user.queries.get_users import GetUsers, GetUsersHandler +from user_service.domain.common.constants import Empty -from src.application.common.pagination.dto import Pagination, SortOrder -from src.application.user import dto -from src.application.user.interfaces.persistence import GetUsersFilters -from src.application.user.queries.get_users import GetUsers, GetUsersHandler -from src.domain.common.constants import Empty from tests.mocks.user_reader import UserReaderMock From ed4075cbe4dd4dfa06d0087d78f8a5278681eff5 Mon Sep 17 00:00:00 2001 From: SamWarden Date: Mon, 14 Apr 2025 14:48:14 +0300 Subject: [PATCH 05/20] feat(uv): use the `uv` tool instead of the `poetry` --- .github/workflows/test.yaml | 80 +- Dockerfile | 45 +- docker-compose.yaml | 7 +- poetry.lock | 2004 ----------------------------------- pyproject.toml | 92 +- 5 files changed, 110 insertions(+), 2118 deletions(-) delete mode 100644 poetry.lock diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 305417d..e228e70 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,58 +1,64 @@ -# This workflow will install Python dependencies, run tests and lint with a single version of Python +# This workflow will install Python dependencies, run tests and linters # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions -name: UserServiceTest +name: CI on: workflow_call: - - pull_request: - branches: [ master, dev ] push: branches: [ master, dev ] + pull_request: + branches: [ master, dev ] jobs: - build: + setup: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Install poetry - run: pipx install poetry - - name: Set up Python 3.11 - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + version: "0.5.29" + enable-cache: true + - name: Install project dependencies + run: uv sync --all-groups --all-extras + - name: Cache venv + uses: actions/cache@v4 with: - python-version: "3.11" - cache: "poetry" - cache-dependency-path: poetry.lock - - name: Install dependencies - run: poetry install --with dev,test,lint + path: | + ./.venv + ~/.local/share/uv + key: venv-${{ runner.os }}-${{ hashFiles('**/uv.lock') }} lint: - needs: build + needs: setup runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Install poetry - run: pipx install poetry - - name: Set up Python 3.11 - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + version: "0.5.29" + enable-cache: true + - name: Restore cached venv + uses: actions/cache@v4 with: - python-version: "3.11" - cache: "poetry" - cache-dependency-path: poetry.lock + path: | + ./.venv + ~/.local/share/uv + key: venv-${{ runner.os }}-${{ hashFiles('**/uv.lock') }} - name: Lint with pre-commit - run: poetry run pre-commit run --all-files + run: ./.venv/bin/pre-commit run --all-files test: - needs: build + needs: setup runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Install poetry - run: pipx install poetry - - name: Set up Python 3.11 - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - name: Restore cached venv + uses: actions/cache@v4 with: - python-version: "3.11" - cache: "poetry" - cache-dependency-path: poetry.lock - - name: Test with pytest - run: poetry run pytest + path: | + ./.venv + ~/.local/share/uv + key: venv-${{ runner.os }}-${{ hashFiles('**/uv.lock') }} + - name: Run tests with pytest + run: ./.venv/bin/pytest diff --git a/Dockerfile b/Dockerfile index 26f6a66..15e261a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,37 +1,34 @@ -FROM python:3.11-slim-buster AS python-base +FROM python:3.13-slim-bookworm AS python-base ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 \ - PIP_NO_CACHE_DIR=off \ - PIP_DISABLE_PIP_VERSION_CHECK=on \ + PIP_NO_CACHE_DIR=1 \ + PIP_DISABLE_PIP_VERSION_CHECK=1 \ PIP_DEFAULT_TIMEOUT=100 \ - POETRY_HOME="/opt/poetry" \ - POETRY_VIRTUALENVS_IN_PROJECT=true \ - POETRY_NO_INTERACTION=1 \ - PYSETUP_PATH="/opt/pysetup" \ - VENV_PATH="/opt/pysetup/.venv" + APP_PATH="/app" \ + UV_VERSION="0.6.14" -ENV PATH="$POETRY_HOME/bin:$VENV_PATH/bin:$PATH" +ENV VIRTUAL_ENV="$APP_PATH/.venv" +ENV PATH="$VIRTUAL_ENV/bin:$PATH" +FROM python-base AS builder -FROM python-base AS builder-base RUN apt-get update \ - && apt-get install -y --no-install-recommends gcc git + && apt-get install -y --no-install-recommends gcc git \ + && rm -rf /var/lib/apt/lists/ -WORKDIR $PYSETUP_PATH -COPY ./pyproject.toml ./poetry.lock ./ -RUN pip install --no-cache-dir --upgrade pip==24.0 \ - && pip install --no-cache-dir setuptools==69.5.1 wheel==0.43.0 \ - && pip install --no-cache-dir poetry==1.8.2 +WORKDIR $APP_PATH -RUN poetry install --no-dev +RUN pip install --no-cache-dir "uv==$UV_VERSION" -FROM python-base AS production -COPY --from=builder-base $PYSETUP_PATH $PYSETUP_PATH -RUN apt-get update \ - && apt-get install -y --no-install-recommends curl \ - && rm -rf /var/lib/apt/lists/* +COPY ./pyproject.toml ./uv.lock ./ +RUN uv venv -p 3.13 \ + && uv sync --all-extras --no-install-project +COPY ./src ./src +RUN uv sync --all-extras --no-editable + +FROM python-base AS runner + +COPY --from=builder $VIRTUAL_ENV $VIRTUAL_ENV -WORKDIR /app -COPY _src/user_service /app/user_service CMD ["python", "-Om", "user_service"] diff --git a/docker-compose.yaml b/docker-compose.yaml index 77914ba..0545d40 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,5 +1,3 @@ -version: "3.9" - services: api: profiles: [ "api" ] @@ -21,10 +19,9 @@ services: - user_service.postgres.network - user_service.rabbitmq.network volumes: - - ./config:/app/config:ro + - ./config:/config:ro environment: - - CONFIG_PATH=${CONFIG_PATH:-./config/prod_config.toml} - command: ["python", "-Om", "src"] + - CONFIG_PATH=${CONFIG_PATH:-/config/prod_config.toml} healthcheck: test: ["CMD-SHELL", "curl -fsSL http://localhost:5000/healthcheck"] interval: 10s diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index 3a08701..0000000 --- a/poetry.lock +++ /dev/null @@ -1,2004 +0,0 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. - -[[package]] -name = "adaptix" -version = "3.0.0b5" -description = "An extremely flexible and configurable data model conversion library" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "adaptix-3.0.0b5-py3-none-any.whl", hash = "sha256:3bf98b05a8a65f28f78515eac035c1c275ab689cf318db8ae5a06e518aa8af67"}, - {file = "adaptix-3.0.0b5.tar.gz", hash = "sha256:426fb1ced59f06b80e59265dabb70f68567c6dfd7fb322fa96cd99d1178bceaa"}, -] - -[package.extras] -attrs = ["attrs (>=21.3.0)"] -attrs-strict = ["attrs (>=21.3.0,<=23.2.0)"] -pydantic = ["pydantic (>=2.0.0)"] -pydantic-strict = ["pydantic (>=2.0.0,<=2.7.0)"] -sqlalchemy = ["sqlalchemy (>=2.0.0)"] -sqlalchemy-strict = ["sqlalchemy (>=2.0.0,<=2.0.29)"] - -[[package]] -name = "aio-pika" -version = "9.4.1" -description = "Wrapper around the aiormq for asyncio and humans" -category = "main" -optional = false -python-versions = ">=3.8,<4.0" -files = [ - {file = "aio_pika-9.4.1-py3-none-any.whl", hash = "sha256:2d4cec5a5d32005a71e31faaf5be58ee993fa29b8243a5d04b8f4a73df272fe8"}, - {file = "aio_pika-9.4.1.tar.gz", hash = "sha256:d177404a27aa797a2ac935f6aa2bbb287fe1f0ddf5628ca20a237b7db3ea6324"}, -] - -[package.dependencies] -aiormq = ">=6.8.0,<6.9.0" -yarl = "*" - -[[package]] -name = "aiormq" -version = "6.8.0" -description = "Pure python AMQP asynchronous client library" -category = "main" -optional = false -python-versions = ">=3.8,<4.0" -files = [ - {file = "aiormq-6.8.0-py3-none-any.whl", hash = "sha256:9a16174dcae4078c957a773d2f02d3dfd6c2fcf12c909dc244333a458f2aeab0"}, - {file = "aiormq-6.8.0.tar.gz", hash = "sha256:198f9c7430feb7bc491016099a06266dc45880b6b1de3925d410fde6541a66fb"}, -] - -[package.dependencies] -pamqp = "3.3.0" -yarl = "*" - -[[package]] -name = "alembic" -version = "1.13.1" -description = "A database migration tool for SQLAlchemy." -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "alembic-1.13.1-py3-none-any.whl", hash = "sha256:2edcc97bed0bd3272611ce3a98d98279e9c209e7186e43e75bbb1b2bdfdbcc43"}, - {file = "alembic-1.13.1.tar.gz", hash = "sha256:4932c8558bf68f2ee92b9bbcb8218671c627064d5b08939437af6d77dc05e595"}, -] - -[package.dependencies] -Mako = "*" -SQLAlchemy = ">=1.3.0" -typing-extensions = ">=4" - -[package.extras] -tz = ["backports.zoneinfo"] - -[[package]] -name = "annotated-types" -version = "0.6.0" -description = "Reusable constraint types to use with typing.Annotated" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, - {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, -] - -[[package]] -name = "anyio" -version = "4.3.0" -description = "High level compatibility layer for multiple asynchronous event loop implementations" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"}, - {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"}, -] - -[package.dependencies] -idna = ">=2.8" -sniffio = ">=1.1" - -[package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (>=0.23)"] - -[[package]] -name = "astroid" -version = "3.1.0" -description = "An abstract syntax tree for Python with inference support." -category = "dev" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "astroid-3.1.0-py3-none-any.whl", hash = "sha256:951798f922990137ac090c53af473db7ab4e70c770e6d7fae0cec59f74411819"}, - {file = "astroid-3.1.0.tar.gz", hash = "sha256:ac248253bfa4bd924a0de213707e7ebeeb3138abeb48d798784ead1e56d419d4"}, -] - -[[package]] -name = "async-timeout" -version = "4.0.3" -description = "Timeout context manager for asyncio programs" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, - {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, -] - -[[package]] -name = "asyncpg" -version = "0.29.0" -description = "An asyncio PostgreSQL driver" -category = "main" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "asyncpg-0.29.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72fd0ef9f00aeed37179c62282a3d14262dbbafb74ec0ba16e1b1864d8a12169"}, - {file = "asyncpg-0.29.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52e8f8f9ff6e21f9b39ca9f8e3e33a5fcdceaf5667a8c5c32bee158e313be385"}, - {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e6823a7012be8b68301342ba33b4740e5a166f6bbda0aee32bc01638491a22"}, - {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:746e80d83ad5d5464cfbf94315eb6744222ab00aa4e522b704322fb182b83610"}, - {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ff8e8109cd6a46ff852a5e6bab8b0a047d7ea42fcb7ca5ae6eaae97d8eacf397"}, - {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97eb024685b1d7e72b1972863de527c11ff87960837919dac6e34754768098eb"}, - {file = "asyncpg-0.29.0-cp310-cp310-win32.whl", hash = "sha256:5bbb7f2cafd8d1fa3e65431833de2642f4b2124be61a449fa064e1a08d27e449"}, - {file = "asyncpg-0.29.0-cp310-cp310-win_amd64.whl", hash = "sha256:76c3ac6530904838a4b650b2880f8e7af938ee049e769ec2fba7cd66469d7772"}, - {file = "asyncpg-0.29.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4900ee08e85af01adb207519bb4e14b1cae8fd21e0ccf80fac6aa60b6da37b4"}, - {file = "asyncpg-0.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a65c1dcd820d5aea7c7d82a3fdcb70e096f8f70d1a8bf93eb458e49bfad036ac"}, - {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b52e46f165585fd6af4863f268566668407c76b2c72d366bb8b522fa66f1870"}, - {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc600ee8ef3dd38b8d67421359779f8ccec30b463e7aec7ed481c8346decf99f"}, - {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:039a261af4f38f949095e1e780bae84a25ffe3e370175193174eb08d3cecab23"}, - {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6feaf2d8f9138d190e5ec4390c1715c3e87b37715cd69b2c3dfca616134efd2b"}, - {file = "asyncpg-0.29.0-cp311-cp311-win32.whl", hash = "sha256:1e186427c88225ef730555f5fdda6c1812daa884064bfe6bc462fd3a71c4b675"}, - {file = "asyncpg-0.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfe73ffae35f518cfd6e4e5f5abb2618ceb5ef02a2365ce64f132601000587d3"}, - {file = "asyncpg-0.29.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6011b0dc29886ab424dc042bf9eeb507670a3b40aece3439944006aafe023178"}, - {file = "asyncpg-0.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b544ffc66b039d5ec5a7454667f855f7fec08e0dfaf5a5490dfafbb7abbd2cfb"}, - {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d84156d5fb530b06c493f9e7635aa18f518fa1d1395ef240d211cb563c4e2364"}, - {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54858bc25b49d1114178d65a88e48ad50cb2b6f3e475caa0f0c092d5f527c106"}, - {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bde17a1861cf10d5afce80a36fca736a86769ab3579532c03e45f83ba8a09c59"}, - {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:37a2ec1b9ff88d8773d3eb6d3784dc7e3fee7756a5317b67f923172a4748a175"}, - {file = "asyncpg-0.29.0-cp312-cp312-win32.whl", hash = "sha256:bb1292d9fad43112a85e98ecdc2e051602bce97c199920586be83254d9dafc02"}, - {file = "asyncpg-0.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:2245be8ec5047a605e0b454c894e54bf2ec787ac04b1cb7e0d3c67aa1e32f0fe"}, - {file = "asyncpg-0.29.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0009a300cae37b8c525e5b449233d59cd9868fd35431abc470a3e364d2b85cb9"}, - {file = "asyncpg-0.29.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cad1324dbb33f3ca0cd2074d5114354ed3be2b94d48ddfd88af75ebda7c43cc"}, - {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:012d01df61e009015944ac7543d6ee30c2dc1eb2f6b10b62a3f598beb6531548"}, - {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000c996c53c04770798053e1730d34e30cb645ad95a63265aec82da9093d88e7"}, - {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e0bfe9c4d3429706cf70d3249089de14d6a01192d617e9093a8e941fea8ee775"}, - {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:642a36eb41b6313ffa328e8a5c5c2b5bea6ee138546c9c3cf1bffaad8ee36dd9"}, - {file = "asyncpg-0.29.0-cp38-cp38-win32.whl", hash = "sha256:a921372bbd0aa3a5822dd0409da61b4cd50df89ae85150149f8c119f23e8c408"}, - {file = "asyncpg-0.29.0-cp38-cp38-win_amd64.whl", hash = "sha256:103aad2b92d1506700cbf51cd8bb5441e7e72e87a7b3a2ca4e32c840f051a6a3"}, - {file = "asyncpg-0.29.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5340dd515d7e52f4c11ada32171d87c05570479dc01dc66d03ee3e150fb695da"}, - {file = "asyncpg-0.29.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e17b52c6cf83e170d3d865571ba574577ab8e533e7361a2b8ce6157d02c665d3"}, - {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f100d23f273555f4b19b74a96840aa27b85e99ba4b1f18d4ebff0734e78dc090"}, - {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48e7c58b516057126b363cec8ca02b804644fd012ef8e6c7e23386b7d5e6ce83"}, - {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9ea3f24eb4c49a615573724d88a48bd1b7821c890c2effe04f05382ed9e8810"}, - {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8d36c7f14a22ec9e928f15f92a48207546ffe68bc412f3be718eedccdf10dc5c"}, - {file = "asyncpg-0.29.0-cp39-cp39-win32.whl", hash = "sha256:797ab8123ebaed304a1fad4d7576d5376c3a006a4100380fb9d517f0b59c1ab2"}, - {file = "asyncpg-0.29.0-cp39-cp39-win_amd64.whl", hash = "sha256:cce08a178858b426ae1aa8409b5cc171def45d4293626e7aa6510696d46decd8"}, - {file = "asyncpg-0.29.0.tar.gz", hash = "sha256:d1c49e1f44fffafd9a55e1a9b101590859d881d639ea2922516f5d9c512d354e"}, -] - -[package.dependencies] -async-timeout = {version = ">=4.0.3", markers = "python_version < \"3.12.0\""} - -[package.extras] -docs = ["Sphinx (>=5.3.0,<5.4.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["flake8 (>=6.1,<7.0)", "uvloop (>=0.15.3)"] - -[[package]] -name = "certifi" -version = "2024.2.2" -description = "Python package for providing Mozilla's CA Bundle." -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ - {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, - {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, -] - -[[package]] -name = "cfgv" -version = "3.4.0" -description = "Validate configuration and produce human readable error messages." -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, - {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.3.2" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "dev" -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, -] - -[[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "di" -version = "0.75.4" -description = "Dependency injection toolkit" -category = "main" -optional = false -python-versions = ">=3.7,<4" -files = [ - {file = "di-0.75.4-py3-none-any.whl", hash = "sha256:2afb862083072ab37e54dc9f2b0bcfc879b1282367db6e97f68f698e6ee07b34"}, - {file = "di-0.75.4.tar.gz", hash = "sha256:7d13e3a3486c84d8f3f770f1b48fe5da2394922aa1f95d28070b287acbb04c09"}, -] - -[package.dependencies] -anyio = {version = ">=3.5.0", optional = true, markers = "extra == \"anyio\""} -graphlib2 = ">=0.4.1,<0.5.0" - -[package.extras] -anyio = ["anyio (>=3.5.0)"] - -[[package]] -name = "didiator" -version = "0.3.1" -description = "A library that implements the Mediator pattern and uses DI library" -category = "main" -optional = false -python-versions = ">=3.10,<4.0" -files = [ - {file = "didiator-0.3.1-py3-none-any.whl", hash = "sha256:0fd3d3af901241a457beae81853e91444de46cb2a4fcbeb9ed1fb91fb4ad65b4"}, - {file = "didiator-0.3.1.tar.gz", hash = "sha256:05375f397ce73ee52934b6da823d439a263cde33579e10589d93944eb9c2e0b3"}, -] - -[package.dependencies] -di = {version = ">=0.75.0,<0.76.0", extras = ["anyio"], optional = true, markers = "extra == \"di\""} - -[package.extras] -di = ["di[anyio] (>=0.75.0,<0.76.0)"] - -[[package]] -name = "dill" -version = "0.3.8" -description = "serialize all of Python" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"}, - {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"}, -] - -[package.extras] -graph = ["objgraph (>=1.7.2)"] -profile = ["gprof2dot (>=2022.7.29)"] - -[[package]] -name = "distlib" -version = "0.3.8" -description = "Distribution utilities" -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, - {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, -] - -[[package]] -name = "docker" -version = "7.0.0" -description = "A Python library for the Docker Engine API." -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "docker-7.0.0-py3-none-any.whl", hash = "sha256:12ba681f2777a0ad28ffbcc846a69c31b4dfd9752b47eb425a274ee269c5e14b"}, - {file = "docker-7.0.0.tar.gz", hash = "sha256:323736fb92cd9418fc5e7133bc953e11a9da04f4483f828b527db553f1e7e5a3"}, -] - -[package.dependencies] -packaging = ">=14.0" -pywin32 = {version = ">=304", markers = "sys_platform == \"win32\""} -requests = ">=2.26.0" -urllib3 = ">=1.26.0" - -[package.extras] -ssh = ["paramiko (>=2.4.3)"] -websockets = ["websocket-client (>=1.3.0)"] - -[[package]] -name = "execnet" -version = "2.1.1" -description = "execnet: rapid multi-Python deployment" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, - {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, -] - -[package.extras] -testing = ["hatch", "pre-commit", "pytest", "tox"] - -[[package]] -name = "fastapi" -version = "0.110.3" -description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "fastapi-0.110.3-py3-none-any.whl", hash = "sha256:fd7600612f755e4050beb74001310b5a7e1796d149c2ee363124abdfa0289d32"}, - {file = "fastapi-0.110.3.tar.gz", hash = "sha256:555700b0159379e94fdbfc6bb66a0f1c43f4cf7060f25239af3d84b63a656626"}, -] - -[package.dependencies] -pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.37.2,<0.38.0" -typing-extensions = ">=4.8.0" - -[package.extras] -all = ["email_validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] - -[[package]] -name = "filelock" -version = "3.14.0" -description = "A platform independent file lock." -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"}, - {file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"}, -] - -[package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] -typing = ["typing-extensions (>=4.8)"] - -[[package]] -name = "graphlib2" -version = "0.4.7" -description = "Rust port of the Python stdlib graphlib modules" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "graphlib2-0.4.7-cp37-abi3-macosx_10_7_x86_64.whl", hash = "sha256:483710733215783cdc76452ccde1247af8f697685c9c1dfd9bb9ff4f52d990ee"}, - {file = "graphlib2-0.4.7-cp37-abi3-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:3619c7d3c5aca95e6cbbfc283aa6bf42ffa5b59d7f39c8d0ad615bce65dc406f"}, - {file = "graphlib2-0.4.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b19f1b91d0f22ca3d1cfb2965478db98cf5916a5c6cea5fdc7caf4bf1bfbc33"}, - {file = "graphlib2-0.4.7-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:624020f6808ee21ffbb2e455f8dd4196bbb37032a35aa3327f0f5b65fb6a35d1"}, - {file = "graphlib2-0.4.7-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6efc6a197a619a97f1b105aea14b202101241c1db9014bd100ad19cf29288cbf"}, - {file = "graphlib2-0.4.7-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d7cc38b68775cb2cdfc487bbaca2f7991da0d76d42a68f412c2ca61461e6e026"}, - {file = "graphlib2-0.4.7-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b06bed98d42f4e10adfe2a8332efdca06b5bac6e7c86dd1d22a4dea4de9b275a"}, - {file = "graphlib2-0.4.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c9ec3a5645bdf020d8bd9196b2665e26090d60e523fd498df29628f2c5fbecc"}, - {file = "graphlib2-0.4.7-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:824df87f767471febfd785a05a2cc77c0c973e0112a548df827763ca0aa8c126"}, - {file = "graphlib2-0.4.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2de5e32ca5c0b06d442d2be4b378cc0bc335c5fcbc14a7d531a621eb8294d019"}, - {file = "graphlib2-0.4.7-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:13a23fcf07c7bef8a5ad0e04ab826d3a2a2bcb493197005300c68b4ea7b8f581"}, - {file = "graphlib2-0.4.7-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:15a8a6daa28c1fb5c518d387879f3bbe313264fbbc2fab5635b718bc71a24913"}, - {file = "graphlib2-0.4.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:0cb6c4449834077972c3cea4602f86513b4b75fcf2d40b12e4fe4bf1aa5c8da2"}, - {file = "graphlib2-0.4.7-cp37-abi3-win32.whl", hash = "sha256:31b40cea537845d80b69403ae306d7c6a68716b76f5171f68daed1804aadefec"}, - {file = "graphlib2-0.4.7-cp37-abi3-win_amd64.whl", hash = "sha256:d40935a9da81a046ebcaa0216ad593ef504ae8a5425a59bdbd254c0462adedc8"}, - {file = "graphlib2-0.4.7-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:9cef08a50632e75a9e11355e68fa1f8c9371d0734642855f8b5c4ead1b058e6f"}, - {file = "graphlib2-0.4.7-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aeecb604d70317c20ca6bc3556f7f5c40146ad1f0ded837e978b2fe6edf3e567"}, - {file = "graphlib2-0.4.7-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb4ae9df7ed895c6557619049c9f73e1c2e6d1fbed568010fd5d4af94e2f0692"}, - {file = "graphlib2-0.4.7-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:3ee3a99fc39df948fef340b01254709cc603263f8b176f72ed26f1eea44070a4"}, - {file = "graphlib2-0.4.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5873480df8991273bd1585122df232acd0f946c401c254bd9f0d661c72589dcf"}, - {file = "graphlib2-0.4.7-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:297c817229501255cd3a744c62c8f91e5139ee79bc550488f5bc765ffa33f7c5"}, - {file = "graphlib2-0.4.7-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:853ef22df8e9f695706e0b8556cda9342d4d617f7d7bd02803e824bcc0c30b20"}, - {file = "graphlib2-0.4.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee62ff1042fde980adf668e30393eca79aee8f1fa1274ab3b98d69091c70c5e8"}, - {file = "graphlib2-0.4.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b16e21e70938132d4160c2591fed59f79b5f8b702e4860c8933111b5fedb55c2"}, - {file = "graphlib2-0.4.7.tar.gz", hash = "sha256:a951c18cb4c2c2834eec898b4c75d3f930d6f08beb37496f0e0ce56eb3f571f5"}, -] - -[[package]] -name = "greenlet" -version = "3.0.3" -description = "Lightweight in-process concurrent programming" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, - {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, - {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, - {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, - {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, - {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, - {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, - {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, - {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, - {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, - {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, - {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, - {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, - {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, - {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, - {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, -] - -[package.extras] -docs = ["Sphinx", "furo"] -test = ["objgraph", "psutil"] - -[[package]] -name = "h11" -version = "0.14.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, -] - -[[package]] -name = "identify" -version = "2.5.36" -description = "File identification library for Python" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"}, - {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"}, -] - -[package.extras] -license = ["ukkonen"] - -[[package]] -name = "idna" -version = "3.7" -description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" -optional = false -python-versions = ">=3.5" -files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, -] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "isort" -version = "5.13.2" -description = "A Python utility / library to sort Python imports." -category = "dev" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, - {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, -] - -[package.extras] -colors = ["colorama (>=0.4.6)"] - -[[package]] -name = "mako" -version = "1.3.3" -description = "A super-fast templating language that borrows the best ideas from the existing templating languages." -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "Mako-1.3.3-py3-none-any.whl", hash = "sha256:5324b88089a8978bf76d1629774fcc2f1c07b82acdf00f4c5dd8ceadfffc4b40"}, - {file = "Mako-1.3.3.tar.gz", hash = "sha256:e16c01d9ab9c11f7290eef1cfefc093fb5a45ee4a3da09e2fec2e4d1bae54e73"}, -] - -[package.dependencies] -MarkupSafe = ">=0.9.2" - -[package.extras] -babel = ["Babel"] -lingua = ["lingua"] -testing = ["pytest"] - -[[package]] -name = "markdown-it-py" -version = "3.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, - {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, -] - -[package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins"] -profiling = ["gprof2dot"] -rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - -[[package]] -name = "markupsafe" -version = "2.1.5" -description = "Safely add untrusted strings to HTML/XML markup." -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, -] - -[[package]] -name = "mccabe" -version = "0.7.0" -description = "McCabe checker, plugin for flake8" -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - -[[package]] -name = "multidict" -version = "6.0.5" -description = "multidict implementation" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, - {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, - {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, - {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, - {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, - {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, - {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, - {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, - {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, - {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, - {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, - {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, - {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, - {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, - {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, - {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, - {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, - {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, -] - -[[package]] -name = "mypy" -version = "1.10.0" -description = "Optional static typing for Python" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "mypy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da1cbf08fb3b851ab3b9523a884c232774008267b1f83371ace57f412fe308c2"}, - {file = "mypy-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:12b6bfc1b1a66095ab413160a6e520e1dc076a28f3e22f7fb25ba3b000b4ef99"}, - {file = "mypy-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e36fb078cce9904c7989b9693e41cb9711e0600139ce3970c6ef814b6ebc2b2"}, - {file = "mypy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2b0695d605ddcd3eb2f736cd8b4e388288c21e7de85001e9f85df9187f2b50f9"}, - {file = "mypy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:cd777b780312ddb135bceb9bc8722a73ec95e042f911cc279e2ec3c667076051"}, - {file = "mypy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3be66771aa5c97602f382230165b856c231d1277c511c9a8dd058be4784472e1"}, - {file = "mypy-1.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b2cbaca148d0754a54d44121b5825ae71868c7592a53b7292eeb0f3fdae95ee"}, - {file = "mypy-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ec404a7cbe9fc0e92cb0e67f55ce0c025014e26d33e54d9e506a0f2d07fe5de"}, - {file = "mypy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e22e1527dc3d4aa94311d246b59e47f6455b8729f4968765ac1eacf9a4760bc7"}, - {file = "mypy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:a87dbfa85971e8d59c9cc1fcf534efe664d8949e4c0b6b44e8ca548e746a8d53"}, - {file = "mypy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a781f6ad4bab20eef8b65174a57e5203f4be627b46291f4589879bf4e257b97b"}, - {file = "mypy-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b808e12113505b97d9023b0b5e0c0705a90571c6feefc6f215c1df9381256e30"}, - {file = "mypy-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f55583b12156c399dce2df7d16f8a5095291354f1e839c252ec6c0611e86e2e"}, - {file = "mypy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cf18f9d0efa1b16478c4c129eabec36148032575391095f73cae2e722fcf9d5"}, - {file = "mypy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc6ac273b23c6b82da3bb25f4136c4fd42665f17f2cd850771cb600bdd2ebeda"}, - {file = "mypy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9fd50226364cd2737351c79807775136b0abe084433b55b2e29181a4c3c878c0"}, - {file = "mypy-1.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f90cff89eea89273727d8783fef5d4a934be2fdca11b47def50cf5d311aff727"}, - {file = "mypy-1.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fcfc70599efde5c67862a07a1aaf50e55bce629ace26bb19dc17cece5dd31ca4"}, - {file = "mypy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:075cbf81f3e134eadaf247de187bd604748171d6b79736fa9b6c9685b4083061"}, - {file = "mypy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:3f298531bca95ff615b6e9f2fc0333aae27fa48052903a0ac90215021cdcfa4f"}, - {file = "mypy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa7ef5244615a2523b56c034becde4e9e3f9b034854c93639adb667ec9ec2976"}, - {file = "mypy-1.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3236a4c8f535a0631f85f5fcdffba71c7feeef76a6002fcba7c1a8e57c8be1ec"}, - {file = "mypy-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a2b5cdbb5dd35aa08ea9114436e0d79aceb2f38e32c21684dcf8e24e1e92821"}, - {file = "mypy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92f93b21c0fe73dc00abf91022234c79d793318b8a96faac147cd579c1671746"}, - {file = "mypy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:28d0e038361b45f099cc086d9dd99c15ff14d0188f44ac883010e172ce86c38a"}, - {file = "mypy-1.10.0-py3-none-any.whl", hash = "sha256:f8c083976eb530019175aabadb60921e73b4f45736760826aa1689dda8208aee"}, - {file = "mypy-1.10.0.tar.gz", hash = "sha256:3d087fcbec056c4ee34974da493a826ce316947485cef3901f511848e687c131"}, -] - -[package.dependencies] -mypy-extensions = ">=1.0.0" -typing-extensions = ">=4.1.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" -optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - -[[package]] -name = "nodeenv" -version = "1.8.0" -description = "Node.js virtual environment builder" -category = "dev" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" -files = [ - {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, - {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, -] - -[package.dependencies] -setuptools = "*" - -[[package]] -name = "orjson" -version = "3.10.1" -description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "orjson-3.10.1-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:8ec2fc456d53ea4a47768f622bb709be68acd455b0c6be57e91462259741c4f3"}, - {file = "orjson-3.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e900863691d327758be14e2a491931605bd0aded3a21beb6ce133889830b659"}, - {file = "orjson-3.10.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ab6ecbd6fe57785ebc86ee49e183f37d45f91b46fc601380c67c5c5e9c0014a2"}, - {file = "orjson-3.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8af7c68b01b876335cccfb4eee0beef2b5b6eae1945d46a09a7c24c9faac7a77"}, - {file = "orjson-3.10.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:915abfb2e528677b488a06eba173e9d7706a20fdfe9cdb15890b74ef9791b85e"}, - {file = "orjson-3.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe3fd4a36eff9c63d25503b439531d21828da9def0059c4f472e3845a081aa0b"}, - {file = "orjson-3.10.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d229564e72cfc062e6481a91977a5165c5a0fdce11ddc19ced8471847a67c517"}, - {file = "orjson-3.10.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9e00495b18304173ac843b5c5fbea7b6f7968564d0d49bef06bfaeca4b656f4e"}, - {file = "orjson-3.10.1-cp310-none-win32.whl", hash = "sha256:fd78ec55179545c108174ba19c1795ced548d6cac4d80d014163033c047ca4ea"}, - {file = "orjson-3.10.1-cp310-none-win_amd64.whl", hash = "sha256:50ca42b40d5a442a9e22eece8cf42ba3d7cd4cd0f2f20184b4d7682894f05eec"}, - {file = "orjson-3.10.1-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b345a3d6953628df2f42502297f6c1e1b475cfbf6268013c94c5ac80e8abc04c"}, - {file = "orjson-3.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:caa7395ef51af4190d2c70a364e2f42138e0e5fcb4bc08bc9b76997659b27dab"}, - {file = "orjson-3.10.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b01d701decd75ae092e5f36f7b88a1e7a1d3bb7c9b9d7694de850fb155578d5a"}, - {file = "orjson-3.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5028981ba393f443d8fed9049211b979cadc9d0afecf162832f5a5b152c6297"}, - {file = "orjson-3.10.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31ff6a222ea362b87bf21ff619598a4dc1106aaafaea32b1c4876d692891ec27"}, - {file = "orjson-3.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e852a83d7803d3406135fb7a57cf0c1e4a3e73bac80ec621bd32f01c653849c5"}, - {file = "orjson-3.10.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2567bc928ed3c3fcd90998009e8835de7c7dc59aabcf764b8374d36044864f3b"}, - {file = "orjson-3.10.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4ce98cac60b7bb56457bdd2ed7f0d5d7f242d291fdc0ca566c83fa721b52e92d"}, - {file = "orjson-3.10.1-cp311-none-win32.whl", hash = "sha256:813905e111318acb356bb8029014c77b4c647f8b03f314e7b475bd9ce6d1a8ce"}, - {file = "orjson-3.10.1-cp311-none-win_amd64.whl", hash = "sha256:03a3ca0b3ed52bed1a869163a4284e8a7b0be6a0359d521e467cdef7e8e8a3ee"}, - {file = "orjson-3.10.1-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f02c06cee680b1b3a8727ec26c36f4b3c0c9e2b26339d64471034d16f74f4ef5"}, - {file = "orjson-3.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1aa2f127ac546e123283e437cc90b5ecce754a22306c7700b11035dad4ccf85"}, - {file = "orjson-3.10.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2cf29b4b74f585225196944dffdebd549ad2af6da9e80db7115984103fb18a96"}, - {file = "orjson-3.10.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1b130c20b116f413caf6059c651ad32215c28500dce9cd029a334a2d84aa66f"}, - {file = "orjson-3.10.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d31f9a709e6114492136e87c7c6da5e21dfedebefa03af85f3ad72656c493ae9"}, - {file = "orjson-3.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d1d169461726f271ab31633cf0e7e7353417e16fb69256a4f8ecb3246a78d6e"}, - {file = "orjson-3.10.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:57c294d73825c6b7f30d11c9e5900cfec9a814893af7f14efbe06b8d0f25fba9"}, - {file = "orjson-3.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d7f11dbacfa9265ec76b4019efffabaabba7a7ebf14078f6b4df9b51c3c9a8ea"}, - {file = "orjson-3.10.1-cp312-none-win32.whl", hash = "sha256:d89e5ed68593226c31c76ab4de3e0d35c760bfd3fbf0a74c4b2be1383a1bf123"}, - {file = "orjson-3.10.1-cp312-none-win_amd64.whl", hash = "sha256:aa76c4fe147fd162107ce1692c39f7189180cfd3a27cfbc2ab5643422812da8e"}, - {file = "orjson-3.10.1-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a2c6a85c92d0e494c1ae117befc93cf8e7bca2075f7fe52e32698da650b2c6d1"}, - {file = "orjson-3.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9813f43da955197d36a7365eb99bed42b83680801729ab2487fef305b9ced866"}, - {file = "orjson-3.10.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ec917b768e2b34b7084cb6c68941f6de5812cc26c6f1a9fecb728e36a3deb9e8"}, - {file = "orjson-3.10.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5252146b3172d75c8a6d27ebca59c9ee066ffc5a277050ccec24821e68742fdf"}, - {file = "orjson-3.10.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:536429bb02791a199d976118b95014ad66f74c58b7644d21061c54ad284e00f4"}, - {file = "orjson-3.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7dfed3c3e9b9199fb9c3355b9c7e4649b65f639e50ddf50efdf86b45c6de04b5"}, - {file = "orjson-3.10.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2b230ec35f188f003f5b543644ae486b2998f6afa74ee3a98fc8ed2e45960afc"}, - {file = "orjson-3.10.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:01234249ba19c6ab1eb0b8be89f13ea21218b2d72d496ef085cfd37e1bae9dd8"}, - {file = "orjson-3.10.1-cp38-none-win32.whl", hash = "sha256:8a884fbf81a3cc22d264ba780920d4885442144e6acaa1411921260416ac9a54"}, - {file = "orjson-3.10.1-cp38-none-win_amd64.whl", hash = "sha256:dab5f802d52b182163f307d2b1f727d30b1762e1923c64c9c56dd853f9671a49"}, - {file = "orjson-3.10.1-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a51fd55d4486bc5293b7a400f9acd55a2dc3b5fc8420d5ffe9b1d6bb1a056a5e"}, - {file = "orjson-3.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53521542a6db1411b3bfa1b24ddce18605a3abdc95a28a67b33f9145f26aa8f2"}, - {file = "orjson-3.10.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:27d610df96ac18ace4931411d489637d20ab3b8f63562b0531bba16011998db0"}, - {file = "orjson-3.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79244b1456e5846d44e9846534bd9e3206712936d026ea8e6a55a7374d2c0694"}, - {file = "orjson-3.10.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d751efaa8a49ae15cbebdda747a62a9ae521126e396fda8143858419f3b03610"}, - {file = "orjson-3.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27ff69c620a4fff33267df70cfd21e0097c2a14216e72943bd5414943e376d77"}, - {file = "orjson-3.10.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ebc58693464146506fde0c4eb1216ff6d4e40213e61f7d40e2f0dde9b2f21650"}, - {file = "orjson-3.10.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5be608c3972ed902e0143a5b8776d81ac1059436915d42defe5c6ae97b3137a4"}, - {file = "orjson-3.10.1-cp39-none-win32.whl", hash = "sha256:4ae10753e7511d359405aadcbf96556c86e9dbf3a948d26c2c9f9a150c52b091"}, - {file = "orjson-3.10.1-cp39-none-win_amd64.whl", hash = "sha256:fb5bc4caa2c192077fdb02dce4e5ef8639e7f20bec4e3a834346693907362932"}, - {file = "orjson-3.10.1.tar.gz", hash = "sha256:a883b28d73370df23ed995c466b4f6c708c1f7a9bdc400fe89165c96c7603204"}, -] - -[[package]] -name = "packaging" -version = "24.0" -description = "Core utilities for Python packages" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, -] - -[[package]] -name = "pamqp" -version = "3.3.0" -description = "RabbitMQ Focused AMQP low-level library" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pamqp-3.3.0-py2.py3-none-any.whl", hash = "sha256:c901a684794157ae39b52cbf700db8c9aae7a470f13528b9d7b4e5f7202f8eb0"}, - {file = "pamqp-3.3.0.tar.gz", hash = "sha256:40b8795bd4efcf2b0f8821c1de83d12ca16d5760f4507836267fd7a02b06763b"}, -] - -[package.extras] -codegen = ["lxml", "requests", "yapf"] -testing = ["coverage", "flake8", "flake8-comprehensions", "flake8-deprecated", "flake8-import-order", "flake8-print", "flake8-quotes", "flake8-rst-docstrings", "flake8-tuple", "yapf"] - -[[package]] -name = "platformdirs" -version = "4.2.1" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, - {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, -] - -[package.extras] -docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] -type = ["mypy (>=1.8)"] - -[[package]] -name = "pluggy" -version = "1.5.0" -description = "plugin and hook calling mechanisms for python" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "pre-commit" -version = "3.7.0" -description = "A framework for managing and maintaining multi-language pre-commit hooks." -category = "dev" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pre_commit-3.7.0-py2.py3-none-any.whl", hash = "sha256:5eae9e10c2b5ac51577c3452ec0a490455c45a0533f7960f993a0d01e59decab"}, - {file = "pre_commit-3.7.0.tar.gz", hash = "sha256:e209d61b8acdcf742404408531f0c37d49d2c734fd7cff2d6076083d191cb060"}, -] - -[package.dependencies] -cfgv = ">=2.0.0" -identify = ">=1.0.0" -nodeenv = ">=0.11.1" -pyyaml = ">=5.1" -virtualenv = ">=20.10.0" - -[[package]] -name = "pydantic" -version = "2.7.1" -description = "Data validation using Python type hints" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pydantic-2.7.1-py3-none-any.whl", hash = "sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5"}, - {file = "pydantic-2.7.1.tar.gz", hash = "sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc"}, -] - -[package.dependencies] -annotated-types = ">=0.4.0" -pydantic-core = "2.18.2" -typing-extensions = ">=4.6.1" - -[package.extras] -email = ["email-validator (>=2.0.0)"] - -[[package]] -name = "pydantic-core" -version = "2.18.2" -description = "Core functionality for Pydantic validation and serialization" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pydantic_core-2.18.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9e08e867b306f525802df7cd16c44ff5ebbe747ff0ca6cf3fde7f36c05a59a81"}, - {file = "pydantic_core-2.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f0a21cbaa69900cbe1a2e7cad2aa74ac3cf21b10c3efb0fa0b80305274c0e8a2"}, - {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0680b1f1f11fda801397de52c36ce38ef1c1dc841a0927a94f226dea29c3ae3d"}, - {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95b9d5e72481d3780ba3442eac863eae92ae43a5f3adb5b4d0a1de89d42bb250"}, - {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fcf5cd9c4b655ad666ca332b9a081112cd7a58a8b5a6ca7a3104bc950f2038"}, - {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b5155ff768083cb1d62f3e143b49a8a3432e6789a3abee8acd005c3c7af1c74"}, - {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553ef617b6836fc7e4df130bb851e32fe357ce36336d897fd6646d6058d980af"}, - {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89ed9eb7d616ef5714e5590e6cf7f23b02d0d539767d33561e3675d6f9e3857"}, - {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:75f7e9488238e920ab6204399ded280dc4c307d034f3924cd7f90a38b1829563"}, - {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ef26c9e94a8c04a1b2924149a9cb081836913818e55681722d7f29af88fe7b38"}, - {file = "pydantic_core-2.18.2-cp310-none-win32.whl", hash = "sha256:182245ff6b0039e82b6bb585ed55a64d7c81c560715d1bad0cbad6dfa07b4027"}, - {file = "pydantic_core-2.18.2-cp310-none-win_amd64.whl", hash = "sha256:e23ec367a948b6d812301afc1b13f8094ab7b2c280af66ef450efc357d2ae543"}, - {file = "pydantic_core-2.18.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:219da3f096d50a157f33645a1cf31c0ad1fe829a92181dd1311022f986e5fbe3"}, - {file = "pydantic_core-2.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc1cfd88a64e012b74e94cd00bbe0f9c6df57049c97f02bb07d39e9c852e19a4"}, - {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b7133a6e6aeb8df37d6f413f7705a37ab4031597f64ab56384c94d98fa0e90"}, - {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:224c421235f6102e8737032483f43c1a8cfb1d2f45740c44166219599358c2cd"}, - {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b14d82cdb934e99dda6d9d60dc84a24379820176cc4a0d123f88df319ae9c150"}, - {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2728b01246a3bba6de144f9e3115b532ee44bd6cf39795194fb75491824a1413"}, - {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:470b94480bb5ee929f5acba6995251ada5e059a5ef3e0dfc63cca287283ebfa6"}, - {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:997abc4df705d1295a42f95b4eec4950a37ad8ae46d913caeee117b6b198811c"}, - {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75250dbc5290e3f1a0f4618db35e51a165186f9034eff158f3d490b3fed9f8a0"}, - {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4456f2dca97c425231d7315737d45239b2b51a50dc2b6f0c2bb181fce6207664"}, - {file = "pydantic_core-2.18.2-cp311-none-win32.whl", hash = "sha256:269322dcc3d8bdb69f054681edff86276b2ff972447863cf34c8b860f5188e2e"}, - {file = "pydantic_core-2.18.2-cp311-none-win_amd64.whl", hash = "sha256:800d60565aec896f25bc3cfa56d2277d52d5182af08162f7954f938c06dc4ee3"}, - {file = "pydantic_core-2.18.2-cp311-none-win_arm64.whl", hash = "sha256:1404c69d6a676245199767ba4f633cce5f4ad4181f9d0ccb0577e1f66cf4c46d"}, - {file = "pydantic_core-2.18.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:fb2bd7be70c0fe4dfd32c951bc813d9fe6ebcbfdd15a07527796c8204bd36242"}, - {file = "pydantic_core-2.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6132dd3bd52838acddca05a72aafb6eab6536aa145e923bb50f45e78b7251043"}, - {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d904828195733c183d20a54230c0df0eb46ec746ea1a666730787353e87182"}, - {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9bd70772c720142be1020eac55f8143a34ec9f82d75a8e7a07852023e46617f"}, - {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b8ed04b3582771764538f7ee7001b02e1170223cf9b75dff0bc698fadb00cf3"}, - {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6dac87ddb34aaec85f873d737e9d06a3555a1cc1a8e0c44b7f8d5daeb89d86f"}, - {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca4ae5a27ad7a4ee5170aebce1574b375de390bc01284f87b18d43a3984df72"}, - {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:886eec03591b7cf058467a70a87733b35f44707bd86cf64a615584fd72488b7c"}, - {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ca7b0c1f1c983e064caa85f3792dd2fe3526b3505378874afa84baf662e12241"}, - {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b4356d3538c3649337df4074e81b85f0616b79731fe22dd11b99499b2ebbdf3"}, - {file = "pydantic_core-2.18.2-cp312-none-win32.whl", hash = "sha256:8b172601454f2d7701121bbec3425dd71efcb787a027edf49724c9cefc14c038"}, - {file = "pydantic_core-2.18.2-cp312-none-win_amd64.whl", hash = "sha256:b1bd7e47b1558ea872bd16c8502c414f9e90dcf12f1395129d7bb42a09a95438"}, - {file = "pydantic_core-2.18.2-cp312-none-win_arm64.whl", hash = "sha256:98758d627ff397e752bc339272c14c98199c613f922d4a384ddc07526c86a2ec"}, - {file = "pydantic_core-2.18.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:9fdad8e35f278b2c3eb77cbdc5c0a49dada440657bf738d6905ce106dc1de439"}, - {file = "pydantic_core-2.18.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1d90c3265ae107f91a4f279f4d6f6f1d4907ac76c6868b27dc7fb33688cfb347"}, - {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390193c770399861d8df9670fb0d1874f330c79caaca4642332df7c682bf6b91"}, - {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:82d5d4d78e4448683cb467897fe24e2b74bb7b973a541ea1dcfec1d3cbce39fb"}, - {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4774f3184d2ef3e14e8693194f661dea5a4d6ca4e3dc8e39786d33a94865cefd"}, - {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4d938ec0adf5167cb335acb25a4ee69a8107e4984f8fbd2e897021d9e4ca21b"}, - {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0e8b1be28239fc64a88a8189d1df7fad8be8c1ae47fcc33e43d4be15f99cc70"}, - {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868649da93e5a3d5eacc2b5b3b9235c98ccdbfd443832f31e075f54419e1b96b"}, - {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:78363590ef93d5d226ba21a90a03ea89a20738ee5b7da83d771d283fd8a56761"}, - {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:852e966fbd035a6468fc0a3496589b45e2208ec7ca95c26470a54daed82a0788"}, - {file = "pydantic_core-2.18.2-cp38-none-win32.whl", hash = "sha256:6a46e22a707e7ad4484ac9ee9f290f9d501df45954184e23fc29408dfad61350"}, - {file = "pydantic_core-2.18.2-cp38-none-win_amd64.whl", hash = "sha256:d91cb5ea8b11607cc757675051f61b3d93f15eca3cefb3e6c704a5d6e8440f4e"}, - {file = "pydantic_core-2.18.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ae0a8a797a5e56c053610fa7be147993fe50960fa43609ff2a9552b0e07013e8"}, - {file = "pydantic_core-2.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:042473b6280246b1dbf530559246f6842b56119c2926d1e52b631bdc46075f2a"}, - {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a388a77e629b9ec814c1b1e6b3b595fe521d2cdc625fcca26fbc2d44c816804"}, - {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25add29b8f3b233ae90ccef2d902d0ae0432eb0d45370fe315d1a5cf231004b"}, - {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f459a5ce8434614dfd39bbebf1041952ae01da6bed9855008cb33b875cb024c0"}, - {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eff2de745698eb46eeb51193a9f41d67d834d50e424aef27df2fcdee1b153845"}, - {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8309f67285bdfe65c372ea3722b7a5642680f3dba538566340a9d36e920b5f0"}, - {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f93a8a2e3938ff656a7c1bc57193b1319960ac015b6e87d76c76bf14fe0244b4"}, - {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:22057013c8c1e272eb8d0eebc796701167d8377441ec894a8fed1af64a0bf399"}, - {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfeecd1ac6cc1fb2692c3d5110781c965aabd4ec5d32799773ca7b1456ac636b"}, - {file = "pydantic_core-2.18.2-cp39-none-win32.whl", hash = "sha256:0d69b4c2f6bb3e130dba60d34c0845ba31b69babdd3f78f7c0c8fae5021a253e"}, - {file = "pydantic_core-2.18.2-cp39-none-win_amd64.whl", hash = "sha256:d9319e499827271b09b4e411905b24a426b8fb69464dfa1696258f53a3334641"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a1874c6dd4113308bd0eb568418e6114b252afe44319ead2b4081e9b9521fe75"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ccdd111c03bfd3666bd2472b674c6899550e09e9f298954cfc896ab92b5b0e6d"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e18609ceaa6eed63753037fc06ebb16041d17d28199ae5aba0052c51449650a9"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e5c584d357c4e2baf0ff7baf44f4994be121e16a2c88918a5817331fc7599d7"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43f0f463cf89ace478de71a318b1b4f05ebc456a9b9300d027b4b57c1a2064fb"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e1b395e58b10b73b07b7cf740d728dd4ff9365ac46c18751bf8b3d8cca8f625a"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0098300eebb1c837271d3d1a2cd2911e7c11b396eac9661655ee524a7f10587b"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:36789b70d613fbac0a25bb07ab3d9dba4d2e38af609c020cf4d888d165ee0bf3"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3f9a801e7c8f1ef8718da265bba008fa121243dfe37c1cea17840b0944dfd72c"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3a6515ebc6e69d85502b4951d89131ca4e036078ea35533bb76327f8424531ce"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20aca1e2298c56ececfd8ed159ae4dde2df0781988c97ef77d5c16ff4bd5b400"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:223ee893d77a310a0391dca6df00f70bbc2f36a71a895cecd9a0e762dc37b349"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2334ce8c673ee93a1d6a65bd90327588387ba073c17e61bf19b4fd97d688d63c"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cbca948f2d14b09d20268cda7b0367723d79063f26c4ffc523af9042cad95592"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b3ef08e20ec49e02d5c6717a91bb5af9b20f1805583cb0adfe9ba2c6b505b5ae"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6fdc8627910eed0c01aed6a390a252fe3ea6d472ee70fdde56273f198938374"}, - {file = "pydantic_core-2.18.2.tar.gz", hash = "sha256:2e29d20810dfc3043ee13ac7d9e25105799817683348823f305ab3f349b9386e"}, -] - -[package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" - -[[package]] -name = "pygments" -version = "2.17.2" -description = "Pygments is a syntax highlighting package written in Python." -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, - {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, -] - -[package.extras] -plugins = ["importlib-metadata"] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pylint" -version = "3.1.0" -description = "python code static checker" -category = "dev" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "pylint-3.1.0-py3-none-any.whl", hash = "sha256:507a5b60953874766d8a366e8e8c7af63e058b26345cfcb5f91f89d987fd6b74"}, - {file = "pylint-3.1.0.tar.gz", hash = "sha256:6a69beb4a6f63debebaab0a3477ecd0f559aa726af4954fc948c51f7a2549e23"}, -] - -[package.dependencies] -astroid = ">=3.1.0,<=3.2.0-dev0" -colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -dill = [ - {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, - {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, -] -isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" -mccabe = ">=0.6,<0.8" -platformdirs = ">=2.2.0" -tomlkit = ">=0.10.1" - -[package.extras] -spelling = ["pyenchant (>=3.2,<4.0)"] -testutils = ["gitpython (>3)"] - -[[package]] -name = "pytest" -version = "8.2.0" -description = "pytest: simple powerful testing with Python" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest-8.2.0-py3-none-any.whl", hash = "sha256:1733f0620f6cda4095bbf0d9ff8022486e91892245bb9e7d5542c018f612f233"}, - {file = "pytest-8.2.0.tar.gz", hash = "sha256:d507d4482197eac0ba2bae2e9babf0672eb333017bcedaa5fb1a3d42c1174b3f"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=1.5,<2.0" - -[package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-asyncio" -version = "0.23.6" -description = "Pytest support for asyncio" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest-asyncio-0.23.6.tar.gz", hash = "sha256:ffe523a89c1c222598c76856e76852b787504ddb72dd5d9b6617ffa8aa2cde5f"}, - {file = "pytest_asyncio-0.23.6-py3-none-any.whl", hash = "sha256:68516fdd1018ac57b846c9846b954f0393b26f094764a28c955eabb0536a4e8a"}, -] - -[package.dependencies] -pytest = ">=7.0.0,<9" - -[package.extras] -docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] -testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] - -[[package]] -name = "pytest-order" -version = "1.2.1" -description = "pytest plugin to run your tests in a specific order" -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ - {file = "pytest-order-1.2.1.tar.gz", hash = "sha256:4451bd8821ba4fa2109455a2fcc882af60ef8e53e09d244d67674be08f56eac3"}, - {file = "pytest_order-1.2.1-py3-none-any.whl", hash = "sha256:c3082fc73f9ddcf13e4a22dda9bbcc2f39865bf537438a1d50fa241e028dd743"}, -] - -[package.dependencies] -pytest = {version = ">=6.2.4", markers = "python_version >= \"3.10\""} - -[[package]] -name = "pytest-xdist" -version = "3.6.1" -description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"}, - {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, -] - -[package.dependencies] -execnet = ">=2.1" -pytest = ">=7.0.0" - -[package.extras] -psutil = ["psutil (>=3.0)"] -setproctitle = ["setproctitle"] -testing = ["filelock"] - -[[package]] -name = "pywin32" -version = "306" -description = "Python for Window Extensions" -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, - {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, - {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, - {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, - {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, - {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, - {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, - {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, - {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, - {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, - {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, - {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, - {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, - {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, -] - -[[package]] -name = "pyyaml" -version = "6.0.1" -description = "YAML parser and emitter for Python" -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, -] - -[[package]] -name = "requests" -version = "2.31.0" -description = "Python HTTP for Humans." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "rich" -version = "13.7.1" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -category = "main" -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, - {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, -] - -[package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - -[[package]] -name = "ruff" -version = "0.4.2" -description = "An extremely fast Python linter and code formatter, written in Rust." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "ruff-0.4.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8d14dc8953f8af7e003a485ef560bbefa5f8cc1ad994eebb5b12136049bbccc5"}, - {file = "ruff-0.4.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:24016ed18db3dc9786af103ff49c03bdf408ea253f3cb9e3638f39ac9cf2d483"}, - {file = "ruff-0.4.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2e06459042ac841ed510196c350ba35a9b24a643e23db60d79b2db92af0c2b"}, - {file = "ruff-0.4.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3afabaf7ba8e9c485a14ad8f4122feff6b2b93cc53cd4dad2fd24ae35112d5c5"}, - {file = "ruff-0.4.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:799eb468ea6bc54b95527143a4ceaf970d5aa3613050c6cff54c85fda3fde480"}, - {file = "ruff-0.4.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ec4ba9436a51527fb6931a8839af4c36a5481f8c19e8f5e42c2f7ad3a49f5069"}, - {file = "ruff-0.4.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6a2243f8f434e487c2a010c7252150b1fdf019035130f41b77626f5655c9ca22"}, - {file = "ruff-0.4.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8772130a063f3eebdf7095da00c0b9898bd1774c43b336272c3e98667d4fb8fa"}, - {file = "ruff-0.4.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ab165ef5d72392b4ebb85a8b0fbd321f69832a632e07a74794c0e598e7a8376"}, - {file = "ruff-0.4.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1f32cadf44c2020e75e0c56c3408ed1d32c024766bd41aedef92aa3ca28eef68"}, - {file = "ruff-0.4.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:22e306bf15e09af45ca812bc42fa59b628646fa7c26072555f278994890bc7ac"}, - {file = "ruff-0.4.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:82986bb77ad83a1719c90b9528a9dd663c9206f7c0ab69282af8223566a0c34e"}, - {file = "ruff-0.4.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:652e4ba553e421a6dc2a6d4868bc3b3881311702633eb3672f9f244ded8908cd"}, - {file = "ruff-0.4.2-py3-none-win32.whl", hash = "sha256:7891ee376770ac094da3ad40c116258a381b86c7352552788377c6eb16d784fe"}, - {file = "ruff-0.4.2-py3-none-win_amd64.whl", hash = "sha256:5ec481661fb2fd88a5d6cf1f83403d388ec90f9daaa36e40e2c003de66751798"}, - {file = "ruff-0.4.2-py3-none-win_arm64.whl", hash = "sha256:cbd1e87c71bca14792948c4ccb51ee61c3296e164019d2d484f3eaa2d360dfaf"}, - {file = "ruff-0.4.2.tar.gz", hash = "sha256:33bcc160aee2520664bc0859cfeaebc84bb7323becff3f303b8f1f2d81cb4edc"}, -] - -[[package]] -name = "setuptools" -version = "69.5.1" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, - {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - -[[package]] -name = "sniffio" -version = "1.3.1" -description = "Sniff out which async library your code is running under" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, - {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, -] - -[[package]] -name = "sqlalchemy" -version = "2.0.29" -description = "Database Abstraction Library" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "SQLAlchemy-2.0.29-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c142852ae192e9fe5aad5c350ea6befe9db14370b34047e1f0f7cf99e63c63b"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:99a1e69d4e26f71e750e9ad6fdc8614fbddb67cfe2173a3628a2566034e223c7"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ef3fbccb4058355053c51b82fd3501a6e13dd808c8d8cd2561e610c5456013c"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d6753305936eddc8ed190e006b7bb33a8f50b9854823485eed3a886857ab8d1"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0f3ca96af060a5250a8ad5a63699180bc780c2edf8abf96c58af175921df847a"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c4520047006b1d3f0d89e0532978c0688219857eb2fee7c48052560ae76aca1e"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-win32.whl", hash = "sha256:b2a0e3cf0caac2085ff172c3faacd1e00c376e6884b5bc4dd5b6b84623e29e4f"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-win_amd64.whl", hash = "sha256:01d10638a37460616708062a40c7b55f73e4d35eaa146781c683e0fa7f6c43fb"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:308ef9cb41d099099fffc9d35781638986870b29f744382904bf9c7dadd08513"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:296195df68326a48385e7a96e877bc19aa210e485fa381c5246bc0234c36c78e"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a13b917b4ffe5a0a31b83d051d60477819ddf18276852ea68037a144a506efb9"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f6d971255d9ddbd3189e2e79d743ff4845c07f0633adfd1de3f63d930dbe673"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:61405ea2d563407d316c63a7b5271ae5d274a2a9fbcd01b0aa5503635699fa1e"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:de7202ffe4d4a8c1e3cde1c03e01c1a3772c92858837e8f3879b497158e4cb44"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-win32.whl", hash = "sha256:b5d7ed79df55a731749ce65ec20d666d82b185fa4898430b17cb90c892741520"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-win_amd64.whl", hash = "sha256:205f5a2b39d7c380cbc3b5dcc8f2762fb5bcb716838e2d26ccbc54330775b003"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d96710d834a6fb31e21381c6d7b76ec729bd08c75a25a5184b1089141356171f"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:52de4736404e53c5c6a91ef2698c01e52333988ebdc218f14c833237a0804f1b"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c7b02525ede2a164c5fa5014915ba3591730f2cc831f5be9ff3b7fd3e30958e"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dfefdb3e54cd15f5d56fd5ae32f1da2d95d78319c1f6dfb9bcd0eb15d603d5d"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a88913000da9205b13f6f195f0813b6ffd8a0c0c2bd58d499e00a30eb508870c"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fecd5089c4be1bcc37c35e9aa678938d2888845a134dd016de457b942cf5a758"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-win32.whl", hash = "sha256:8197d6f7a3d2b468861ebb4c9f998b9df9e358d6e1cf9c2a01061cb9b6cf4e41"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-win_amd64.whl", hash = "sha256:9b19836ccca0d321e237560e475fd99c3d8655d03da80c845c4da20dda31b6e1"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:87a1d53a5382cdbbf4b7619f107cc862c1b0a4feb29000922db72e5a66a5ffc0"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a0732dffe32333211801b28339d2a0babc1971bc90a983e3035e7b0d6f06b93"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90453597a753322d6aa770c5935887ab1fc49cc4c4fdd436901308383d698b4b"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ea311d4ee9a8fa67f139c088ae9f905fcf0277d6cd75c310a21a88bf85e130f5"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5f20cb0a63a3e0ec4e169aa8890e32b949c8145983afa13a708bc4b0a1f30e03"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-win32.whl", hash = "sha256:e5bbe55e8552019c6463709b39634a5fc55e080d0827e2a3a11e18eb73f5cdbd"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-win_amd64.whl", hash = "sha256:c2f9c762a2735600654c654bf48dad388b888f8ce387b095806480e6e4ff6907"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7e614d7a25a43a9f54fcce4675c12761b248547f3d41b195e8010ca7297c369c"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:471fcb39c6adf37f820350c28aac4a7df9d3940c6548b624a642852e727ea586"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:988569c8732f54ad3234cf9c561364221a9e943b78dc7a4aaf35ccc2265f1930"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dddaae9b81c88083e6437de95c41e86823d150f4ee94bf24e158a4526cbead01"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:334184d1ab8f4c87f9652b048af3f7abea1c809dfe526fb0435348a6fef3d380"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:38b624e5cf02a69b113c8047cf7f66b5dfe4a2ca07ff8b8716da4f1b3ae81567"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-win32.whl", hash = "sha256:bab41acf151cd68bc2b466deae5deeb9e8ae9c50ad113444151ad965d5bf685b"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-win_amd64.whl", hash = "sha256:52c8011088305476691b8750c60e03b87910a123cfd9ad48576d6414b6ec2a1d"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3071ad498896907a5ef756206b9dc750f8e57352113c19272bdfdc429c7bd7de"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dba622396a3170974f81bad49aacebd243455ec3cc70615aeaef9e9613b5bca5"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b184e3de58009cc0bf32e20f137f1ec75a32470f5fede06c58f6c355ed42a72"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c37f1050feb91f3d6c32f864d8e114ff5545a4a7afe56778d76a9aec62638ba"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bda7ce59b06d0f09afe22c56714c65c957b1068dee3d5e74d743edec7daba552"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:25664e18bef6dc45015b08f99c63952a53a0a61f61f2e48a9e70cec27e55f699"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-win32.whl", hash = "sha256:77d29cb6c34b14af8a484e831ab530c0f7188f8efed1c6a833a2c674bf3c26ec"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-win_amd64.whl", hash = "sha256:04c487305ab035a9548f573763915189fc0fe0824d9ba28433196f8436f1449c"}, - {file = "SQLAlchemy-2.0.29-py3-none-any.whl", hash = "sha256:dc4ee2d4ee43251905f88637d5281a8d52e916a021384ec10758826f5cbae305"}, - {file = "SQLAlchemy-2.0.29.tar.gz", hash = "sha256:bd9566b8e58cabd700bc367b60e90d9349cd16f0984973f98a9a09f9c64e86f0"}, -] - -[package.dependencies] -greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} -typing-extensions = ">=4.6.0" - -[package.extras] -aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] -aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] -asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] -mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)"] -mysql = ["mysqlclient (>=1.4.0)"] -mysql-connector = ["mysql-connector-python"] -oracle = ["cx_oracle (>=8)"] -oracle-oracledb = ["oracledb (>=1.0.1)"] -postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql-pg8000 = ["pg8000 (>=1.29.1)"] -postgresql-psycopg = ["psycopg (>=3.0.7)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] -postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] -pymysql = ["pymysql"] -sqlcipher = ["sqlcipher3_binary"] - -[[package]] -name = "starlette" -version = "0.37.2" -description = "The little ASGI library that shines." -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"}, - {file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"}, -] - -[package.dependencies] -anyio = ">=3.4.0,<5" - -[package.extras] -full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] - -[[package]] -name = "structlog" -version = "24.1.0" -description = "Structured Logging for Python" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "structlog-24.1.0-py3-none-any.whl", hash = "sha256:3f6efe7d25fab6e86f277713c218044669906537bb717c1807a09d46bca0714d"}, - {file = "structlog-24.1.0.tar.gz", hash = "sha256:41a09886e4d55df25bdcb9b5c9674bccfab723ff43e0a86a1b7b236be8e57b16"}, -] - -[package.extras] -dev = ["structlog[tests,typing]"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-mermaid", "sphinxext-opengraph", "twisted"] -tests = ["freezegun (>=0.2.8)", "pretend", "pytest (>=6.0)", "pytest-asyncio (>=0.17)", "simplejson"] -typing = ["mypy (>=1.4)", "rich", "twisted"] - -[[package]] -name = "testcontainers" -version = "4.4.0" -description = "Python library for throwaway instances of anything that can run in a Docker container" -category = "dev" -optional = false -python-versions = "<4.0,>=3.9" -files = [ - {file = "testcontainers-4.4.0-py3-none-any.whl", hash = "sha256:455e0e28bbf9dcf2d66a342e3103b0d8889db3295a490c009a98848e08791837"}, - {file = "testcontainers-4.4.0.tar.gz", hash = "sha256:f4e87c3831991f9b4d5b6544d0f438fbfd140a74c834b13efb15a2f7c89833c4"}, -] - -[package.dependencies] -docker = "*" -typing-extensions = "*" -urllib3 = "*" -wrapt = "*" - -[package.extras] -arangodb = ["python-arango (>=7.8,<8.0)"] -azurite = ["azure-storage-blob (>=12.19,<13.0)"] -chroma = ["chromadb-client"] -clickhouse = ["clickhouse-driver"] -google = ["google-cloud-datastore (>=2)", "google-cloud-pubsub (>=2)"] -influxdb = ["influxdb", "influxdb-client"] -k3s = ["kubernetes", "pyyaml"] -keycloak = ["python-keycloak"] -localstack = ["boto3"] -minio = ["minio"] -mongodb = ["pymongo"] -mssql = ["pymssql", "sqlalchemy"] -mysql = ["pymysql[rsa]", "sqlalchemy"] -nats = ["nats-py"] -neo4j = ["neo4j"] -opensearch = ["opensearch-py"] -oracle = ["oracledb", "sqlalchemy"] -oracle-free = ["oracledb", "sqlalchemy"] -qdrant = ["qdrant-client"] -rabbitmq = ["pika"] -redis = ["redis"] -registry = ["bcrypt"] -selenium = ["selenium"] -weaviate = ["weaviate-client (>=4.5.4,<5.0.0)"] - -[[package]] -name = "tomlkit" -version = "0.12.4" -description = "Style preserving TOML library" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tomlkit-0.12.4-py3-none-any.whl", hash = "sha256:5cd82d48a3dd89dee1f9d64420aa20ae65cfbd00668d6f094d7578a78efbb77b"}, - {file = "tomlkit-0.12.4.tar.gz", hash = "sha256:7ca1cfc12232806517a8515047ba66a19369e71edf2439d0f5824f91032b6cc3"}, -] - -[[package]] -name = "typing-extensions" -version = "4.11.0" -description = "Backported and Experimental Type Hints for Python 3.8+" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, - {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, -] - -[[package]] -name = "urllib3" -version = "2.2.1" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, - {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "uuid6" -version = "2024.1.12" -description = "New time-based UUID formats which are suited for use as a database key" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "uuid6-2024.1.12-py3-none-any.whl", hash = "sha256:8150093c8d05a331bc0535bc5ef6cf57ac6eceb2404fd319bc10caee2e02c065"}, - {file = "uuid6-2024.1.12.tar.gz", hash = "sha256:ed0afb3a973057575f9883201baefe402787ca5e11e1d24e377190f0c43f1993"}, -] - -[[package]] -name = "uvicorn" -version = "0.29.0" -description = "The lightning-fast ASGI server." -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"}, - {file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"}, -] - -[package.dependencies] -click = ">=7.0" -h11 = ">=0.8" - -[package.extras] -standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] - -[[package]] -name = "uvloop" -version = "0.19.0" -description = "Fast implementation of asyncio event loop on top of libuv" -category = "main" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e"}, - {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428"}, - {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8"}, - {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849"}, - {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957"}, - {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd"}, - {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef"}, - {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2"}, - {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1"}, - {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24"}, - {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533"}, - {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12"}, - {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650"}, - {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec"}, - {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc"}, - {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6"}, - {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593"}, - {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3"}, - {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:78ab247f0b5671cc887c31d33f9b3abfb88d2614b84e4303f1a63b46c046c8bd"}, - {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:472d61143059c84947aa8bb74eabbace30d577a03a1805b77933d6bd13ddebbd"}, - {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45bf4c24c19fb8a50902ae37c5de50da81de4922af65baf760f7c0c42e1088be"}, - {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271718e26b3e17906b28b67314c45d19106112067205119dddbd834c2b7ce797"}, - {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:34175c9fd2a4bc3adc1380e1261f60306344e3407c20a4d684fd5f3be010fa3d"}, - {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e27f100e1ff17f6feeb1f33968bc185bf8ce41ca557deee9d9bbbffeb72030b7"}, - {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13dfdf492af0aa0a0edf66807d2b465607d11c4fa48f4a1fd41cbea5b18e8e8b"}, - {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e3d4e85ac060e2342ff85e90d0c04157acb210b9ce508e784a944f852a40e67"}, - {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca4956c9ab567d87d59d49fa3704cf29e37109ad348f2d5223c9bf761a332e7"}, - {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f467a5fd23b4fc43ed86342641f3936a68ded707f4627622fa3f82a120e18256"}, - {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:492e2c32c2af3f971473bc22f086513cedfc66a130756145a931a90c3958cb17"}, - {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2df95fca285a9f5bfe730e51945ffe2fa71ccbfdde3b0da5772b4ee4f2e770d5"}, - {file = "uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd"}, -] - -[package.extras] -docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] - -[[package]] -name = "virtualenv" -version = "20.26.1" -description = "Virtual Python Environment builder" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "virtualenv-20.26.1-py3-none-any.whl", hash = "sha256:7aa9982a728ae5892558bff6a2839c00b9ed145523ece2274fad6f414690ae75"}, - {file = "virtualenv-20.26.1.tar.gz", hash = "sha256:604bfdceaeece392802e6ae48e69cec49168b9c5f4a44e483963f9242eb0e78b"}, -] - -[package.dependencies] -distlib = ">=0.3.7,<1" -filelock = ">=3.12.2,<4" -platformdirs = ">=3.9.1,<5" - -[package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] - -[[package]] -name = "wrapt" -version = "1.16.0" -description = "Module for decorators, wrappers and monkey patching." -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ - {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, - {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, - {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, - {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, - {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, - {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, - {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, - {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, - {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, - {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, - {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, - {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, - {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, - {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, - {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, - {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, - {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, - {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, -] - -[[package]] -name = "yarl" -version = "1.9.4" -description = "Yet another URL library" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, - {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, - {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, - {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, - {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, - {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, - {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, - {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, - {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, - {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, - {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, - {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, - {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, - {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, - {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, - {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, -] - -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" - -[metadata] -lock-version = "2.0" -python-versions = "^3.11" -content-hash = "e7a19cc10bdf3da2070282d53cab5c43c436dc1bcc4221a8a400a3e4998e6ceb" diff --git a/pyproject.toml b/pyproject.toml index e355e3f..0a44e17 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,57 +1,53 @@ -[tool.poetry] +[project] name = "user_service" -version = "1.0.0" -description = "User service example" +version = "2.0.0" +description = "The user service app" +requires-python = ">=3.13" +readme = "README.md" +license = {file = "LICENSE.md"} + authors = [ - "SamWarden ", + { name = "SamWarden", email = "SamWardenSad@gmail.com" }, ] maintainers = [ - "SamWarden ", + { name = "SamWarden", email = "SamWardenSad@gmail.com" }, ] -license = "MIT" -readme = "README.md" -homepage = "https://github.com/SamWarden/user_service" -repository = "https://github.com/SamWarden/user_service" - -[tool.poetry.dependencies] -python = "^3.11" -sqlalchemy = "^2.0.29" -structlog = "^24.1.0" -fastapi = "^0.110.2" -orjson = "^3.10.1" -alembic = "^1.13.1" -rich = "^13.7.1" -asyncpg = "^0.29.0" -uvicorn = "^0.29.0" -didiator = {version = "^0.3.1", extras = ["di"]} -uuid6 = "^2024.1.12" -aio-pika = "^9.4.1" -uvloop = "^0.19.0" -adaptix = "^3.0.0b5" - -[tool.poetry.group.dev] -optional = true -[tool.poetry.group.dev.dependencies] -pre-commit = "^3.3.2" - -[tool.poetry.group.test] -optional = true - -[tool.poetry.group.test.dependencies] -pytest = "^8.2.0" -pytest-asyncio = "^0.23.6" -testcontainers = {version = "^4.4a.0", extras = ["postgresql"]} -pytest-order = "^1.2.1" -pytest-xdist = "^3.6.1" +dependencies = [ + "adaptix>=3.0.0b10", + "aio-pika>=9.5.5", + "alembic>=1.15.2", + "asyncpg>=0.30.0", + "didiator[di]==0.3.1", + "fastapi>=0.115.12", + "orjson>=3.10.16", + "rich>=14.0.0", + "sqlalchemy>=2.0.40", + "structlog>=25.2.0", + "uuid6>=2024.7.10", + "uvicorn>=0.34.1", + "uvloop>=0.21.0", +] -[tool.poetry.group.lint] -optional = true +[dependency-groups] +dev = ["pre-commit==4.1.*"] +test = [ + "pytest==8.3.*", + "pytest-asyncio>=0.26.0", + "pytest-order>=1.3.0", + "pytest-xdist>=3.6.1", + "testcontainers>=4.10.0", +] +lint = [ + "mypy==1.15.*", + "ruff==0.9.*", + "pylint==3.3.*", +] -[tool.poetry.group.lint.dependencies] -pylint = "^3.1.0" -mypy = "^1.10.0" -ruff = "^0.4.2" +[project.urls] +Homepage = "https://github.com/SamWarden/user_service" +Repository = "https://github.com/SamWarden/user_service" +Issues = "https://github.com/SamWarden/user_service/issues" [tool.black] line-length = 120 @@ -81,5 +77,5 @@ testpaths = ["tests"] asyncio_mode = "auto" [build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" +requires = ["setuptools==75.8.0"] +build-backend = "setuptools.build_meta" From 3c3919f7d36e1cf238cb8ccc7855758f78857c4e Mon Sep 17 00:00:00 2001 From: SamWarden Date: Mon, 14 Apr 2025 14:50:25 +0300 Subject: [PATCH 06/20] feat(uv): add the `uv.lock` file --- uv.lock | 1120 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1120 insertions(+) create mode 100644 uv.lock diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..0b12b07 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1120 @@ +version = 1 +revision = 1 +requires-python = ">=3.13" + +[[package]] +name = "adaptix" +version = "3.0.0b10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/95/2294d649a21dd13142dc3509b8c26a8fb0e3027134cbc4d71e1f8f2ba94a/adaptix-3.0.0b10.tar.gz", hash = "sha256:d15f1e02658d4c443ee2eb1bda6d38fa525d894a31d0208a3d007b5e05fbb3af", size = 132151 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/b2/4c62633268f07fe2cd294e016dc68ea5edd510fd33ca17fa2d608c5c6063/adaptix-3.0.0b10-py3-none-any.whl", hash = "sha256:584bdfc87817e9897baccf601f78ee2a0d0efbc7eadf107ac759be390ffa787a", size = 177172 }, +] + +[[package]] +name = "aio-pika" +version = "9.5.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiormq" }, + { name = "exceptiongroup" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/48/00/5391405f15e85bd6cb859186dbe04d99186ca29410a7cdc52848b55a1d72/aio_pika-9.5.5.tar.gz", hash = "sha256:3d2f25838860fa7e209e21fc95555f558401f9b49a832897419489f1c9e1d6a4", size = 48468 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/cf/efa5581760bd08263bce8dbf943f32006b6dfd5bc120f43a26257281b546/aio_pika-9.5.5-py3-none-any.whl", hash = "sha256:94e0ac3666398d6a28b0c3b530c1febf4c6d4ececb345620727cfd7bfe1c02e0", size = 54257 }, +] + +[[package]] +name = "aiormq" +version = "6.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pamqp" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a4/79/5397756a8782bf3d0dce392b48260c3ec81010f16bef8441ff03505dccb4/aiormq-6.8.1.tar.gz", hash = "sha256:a964ab09634be1da1f9298ce225b310859763d5cf83ef3a7eae1a6dc6bd1da1a", size = 30528 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/be/1a613ae1564426f86650ff58c351902895aa969f7e537e74bfd568f5c8bf/aiormq-6.8.1-py3-none-any.whl", hash = "sha256:5da896c8624193708f9409ffad0b20395010e2747f22aa4150593837f40aa017", size = 31174 }, +] + +[[package]] +name = "alembic" +version = "1.15.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako" }, + { name = "sqlalchemy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e6/57/e314c31b261d1e8a5a5f1908065b4ff98270a778ce7579bd4254477209a7/alembic-1.15.2.tar.gz", hash = "sha256:1c72391bbdeffccfe317eefba686cb9a3c078005478885413b95c3b26c57a8a7", size = 1925573 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/18/d89a443ed1ab9bcda16264716f809c663866d4ca8de218aa78fd50b38ead/alembic-1.15.2-py3-none-any.whl", hash = "sha256:2e76bd916d547f6900ec4bb5a90aeac1485d2c92536923d0b138c02b126edc53", size = 231911 }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, +] + +[[package]] +name = "anyio" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 }, +] + +[[package]] +name = "astroid" +version = "3.3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/39/33/536530122a22a7504b159bccaf30a1f76aa19d23028bd8b5009eb9b2efea/astroid-3.3.9.tar.gz", hash = "sha256:622cc8e3048684aa42c820d9d218978021c3c3d174fb03a9f0d615921744f550", size = 398731 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/80/c749efbd8eef5ea77c7d6f1956e8fbfb51963b7f93ef79647afd4d9886e3/astroid-3.3.9-py3-none-any.whl", hash = "sha256:d05bfd0acba96a7bd43e222828b7d9bc1e138aaeb0649707908d3702a9831248", size = 275339 }, +] + +[[package]] +name = "asyncpg" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/4c/7c991e080e106d854809030d8584e15b2e996e26f16aee6d757e387bc17d/asyncpg-0.30.0.tar.gz", hash = "sha256:c551e9928ab6707602f44811817f82ba3c446e018bfe1d3abecc8ba5f3eac851", size = 957746 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/22/e20602e1218dc07692acf70d5b902be820168d6282e69ef0d3cb920dc36f/asyncpg-0.30.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05b185ebb8083c8568ea8a40e896d5f7af4b8554b64d7719c0eaa1eb5a5c3a70", size = 670373 }, + { url = "https://files.pythonhosted.org/packages/3d/b3/0cf269a9d647852a95c06eb00b815d0b95a4eb4b55aa2d6ba680971733b9/asyncpg-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c47806b1a8cbb0a0db896f4cd34d89942effe353a5035c62734ab13b9f938da3", size = 634745 }, + { url = "https://files.pythonhosted.org/packages/8e/6d/a4f31bf358ce8491d2a31bfe0d7bcf25269e80481e49de4d8616c4295a34/asyncpg-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b6fde867a74e8c76c71e2f64f80c64c0f3163e687f1763cfaf21633ec24ec33", size = 3512103 }, + { url = "https://files.pythonhosted.org/packages/96/19/139227a6e67f407b9c386cb594d9628c6c78c9024f26df87c912fabd4368/asyncpg-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46973045b567972128a27d40001124fbc821c87a6cade040cfcd4fa8a30bcdc4", size = 3592471 }, + { url = "https://files.pythonhosted.org/packages/67/e4/ab3ca38f628f53f0fd28d3ff20edff1c975dd1cb22482e0061916b4b9a74/asyncpg-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9110df111cabc2ed81aad2f35394a00cadf4f2e0635603db6ebbd0fc896f46a4", size = 3496253 }, + { url = "https://files.pythonhosted.org/packages/ef/5f/0bf65511d4eeac3a1f41c54034a492515a707c6edbc642174ae79034d3ba/asyncpg-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04ff0785ae7eed6cc138e73fc67b8e51d54ee7a3ce9b63666ce55a0bf095f7ba", size = 3662720 }, + { url = "https://files.pythonhosted.org/packages/e7/31/1513d5a6412b98052c3ed9158d783b1e09d0910f51fbe0e05f56cc370bc4/asyncpg-0.30.0-cp313-cp313-win32.whl", hash = "sha256:ae374585f51c2b444510cdf3595b97ece4f233fde739aa14b50e0d64e8a7a590", size = 560404 }, + { url = "https://files.pythonhosted.org/packages/c8/a4/cec76b3389c4c5ff66301cd100fe88c318563ec8a520e0b2e792b5b84972/asyncpg-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:f59b430b8e27557c3fb9869222559f7417ced18688375825f8f12302c34e915e", size = 621623 }, +] + +[[package]] +name = "certifi" +version = "2025.1.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, + { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, + { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, + { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, + { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, + { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, + { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, + { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, + { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, + { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, + { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, + { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, + { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, + { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, +] + +[[package]] +name = "click" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "di" +version = "0.75.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "graphlib2" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/c6/aa5bd5edf357335dfa9f05aa7c6bc59d2378773bc03a3f8ffef2c2de34eb/di-0.75.4.tar.gz", hash = "sha256:7d13e3a3486c84d8f3f770f1b48fe5da2394922aa1f95d28070b287acbb04c09", size = 20548 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/59/f02673bfaa267c7477425f5846beea2c5f90ff4ab0d5fa05a3c611d4d07e/di-0.75.4-py3-none-any.whl", hash = "sha256:2afb862083072ab37e54dc9f2b0bcfc879b1282367db6e97f68f698e6ee07b34", size = 24566 }, +] + +[package.optional-dependencies] +anyio = [ + { name = "anyio" }, +] + +[[package]] +name = "didiator" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b3/1d2fb7ffe771bb7fc00653a662509ef586410707df8af556bbe29091d224/didiator-0.3.1.tar.gz", hash = "sha256:05375f397ce73ee52934b6da823d439a263cde33579e10589d93944eb9c2e0b3", size = 14245 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/ec/35624f50f7c2bf86f24d8112349cb4ae4b3cd14b72db6cb6cf58a8e82626/didiator-0.3.1-py3-none-any.whl", hash = "sha256:0fd3d3af901241a457beae81853e91444de46cb2a4fcbeb9ed1fb91fb4ad65b4", size = 22057 }, +] + +[package.optional-dependencies] +di = [ + { name = "di", extra = ["anyio"] }, +] + +[[package]] +name = "dill" +version = "0.3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/43/86fe3f9e130c4137b0f1b50784dd70a5087b911fe07fa81e53e0c4c47fea/dill-0.3.9.tar.gz", hash = "sha256:81aa267dddf68cbfe8029c42ca9ec6a4ab3b22371d1c450abc54422577b4512c", size = 187000 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/d1/e73b6ad76f0b1fb7f23c35c6d95dbc506a9c8804f43dda8cb5b0fa6331fd/dill-0.3.9-py3-none-any.whl", hash = "sha256:468dff3b89520b474c0397703366b7b95eebe6303f108adf9b19da1f702be87a", size = 119418 }, +] + +[[package]] +name = "distlib" +version = "0.3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, +] + +[[package]] +name = "docker" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "requests" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/9b/4a2ea29aeba62471211598dac5d96825bb49348fa07e906ea930394a83ce/docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c", size = 117834 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774 }, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, +] + +[[package]] +name = "execnet" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/ff/b4c0dc78fbe20c3e59c0c7334de0c27eb4001a2b2017999af398bf730817/execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3", size = 166524 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc", size = 40612 }, +] + +[[package]] +name = "fastapi" +version = "0.115.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164 }, +] + +[[package]] +name = "filelock" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215 }, +] + +[[package]] +name = "graphlib2" +version = "0.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a4/a4/0dbdce7ad4b0f9b869bb31bad4b7546d586d41879abd9b6060cb309b9368/graphlib2-0.4.7.tar.gz", hash = "sha256:a951c18cb4c2c2834eec898b4c75d3f930d6f08beb37496f0e0ce56eb3f571f5", size = 390510 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/ed/4c5254b064bf80769364855cb31dc9f6bc589ba1b6119acb2ce76111cbac/graphlib2-0.4.7-cp37-abi3-macosx_10_7_x86_64.whl", hash = "sha256:483710733215783cdc76452ccde1247af8f697685c9c1dfd9bb9ff4f52d990ee", size = 203680 }, + { url = "https://files.pythonhosted.org/packages/53/1f/5096430835c4649cf0847ec529b09a870548b0258f2e4a8bdeea78db5b57/graphlib2-0.4.7-cp37-abi3-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:3619c7d3c5aca95e6cbbfc283aa6bf42ffa5b59d7f39c8d0ad615bce65dc406f", size = 389025 }, + { url = "https://files.pythonhosted.org/packages/cc/f1/0966a43c48cc4747181cd1ecac32699c38ffb43566a75250b092d7ce090a/graphlib2-0.4.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b19f1b91d0f22ca3d1cfb2965478db98cf5916a5c6cea5fdc7caf4bf1bfbc33", size = 206886 }, + { url = "https://files.pythonhosted.org/packages/68/5b/b7e024a4578d7df133dfc81066e47e1dbe2e49c1771c5b6294c83d6a2011/graphlib2-0.4.7-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:624020f6808ee21ffbb2e455f8dd4196bbb37032a35aa3327f0f5b65fb6a35d1", size = 212552 }, + { url = "https://files.pythonhosted.org/packages/44/29/cfd6ff9573bf6f862d5e6e7e4824a59208eed715be185d903029d980d5f2/graphlib2-0.4.7-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6efc6a197a619a97f1b105aea14b202101241c1db9014bd100ad19cf29288cbf", size = 322389 }, + { url = "https://files.pythonhosted.org/packages/cc/5d/176f475be69d6c85e448dae141b455cac78f7bb6f46577240e065131264a/graphlib2-0.4.7-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d7cc38b68775cb2cdfc487bbaca2f7991da0d76d42a68f412c2ca61461e6e026", size = 311704 }, + { url = "https://files.pythonhosted.org/packages/7d/51/376b2a17015adc2893fcf08f913d00137530b10d28647cabf4c3aa1be7f1/graphlib2-0.4.7-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b06bed98d42f4e10adfe2a8332efdca06b5bac6e7c86dd1d22a4dea4de9b275a", size = 278153 }, + { url = "https://files.pythonhosted.org/packages/92/ab/604c1cd20f37fff2a53780cb61abc51319041618c606751a4fc8924dfb54/graphlib2-0.4.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c9ec3a5645bdf020d8bd9196b2665e26090d60e523fd498df29628f2c5fbecc", size = 223083 }, + { url = "https://files.pythonhosted.org/packages/5a/ad/4765785902301c6bc0860953fbfe489f50d1cb9f1162ce3cc51c6f097fb4/graphlib2-0.4.7-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:824df87f767471febfd785a05a2cc77c0c973e0112a548df827763ca0aa8c126", size = 247146 }, + { url = "https://files.pythonhosted.org/packages/27/f4/73b2b48ac62f10189d9305c4ea1781638870a3cb42ffaa6a566f847b6019/graphlib2-0.4.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2de5e32ca5c0b06d442d2be4b378cc0bc335c5fcbc14a7d531a621eb8294d019", size = 387551 }, + { url = "https://files.pythonhosted.org/packages/54/26/797f597ce81e2c9c4c51cdbd74df6e1c25d03e7e34a9c72ebae23ecad5af/graphlib2-0.4.7-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:13a23fcf07c7bef8a5ad0e04ab826d3a2a2bcb493197005300c68b4ea7b8f581", size = 475726 }, + { url = "https://files.pythonhosted.org/packages/8b/39/977c9c8a8f77ffa4543fce6caf771c98ed94483a4cecf60b74ede49a3c2a/graphlib2-0.4.7-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:15a8a6daa28c1fb5c518d387879f3bbe313264fbbc2fab5635b718bc71a24913", size = 419333 }, + { url = "https://files.pythonhosted.org/packages/47/80/c91604bdd85283c9d66a73a42d9d2029a6acd135d4efb206879634fe3765/graphlib2-0.4.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:0cb6c4449834077972c3cea4602f86513b4b75fcf2d40b12e4fe4bf1aa5c8da2", size = 394554 }, + { url = "https://files.pythonhosted.org/packages/a4/8d/bc4546928353e4e2f30ce9531ee9be2e0fde1efc112181f9de69e2a49cdd/graphlib2-0.4.7-cp37-abi3-win32.whl", hash = "sha256:31b40cea537845d80b69403ae306d7c6a68716b76f5171f68daed1804aadefec", size = 145114 }, + { url = "https://files.pythonhosted.org/packages/80/60/f17470522a9bdd43ffbdf0d067490e6b3f8083b46b5387a91551e413cea8/graphlib2-0.4.7-cp37-abi3-win_amd64.whl", hash = "sha256:d40935a9da81a046ebcaa0216ad593ef504ae8a5425a59bdbd254c0462adedc8", size = 150482 }, +] + +[[package]] +name = "greenlet" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/ff/df5fede753cc10f6a5be0931204ea30c35fa2f2ea7a35b25bdaf4fe40e46/greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467", size = 186022 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/57/0db4940cd7bb461365ca8d6fd53e68254c9dbbcc2b452e69d0d41f10a85e/greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1", size = 272990 }, + { url = "https://files.pythonhosted.org/packages/1c/ec/423d113c9f74e5e402e175b157203e9102feeb7088cee844d735b28ef963/greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff", size = 649175 }, + { url = "https://files.pythonhosted.org/packages/a9/46/ddbd2db9ff209186b7b7c621d1432e2f21714adc988703dbdd0e65155c77/greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a", size = 663425 }, + { url = "https://files.pythonhosted.org/packages/bc/f9/9c82d6b2b04aa37e38e74f0c429aece5eeb02bab6e3b98e7db89b23d94c6/greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e", size = 657736 }, + { url = "https://files.pythonhosted.org/packages/d9/42/b87bc2a81e3a62c3de2b0d550bf91a86939442b7ff85abb94eec3fc0e6aa/greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4", size = 660347 }, + { url = "https://files.pythonhosted.org/packages/37/fa/71599c3fd06336cdc3eac52e6871cfebab4d9d70674a9a9e7a482c318e99/greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e", size = 615583 }, + { url = "https://files.pythonhosted.org/packages/4e/96/e9ef85de031703ee7a4483489b40cf307f93c1824a02e903106f2ea315fe/greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1", size = 1133039 }, + { url = "https://files.pythonhosted.org/packages/87/76/b2b6362accd69f2d1889db61a18c94bc743e961e3cab344c2effaa4b4a25/greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c", size = 1160716 }, + { url = "https://files.pythonhosted.org/packages/1f/1b/54336d876186920e185066d8c3024ad55f21d7cc3683c856127ddb7b13ce/greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761", size = 299490 }, + { url = "https://files.pythonhosted.org/packages/5f/17/bea55bf36990e1638a2af5ba10c1640273ef20f627962cf97107f1e5d637/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011", size = 643731 }, + { url = "https://files.pythonhosted.org/packages/78/d2/aa3d2157f9ab742a08e0fd8f77d4699f37c22adfbfeb0c610a186b5f75e0/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13", size = 649304 }, + { url = "https://files.pythonhosted.org/packages/f1/8e/d0aeffe69e53ccff5a28fa86f07ad1d2d2d6537a9506229431a2a02e2f15/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475", size = 646537 }, + { url = "https://files.pythonhosted.org/packages/05/79/e15408220bbb989469c8871062c97c6c9136770657ba779711b90870d867/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b", size = 642506 }, + { url = "https://files.pythonhosted.org/packages/18/87/470e01a940307796f1d25f8167b551a968540fbe0551c0ebb853cb527dd6/greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822", size = 602753 }, + { url = "https://files.pythonhosted.org/packages/e2/72/576815ba674eddc3c25028238f74d7b8068902b3968cbe456771b166455e/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01", size = 1122731 }, + { url = "https://files.pythonhosted.org/packages/ac/38/08cc303ddddc4b3d7c628c3039a61a3aae36c241ed01393d00c2fd663473/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6", size = 1142112 }, +] + +[[package]] +name = "h11" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, +] + +[[package]] +name = "identify" +version = "2.6.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9b/98/a71ab060daec766acc30fb47dfca219d03de34a70d616a79a38c6066c5bf/identify-2.6.9.tar.gz", hash = "sha256:d40dfe3142a1421d8518e3d3985ef5ac42890683e32306ad614a29490abeb6bf", size = 99249 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/ce/0845144ed1f0e25db5e7a79c2354c1da4b5ce392b8966449d5db8dca18f1/identify-2.6.9-py2.py3-none-any.whl", hash = "sha256:c98b4322da415a8e5a70ff6e51fbc2d2932c015532d77e9f8537b4ba7813b150", size = 99101 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, +] + +[[package]] +name = "isort" +version = "6.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b8/21/1e2a441f74a653a144224d7d21afe8f4169e6c7c20bb13aec3a2dc3815e0/isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450", size = 821955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/11/114d0a5f4dabbdcedc1125dee0888514c3c3b16d3e9facad87ed96fad97c/isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615", size = 94186 }, +] + +[[package]] +name = "mako" +version = "1.3.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509 }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350 }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + +[[package]] +name = "multidict" +version = "6.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/2c/e367dfb4c6538614a0c9453e510d75d66099edf1c4e69da1b5ce691a1931/multidict-6.4.3.tar.gz", hash = "sha256:3ada0b058c9f213c5f95ba301f922d402ac234f1111a7d8fd70f1b99f3c281ec", size = 89372 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/4b/86fd786d03915c6f49998cf10cd5fe6b6ac9e9a071cb40885d2e080fb90d/multidict-6.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a76534263d03ae0cfa721fea40fd2b5b9d17a6f85e98025931d41dc49504474", size = 63831 }, + { url = "https://files.pythonhosted.org/packages/45/05/9b51fdf7aef2563340a93be0a663acba2c428c4daeaf3960d92d53a4a930/multidict-6.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:805031c2f599eee62ac579843555ed1ce389ae00c7e9f74c2a1b45e0564a88dd", size = 37888 }, + { url = "https://files.pythonhosted.org/packages/0b/43/53fc25394386c911822419b522181227ca450cf57fea76e6188772a1bd91/multidict-6.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c56c179839d5dcf51d565132185409d1d5dd8e614ba501eb79023a6cab25576b", size = 36852 }, + { url = "https://files.pythonhosted.org/packages/8a/68/7b99c751e822467c94a235b810a2fd4047d4ecb91caef6b5c60116991c4b/multidict-6.4.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c64f4ddb3886dd8ab71b68a7431ad4aa01a8fa5be5b11543b29674f29ca0ba3", size = 223644 }, + { url = "https://files.pythonhosted.org/packages/80/1b/d458d791e4dd0f7e92596667784fbf99e5c8ba040affe1ca04f06b93ae92/multidict-6.4.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3002a856367c0b41cad6784f5b8d3ab008eda194ed7864aaa58f65312e2abcac", size = 230446 }, + { url = "https://files.pythonhosted.org/packages/e2/46/9793378d988905491a7806d8987862dc5a0bae8a622dd896c4008c7b226b/multidict-6.4.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d75e621e7d887d539d6e1d789f0c64271c250276c333480a9e1de089611f790", size = 231070 }, + { url = "https://files.pythonhosted.org/packages/a7/b8/b127d3e1f8dd2a5bf286b47b24567ae6363017292dc6dec44656e6246498/multidict-6.4.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:995015cf4a3c0d72cbf453b10a999b92c5629eaf3a0c3e1efb4b5c1f602253bb", size = 229956 }, + { url = "https://files.pythonhosted.org/packages/0c/93/f70a4c35b103fcfe1443059a2bb7f66e5c35f2aea7804105ff214f566009/multidict-6.4.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2b0fabae7939d09d7d16a711468c385272fa1b9b7fb0d37e51143585d8e72e0", size = 222599 }, + { url = "https://files.pythonhosted.org/packages/63/8c/e28e0eb2fe34921d6aa32bfc4ac75b09570b4d6818cc95d25499fe08dc1d/multidict-6.4.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:61ed4d82f8a1e67eb9eb04f8587970d78fe7cddb4e4d6230b77eda23d27938f9", size = 216136 }, + { url = "https://files.pythonhosted.org/packages/72/f5/fbc81f866585b05f89f99d108be5d6ad170e3b6c4d0723d1a2f6ba5fa918/multidict-6.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:062428944a8dc69df9fdc5d5fc6279421e5f9c75a9ee3f586f274ba7b05ab3c8", size = 228139 }, + { url = "https://files.pythonhosted.org/packages/bb/ba/7d196bad6b85af2307d81f6979c36ed9665f49626f66d883d6c64d156f78/multidict-6.4.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b90e27b4674e6c405ad6c64e515a505c6d113b832df52fdacb6b1ffd1fa9a1d1", size = 226251 }, + { url = "https://files.pythonhosted.org/packages/cc/e2/fae46a370dce79d08b672422a33df721ec8b80105e0ea8d87215ff6b090d/multidict-6.4.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7d50d4abf6729921e9613d98344b74241572b751c6b37feed75fb0c37bd5a817", size = 221868 }, + { url = "https://files.pythonhosted.org/packages/26/20/bbc9a3dec19d5492f54a167f08546656e7aef75d181d3d82541463450e88/multidict-6.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:43fe10524fb0a0514be3954be53258e61d87341008ce4914f8e8b92bee6f875d", size = 233106 }, + { url = "https://files.pythonhosted.org/packages/ee/8d/f30ae8f5ff7a2461177f4d8eb0d8f69f27fb6cfe276b54ec4fd5a282d918/multidict-6.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:236966ca6c472ea4e2d3f02f6673ebfd36ba3f23159c323f5a496869bc8e47c9", size = 230163 }, + { url = "https://files.pythonhosted.org/packages/15/e9/2833f3c218d3c2179f3093f766940ded6b81a49d2e2f9c46ab240d23dfec/multidict-6.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:422a5ec315018e606473ba1f5431e064cf8b2a7468019233dcf8082fabad64c8", size = 225906 }, + { url = "https://files.pythonhosted.org/packages/f1/31/6edab296ac369fd286b845fa5dd4c409e63bc4655ed8c9510fcb477e9ae9/multidict-6.4.3-cp313-cp313-win32.whl", hash = "sha256:f901a5aace8e8c25d78960dcc24c870c8d356660d3b49b93a78bf38eb682aac3", size = 35238 }, + { url = "https://files.pythonhosted.org/packages/23/57/2c0167a1bffa30d9a1383c3dab99d8caae985defc8636934b5668830d2ef/multidict-6.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:1c152c49e42277bc9a2f7b78bd5fa10b13e88d1b0328221e7aef89d5c60a99a5", size = 38799 }, + { url = "https://files.pythonhosted.org/packages/c9/13/2ead63b9ab0d2b3080819268acb297bd66e238070aa8d42af12b08cbee1c/multidict-6.4.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:be8751869e28b9c0d368d94f5afcb4234db66fe8496144547b4b6d6a0645cfc6", size = 68642 }, + { url = "https://files.pythonhosted.org/packages/85/45/f1a751e1eede30c23951e2ae274ce8fad738e8a3d5714be73e0a41b27b16/multidict-6.4.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0d4b31f8a68dccbcd2c0ea04f0e014f1defc6b78f0eb8b35f2265e8716a6df0c", size = 40028 }, + { url = "https://files.pythonhosted.org/packages/a7/29/fcc53e886a2cc5595cc4560df333cb9630257bda65003a7eb4e4e0d8f9c1/multidict-6.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:032efeab3049e37eef2ff91271884303becc9e54d740b492a93b7e7266e23756", size = 39424 }, + { url = "https://files.pythonhosted.org/packages/f6/f0/056c81119d8b88703971f937b371795cab1407cd3c751482de5bfe1a04a9/multidict-6.4.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e78006af1a7c8a8007e4f56629d7252668344442f66982368ac06522445e375", size = 226178 }, + { url = "https://files.pythonhosted.org/packages/a3/79/3b7e5fea0aa80583d3a69c9d98b7913dfd4fbc341fb10bb2fb48d35a9c21/multidict-6.4.3-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:daeac9dd30cda8703c417e4fddccd7c4dc0c73421a0b54a7da2713be125846be", size = 222617 }, + { url = "https://files.pythonhosted.org/packages/06/db/3ed012b163e376fc461e1d6a67de69b408339bc31dc83d39ae9ec3bf9578/multidict-6.4.3-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f6f90700881438953eae443a9c6f8a509808bc3b185246992c4233ccee37fea", size = 227919 }, + { url = "https://files.pythonhosted.org/packages/b1/db/0433c104bca380989bc04d3b841fc83e95ce0c89f680e9ea4251118b52b6/multidict-6.4.3-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f84627997008390dd15762128dcf73c3365f4ec0106739cde6c20a07ed198ec8", size = 226097 }, + { url = "https://files.pythonhosted.org/packages/c2/95/910db2618175724dd254b7ae635b6cd8d2947a8b76b0376de7b96d814dab/multidict-6.4.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3307b48cd156153b117c0ea54890a3bdbf858a5b296ddd40dc3852e5f16e9b02", size = 220706 }, + { url = "https://files.pythonhosted.org/packages/d1/af/aa176c6f5f1d901aac957d5258d5e22897fe13948d1e69063ae3d5d0ca01/multidict-6.4.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ead46b0fa1dcf5af503a46e9f1c2e80b5d95c6011526352fa5f42ea201526124", size = 211728 }, + { url = "https://files.pythonhosted.org/packages/e7/42/d51cc5fc1527c3717d7f85137d6c79bb7a93cd214c26f1fc57523774dbb5/multidict-6.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1748cb2743bedc339d63eb1bca314061568793acd603a6e37b09a326334c9f44", size = 226276 }, + { url = "https://files.pythonhosted.org/packages/28/6b/d836dea45e0b8432343ba4acf9a8ecaa245da4c0960fb7ab45088a5e568a/multidict-6.4.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:acc9fa606f76fc111b4569348cc23a771cb52c61516dcc6bcef46d612edb483b", size = 212069 }, + { url = "https://files.pythonhosted.org/packages/55/34/0ee1a7adb3560e18ee9289c6e5f7db54edc312b13e5c8263e88ea373d12c/multidict-6.4.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:31469d5832b5885adeb70982e531ce86f8c992334edd2f2254a10fa3182ac504", size = 217858 }, + { url = "https://files.pythonhosted.org/packages/04/08/586d652c2f5acefe0cf4e658eedb4d71d4ba6dfd4f189bd81b400fc1bc6b/multidict-6.4.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ba46b51b6e51b4ef7bfb84b82f5db0dc5e300fb222a8a13b8cd4111898a869cf", size = 226988 }, + { url = "https://files.pythonhosted.org/packages/82/e3/cc59c7e2bc49d7f906fb4ffb6d9c3a3cf21b9f2dd9c96d05bef89c2b1fd1/multidict-6.4.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:389cfefb599edf3fcfd5f64c0410da686f90f5f5e2c4d84e14f6797a5a337af4", size = 220435 }, + { url = "https://files.pythonhosted.org/packages/e0/32/5c3a556118aca9981d883f38c4b1bfae646f3627157f70f4068e5a648955/multidict-6.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:64bc2bbc5fba7b9db5c2c8d750824f41c6994e3882e6d73c903c2afa78d091e4", size = 221494 }, + { url = "https://files.pythonhosted.org/packages/b9/3b/1599631f59024b75c4d6e3069f4502409970a336647502aaf6b62fb7ac98/multidict-6.4.3-cp313-cp313t-win32.whl", hash = "sha256:0ecdc12ea44bab2807d6b4a7e5eef25109ab1c82a8240d86d3c1fc9f3b72efd5", size = 41775 }, + { url = "https://files.pythonhosted.org/packages/e8/4e/09301668d675d02ca8e8e1a3e6be046619e30403f5ada2ed5b080ae28d02/multidict-6.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:7146a8742ea71b5d7d955bffcef58a9e6e04efba704b52a460134fefd10a8208", size = 45946 }, + { url = "https://files.pythonhosted.org/packages/96/10/7d526c8974f017f1e7ca584c71ee62a638e9334d8d33f27d7cdfc9ae79e4/multidict-6.4.3-py3-none-any.whl", hash = "sha256:59fe01ee8e2a1e8ceb3f6dbb216b09c8d9f4ef1c22c4fc825d045a147fa2ebc9", size = 10400 }, +] + +[[package]] +name = "mypy" +version = "1.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", size = 3239717 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/9b/fd2e05d6ffff24d912f150b87db9e364fa8282045c875654ce7e32fffa66/mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445", size = 10788592 }, + { url = "https://files.pythonhosted.org/packages/74/37/b246d711c28a03ead1fd906bbc7106659aed7c089d55fe40dd58db812628/mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d", size = 9753611 }, + { url = "https://files.pythonhosted.org/packages/a6/ac/395808a92e10cfdac8003c3de9a2ab6dc7cde6c0d2a4df3df1b815ffd067/mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5", size = 11438443 }, + { url = "https://files.pythonhosted.org/packages/d2/8b/801aa06445d2de3895f59e476f38f3f8d610ef5d6908245f07d002676cbf/mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036", size = 12402541 }, + { url = "https://files.pythonhosted.org/packages/c7/67/5a4268782eb77344cc613a4cf23540928e41f018a9a1ec4c6882baf20ab8/mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357", size = 12494348 }, + { url = "https://files.pythonhosted.org/packages/83/3e/57bb447f7bbbfaabf1712d96f9df142624a386d98fb026a761532526057e/mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf", size = 9373648 }, + { url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", size = 2221777 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, +] + +[[package]] +name = "orjson" +version = "3.10.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/c7/03913cc4332174071950acf5b0735463e3f63760c80585ef369270c2b372/orjson-3.10.16.tar.gz", hash = "sha256:d2aaa5c495e11d17b9b93205f5fa196737ee3202f000aaebf028dc9a73750f10", size = 5410415 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/b9/ff6aa28b8c86af9526160905593a2fe8d004ac7a5e592ee0b0ff71017511/orjson-3.10.16-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:148a97f7de811ba14bc6dbc4a433e0341ffd2cc285065199fb5f6a98013744bd", size = 249289 }, + { url = "https://files.pythonhosted.org/packages/6c/81/6d92a586149b52684ab8fd70f3623c91d0e6a692f30fd8c728916ab2263c/orjson-3.10.16-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:1d960c1bf0e734ea36d0adc880076de3846aaec45ffad29b78c7f1b7962516b8", size = 133640 }, + { url = "https://files.pythonhosted.org/packages/c2/88/b72443f4793d2e16039ab85d0026677932b15ab968595fb7149750d74134/orjson-3.10.16-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a318cd184d1269f68634464b12871386808dc8b7c27de8565234d25975a7a137", size = 138286 }, + { url = "https://files.pythonhosted.org/packages/c3/3c/72a22d4b28c076c4016d5a52bd644a8e4d849d3bb0373d9e377f9e3b2250/orjson-3.10.16-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df23f8df3ef9223d1d6748bea63fca55aae7da30a875700809c500a05975522b", size = 132307 }, + { url = "https://files.pythonhosted.org/packages/8a/a2/f1259561bdb6ad7061ff1b95dab082fe32758c4bc143ba8d3d70831f0a06/orjson-3.10.16-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b94dda8dd6d1378f1037d7f3f6b21db769ef911c4567cbaa962bb6dc5021cf90", size = 136739 }, + { url = "https://files.pythonhosted.org/packages/3d/af/c7583c4b34f33d8b8b90cfaab010ff18dd64e7074cc1e117a5f1eff20dcf/orjson-3.10.16-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f12970a26666a8775346003fd94347d03ccb98ab8aa063036818381acf5f523e", size = 138076 }, + { url = "https://files.pythonhosted.org/packages/d7/59/d7fc7fbdd3d4a64c2eae4fc7341a5aa39cf9549bd5e2d7f6d3c07f8b715b/orjson-3.10.16-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15a1431a245d856bd56e4d29ea0023eb4d2c8f71efe914beb3dee8ab3f0cd7fb", size = 142643 }, + { url = "https://files.pythonhosted.org/packages/92/0e/3bd8f2197d27601f16b4464ae948826da2bcf128af31230a9dbbad7ceb57/orjson-3.10.16-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c83655cfc247f399a222567d146524674a7b217af7ef8289c0ff53cfe8db09f0", size = 133168 }, + { url = "https://files.pythonhosted.org/packages/af/a8/351fd87b664b02f899f9144d2c3dc848b33ac04a5df05234cbfb9e2a7540/orjson-3.10.16-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fa59ae64cb6ddde8f09bdbf7baf933c4cd05734ad84dcf4e43b887eb24e37652", size = 135271 }, + { url = "https://files.pythonhosted.org/packages/ba/b0/a6d42a7d412d867c60c0337d95123517dd5a9370deea705ea1be0f89389e/orjson-3.10.16-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ca5426e5aacc2e9507d341bc169d8af9c3cbe88f4cd4c1cf2f87e8564730eb56", size = 412444 }, + { url = "https://files.pythonhosted.org/packages/79/ec/7572cd4e20863f60996f3f10bc0a6da64a6fd9c35954189a914cec0b7377/orjson-3.10.16-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6fd5da4edf98a400946cd3a195680de56f1e7575109b9acb9493331047157430", size = 152737 }, + { url = "https://files.pythonhosted.org/packages/a9/19/ceb9e8fed5403b2e76a8ac15f581b9d25780a3be3c9b3aa54b7777a210d5/orjson-3.10.16-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:980ecc7a53e567169282a5e0ff078393bac78320d44238da4e246d71a4e0e8f5", size = 137482 }, + { url = "https://files.pythonhosted.org/packages/1b/78/a78bb810f3786579dbbbd94768284cbe8f2fd65167cd7020260679665c17/orjson-3.10.16-cp313-cp313-win32.whl", hash = "sha256:28f79944dd006ac540a6465ebd5f8f45dfdf0948ff998eac7a908275b4c1add6", size = 141714 }, + { url = "https://files.pythonhosted.org/packages/81/9c/b66ce9245ff319df2c3278acd351a3f6145ef34b4a2d7f4b0f739368370f/orjson-3.10.16-cp313-cp313-win_amd64.whl", hash = "sha256:fe0a145e96d51971407cb8ba947e63ead2aa915db59d6631a355f5f2150b56b7", size = 133954 }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "pamqp" +version = "3.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/62/35bbd3d3021e008606cd0a9532db7850c65741bbf69ac8a3a0d8cfeb7934/pamqp-3.3.0.tar.gz", hash = "sha256:40b8795bd4efcf2b0f8821c1de83d12ca16d5760f4507836267fd7a02b06763b", size = 30993 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/8d/c1e93296e109a320e508e38118cf7d1fc2a4d1c2ec64de78565b3c445eb5/pamqp-3.3.0-py2.py3-none-any.whl", hash = "sha256:c901a684794157ae39b52cbf700db8c9aae7a470f13528b9d7b4e5f7202f8eb0", size = 33848 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "pre-commit" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/13/b62d075317d8686071eb843f0bb1f195eb332f48869d3c31a4c6f1e063ac/pre_commit-4.1.0.tar.gz", hash = "sha256:ae3f018575a588e30dfddfab9a05448bfbd6b73d78709617b5a2b853549716d4", size = 193330 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/b3/df14c580d82b9627d173ceea305ba898dca135feb360b6d84019d0803d3b/pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b", size = 220560 }, +] + +[[package]] +name = "propcache" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/07/c8/fdc6686a986feae3541ea23dcaa661bd93972d3940460646c6bb96e21c40/propcache-0.3.1.tar.gz", hash = "sha256:40d980c33765359098837527e18eddefc9a24cea5b45e078a7f3bb5b032c6ecf", size = 43651 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/60/f645cc8b570f99be3cf46714170c2de4b4c9d6b827b912811eff1eb8a412/propcache-0.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f1528ec4374617a7a753f90f20e2f551121bb558fcb35926f99e3c42367164b8", size = 77865 }, + { url = "https://files.pythonhosted.org/packages/6f/d4/c1adbf3901537582e65cf90fd9c26fde1298fde5a2c593f987112c0d0798/propcache-0.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc1915ec523b3b494933b5424980831b636fe483d7d543f7afb7b3bf00f0c10f", size = 45452 }, + { url = "https://files.pythonhosted.org/packages/d1/b5/fe752b2e63f49f727c6c1c224175d21b7d1727ce1d4873ef1c24c9216830/propcache-0.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a110205022d077da24e60b3df8bcee73971be9575dec5573dd17ae5d81751111", size = 44800 }, + { url = "https://files.pythonhosted.org/packages/62/37/fc357e345bc1971e21f76597028b059c3d795c5ca7690d7a8d9a03c9708a/propcache-0.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d249609e547c04d190e820d0d4c8ca03ed4582bcf8e4e160a6969ddfb57b62e5", size = 225804 }, + { url = "https://files.pythonhosted.org/packages/0d/f1/16e12c33e3dbe7f8b737809bad05719cff1dccb8df4dafbcff5575002c0e/propcache-0.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ced33d827625d0a589e831126ccb4f5c29dfdf6766cac441d23995a65825dcb", size = 230650 }, + { url = "https://files.pythonhosted.org/packages/3e/a2/018b9f2ed876bf5091e60153f727e8f9073d97573f790ff7cdf6bc1d1fb8/propcache-0.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4114c4ada8f3181af20808bedb250da6bae56660e4b8dfd9cd95d4549c0962f7", size = 234235 }, + { url = "https://files.pythonhosted.org/packages/45/5f/3faee66fc930dfb5da509e34c6ac7128870631c0e3582987fad161fcb4b1/propcache-0.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:975af16f406ce48f1333ec5e912fe11064605d5c5b3f6746969077cc3adeb120", size = 228249 }, + { url = "https://files.pythonhosted.org/packages/62/1e/a0d5ebda5da7ff34d2f5259a3e171a94be83c41eb1e7cd21a2105a84a02e/propcache-0.3.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a34aa3a1abc50740be6ac0ab9d594e274f59960d3ad253cd318af76b996dd654", size = 214964 }, + { url = "https://files.pythonhosted.org/packages/db/a0/d72da3f61ceab126e9be1f3bc7844b4e98c6e61c985097474668e7e52152/propcache-0.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9cec3239c85ed15bfaded997773fdad9fb5662b0a7cbc854a43f291eb183179e", size = 222501 }, + { url = "https://files.pythonhosted.org/packages/18/6d/a008e07ad7b905011253adbbd97e5b5375c33f0b961355ca0a30377504ac/propcache-0.3.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:05543250deac8e61084234d5fc54f8ebd254e8f2b39a16b1dce48904f45b744b", size = 217917 }, + { url = "https://files.pythonhosted.org/packages/98/37/02c9343ffe59e590e0e56dc5c97d0da2b8b19fa747ebacf158310f97a79a/propcache-0.3.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5cb5918253912e088edbf023788de539219718d3b10aef334476b62d2b53de53", size = 217089 }, + { url = "https://files.pythonhosted.org/packages/53/1b/d3406629a2c8a5666d4674c50f757a77be119b113eedd47b0375afdf1b42/propcache-0.3.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f3bbecd2f34d0e6d3c543fdb3b15d6b60dd69970c2b4c822379e5ec8f6f621d5", size = 228102 }, + { url = "https://files.pythonhosted.org/packages/cd/a7/3664756cf50ce739e5f3abd48febc0be1a713b1f389a502ca819791a6b69/propcache-0.3.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aca63103895c7d960a5b9b044a83f544b233c95e0dcff114389d64d762017af7", size = 230122 }, + { url = "https://files.pythonhosted.org/packages/35/36/0bbabaacdcc26dac4f8139625e930f4311864251276033a52fd52ff2a274/propcache-0.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a0a9898fdb99bf11786265468571e628ba60af80dc3f6eb89a3545540c6b0ef", size = 226818 }, + { url = "https://files.pythonhosted.org/packages/cc/27/4e0ef21084b53bd35d4dae1634b6d0bad35e9c58ed4f032511acca9d4d26/propcache-0.3.1-cp313-cp313-win32.whl", hash = "sha256:3a02a28095b5e63128bcae98eb59025924f121f048a62393db682f049bf4ac24", size = 40112 }, + { url = "https://files.pythonhosted.org/packages/a6/2c/a54614d61895ba6dd7ac8f107e2b2a0347259ab29cbf2ecc7b94fa38c4dc/propcache-0.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:813fbb8b6aea2fc9659815e585e548fe706d6f663fa73dff59a1677d4595a037", size = 44034 }, + { url = "https://files.pythonhosted.org/packages/5a/a8/0a4fd2f664fc6acc66438370905124ce62e84e2e860f2557015ee4a61c7e/propcache-0.3.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a444192f20f5ce8a5e52761a031b90f5ea6288b1eef42ad4c7e64fef33540b8f", size = 82613 }, + { url = "https://files.pythonhosted.org/packages/4d/e5/5ef30eb2cd81576256d7b6caaa0ce33cd1d2c2c92c8903cccb1af1a4ff2f/propcache-0.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0fbe94666e62ebe36cd652f5fc012abfbc2342de99b523f8267a678e4dfdee3c", size = 47763 }, + { url = "https://files.pythonhosted.org/packages/87/9a/87091ceb048efeba4d28e903c0b15bcc84b7c0bf27dc0261e62335d9b7b8/propcache-0.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f011f104db880f4e2166bcdcf7f58250f7a465bc6b068dc84c824a3d4a5c94dc", size = 47175 }, + { url = "https://files.pythonhosted.org/packages/3e/2f/854e653c96ad1161f96194c6678a41bbb38c7947d17768e8811a77635a08/propcache-0.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e584b6d388aeb0001d6d5c2bd86b26304adde6d9bb9bfa9c4889805021b96de", size = 292265 }, + { url = "https://files.pythonhosted.org/packages/40/8d/090955e13ed06bc3496ba4a9fb26c62e209ac41973cb0d6222de20c6868f/propcache-0.3.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a17583515a04358b034e241f952f1715243482fc2c2945fd99a1b03a0bd77d6", size = 294412 }, + { url = "https://files.pythonhosted.org/packages/39/e6/d51601342e53cc7582449e6a3c14a0479fab2f0750c1f4d22302e34219c6/propcache-0.3.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5aed8d8308215089c0734a2af4f2e95eeb360660184ad3912686c181e500b2e7", size = 294290 }, + { url = "https://files.pythonhosted.org/packages/3b/4d/be5f1a90abc1881884aa5878989a1acdafd379a91d9c7e5e12cef37ec0d7/propcache-0.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d8e309ff9a0503ef70dc9a0ebd3e69cf7b3894c9ae2ae81fc10943c37762458", size = 282926 }, + { url = "https://files.pythonhosted.org/packages/57/2b/8f61b998c7ea93a2b7eca79e53f3e903db1787fca9373af9e2cf8dc22f9d/propcache-0.3.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b655032b202028a582d27aeedc2e813299f82cb232f969f87a4fde491a233f11", size = 267808 }, + { url = "https://files.pythonhosted.org/packages/11/1c/311326c3dfce59c58a6098388ba984b0e5fb0381ef2279ec458ef99bd547/propcache-0.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f64d91b751df77931336b5ff7bafbe8845c5770b06630e27acd5dbb71e1931c", size = 290916 }, + { url = "https://files.pythonhosted.org/packages/4b/74/91939924b0385e54dc48eb2e4edd1e4903ffd053cf1916ebc5347ac227f7/propcache-0.3.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:19a06db789a4bd896ee91ebc50d059e23b3639c25d58eb35be3ca1cbe967c3bf", size = 262661 }, + { url = "https://files.pythonhosted.org/packages/c2/d7/e6079af45136ad325c5337f5dd9ef97ab5dc349e0ff362fe5c5db95e2454/propcache-0.3.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:bef100c88d8692864651b5f98e871fb090bd65c8a41a1cb0ff2322db39c96c27", size = 264384 }, + { url = "https://files.pythonhosted.org/packages/b7/d5/ba91702207ac61ae6f1c2da81c5d0d6bf6ce89e08a2b4d44e411c0bbe867/propcache-0.3.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:87380fb1f3089d2a0b8b00f006ed12bd41bd858fabfa7330c954c70f50ed8757", size = 291420 }, + { url = "https://files.pythonhosted.org/packages/58/70/2117780ed7edcd7ba6b8134cb7802aada90b894a9810ec56b7bb6018bee7/propcache-0.3.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e474fc718e73ba5ec5180358aa07f6aded0ff5f2abe700e3115c37d75c947e18", size = 290880 }, + { url = "https://files.pythonhosted.org/packages/4a/1f/ecd9ce27710021ae623631c0146719280a929d895a095f6d85efb6a0be2e/propcache-0.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:17d1c688a443355234f3c031349da69444be052613483f3e4158eef751abcd8a", size = 287407 }, + { url = "https://files.pythonhosted.org/packages/3e/66/2e90547d6b60180fb29e23dc87bd8c116517d4255240ec6d3f7dc23d1926/propcache-0.3.1-cp313-cp313t-win32.whl", hash = "sha256:359e81a949a7619802eb601d66d37072b79b79c2505e6d3fd8b945538411400d", size = 42573 }, + { url = "https://files.pythonhosted.org/packages/cb/8f/50ad8599399d1861b4d2b6b45271f0ef6af1b09b0a2386a46dbaf19c9535/propcache-0.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e7fb9a84c9abbf2b2683fa3e7b0d7da4d8ecf139a1c635732a8bda29c5214b0e", size = 46757 }, + { url = "https://files.pythonhosted.org/packages/b8/d3/c3cb8f1d6ae3b37f83e1de806713a9b3642c5895f0215a62e1a4bd6e5e34/propcache-0.3.1-py3-none-any.whl", hash = "sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40", size = 12376 }, +] + +[[package]] +name = "pydantic" +version = "2.11.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/2e/ca897f093ee6c5f3b0bee123ee4465c50e75431c3d5b6a3b44a47134e891/pydantic-2.11.3.tar.gz", hash = "sha256:7471657138c16adad9322fe3070c0116dd6c3ad8d649300e3cbdfe91f4db4ec3", size = 785513 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/1d/407b29780a289868ed696d1616f4aad49d6388e5a77f567dcd2629dcd7b8/pydantic-2.11.3-py3-none-any.whl", hash = "sha256:a082753436a07f9ba1289c6ffa01cd93db3548776088aa917cc43b63f68fa60f", size = 443591 }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/17/19/ed6a078a5287aea7922de6841ef4c06157931622c89c2a47940837b5eecd/pydantic_core-2.33.1.tar.gz", hash = "sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df", size = 434395 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/24/eed3466a4308d79155f1cdd5c7432c80ddcc4530ba8623b79d5ced021641/pydantic_core-2.33.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70af6a21237b53d1fe7b9325b20e65cbf2f0a848cf77bed492b029139701e66a", size = 2033551 }, + { url = "https://files.pythonhosted.org/packages/ab/14/df54b1a0bc9b6ded9b758b73139d2c11b4e8eb43e8ab9c5847c0a2913ada/pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:282b3fe1bbbe5ae35224a0dbd05aed9ccabccd241e8e6b60370484234b456266", size = 1852785 }, + { url = "https://files.pythonhosted.org/packages/fa/96/e275f15ff3d34bb04b0125d9bc8848bf69f25d784d92a63676112451bfb9/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b315e596282bbb5822d0c7ee9d255595bd7506d1cb20c2911a4da0b970187d3", size = 1897758 }, + { url = "https://files.pythonhosted.org/packages/b7/d8/96bc536e975b69e3a924b507d2a19aedbf50b24e08c80fb00e35f9baaed8/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dfae24cf9921875ca0ca6a8ecb4bb2f13c855794ed0d468d6abbec6e6dcd44a", size = 1986109 }, + { url = "https://files.pythonhosted.org/packages/90/72/ab58e43ce7e900b88cb571ed057b2fcd0e95b708a2e0bed475b10130393e/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dd8ecfde08d8bfadaea669e83c63939af76f4cf5538a72597016edfa3fad516", size = 2129159 }, + { url = "https://files.pythonhosted.org/packages/dc/3f/52d85781406886c6870ac995ec0ba7ccc028b530b0798c9080531b409fdb/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f593494876eae852dc98c43c6f260f45abdbfeec9e4324e31a481d948214764", size = 2680222 }, + { url = "https://files.pythonhosted.org/packages/f4/56/6e2ef42f363a0eec0fd92f74a91e0ac48cd2e49b695aac1509ad81eee86a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:948b73114f47fd7016088e5186d13faf5e1b2fe83f5e320e371f035557fd264d", size = 2006980 }, + { url = "https://files.pythonhosted.org/packages/4c/c0/604536c4379cc78359f9ee0aa319f4aedf6b652ec2854953f5a14fc38c5a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11f3864eb516af21b01e25fac915a82e9ddad3bb0fb9e95a246067398b435a4", size = 2120840 }, + { url = "https://files.pythonhosted.org/packages/1f/46/9eb764814f508f0edfb291a0f75d10854d78113fa13900ce13729aaec3ae/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:549150be302428b56fdad0c23c2741dcdb5572413776826c965619a25d9c6bde", size = 2072518 }, + { url = "https://files.pythonhosted.org/packages/42/e3/fb6b2a732b82d1666fa6bf53e3627867ea3131c5f39f98ce92141e3e3dc1/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:495bc156026efafd9ef2d82372bd38afce78ddd82bf28ef5276c469e57c0c83e", size = 2248025 }, + { url = "https://files.pythonhosted.org/packages/5c/9d/fbe8fe9d1aa4dac88723f10a921bc7418bd3378a567cb5e21193a3c48b43/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ec79de2a8680b1a67a07490bddf9636d5c2fab609ba8c57597e855fa5fa4dacd", size = 2254991 }, + { url = "https://files.pythonhosted.org/packages/aa/99/07e2237b8a66438d9b26482332cda99a9acccb58d284af7bc7c946a42fd3/pydantic_core-2.33.1-cp313-cp313-win32.whl", hash = "sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f", size = 1915262 }, + { url = "https://files.pythonhosted.org/packages/8a/f4/e457a7849beeed1e5defbcf5051c6f7b3c91a0624dd31543a64fc9adcf52/pydantic_core-2.33.1-cp313-cp313-win_amd64.whl", hash = "sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40", size = 1956626 }, + { url = "https://files.pythonhosted.org/packages/20/d0/e8d567a7cff7b04e017ae164d98011f1e1894269fe8e90ea187a3cbfb562/pydantic_core-2.33.1-cp313-cp313-win_arm64.whl", hash = "sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523", size = 1909590 }, + { url = "https://files.pythonhosted.org/packages/ef/fd/24ea4302d7a527d672c5be06e17df16aabfb4e9fdc6e0b345c21580f3d2a/pydantic_core-2.33.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:401d7b76e1000d0dd5538e6381d28febdcacb097c8d340dde7d7fc6e13e9f95d", size = 1812963 }, + { url = "https://files.pythonhosted.org/packages/5f/95/4fbc2ecdeb5c1c53f1175a32d870250194eb2fdf6291b795ab08c8646d5d/pydantic_core-2.33.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aeb055a42d734c0255c9e489ac67e75397d59c6fbe60d155851e9782f276a9c", size = 1986896 }, + { url = "https://files.pythonhosted.org/packages/71/ae/fe31e7f4a62431222d8f65a3bd02e3fa7e6026d154a00818e6d30520ea77/pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18", size = 1931810 }, +] + +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, +] + +[[package]] +name = "pylint" +version = "3.3.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "astroid" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "dill" }, + { name = "isort" }, + { name = "mccabe" }, + { name = "platformdirs" }, + { name = "tomlkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/a7/113d02340afb9dcbb0c8b25454e9538cd08f0ebf3e510df4ed916caa1a89/pylint-3.3.6.tar.gz", hash = "sha256:b634a041aac33706d56a0d217e6587228c66427e20ec21a019bc4cdee48c040a", size = 1519586 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/21/9537fc94aee9ec7316a230a49895266cf02d78aa29b0a2efbc39566e0935/pylint-3.3.6-py3-none-any.whl", hash = "sha256:8b7c2d3e86ae3f94fb27703d521dd0b9b6b378775991f504d7c3a6275aa0a6a6", size = 522462 }, +] + +[[package]] +name = "pytest" +version = "8.3.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, +] + +[[package]] +name = "pytest-asyncio" +version = "0.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8e/c4/453c52c659521066969523e87d85d54139bbd17b78f09532fb8eb8cdb58e/pytest_asyncio-0.26.0.tar.gz", hash = "sha256:c4df2a697648241ff39e7f0e4a73050b03f123f760673956cf0d72a4990e312f", size = 54156 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/7f/338843f449ace853647ace35870874f69a764d251872ed1b4de9f234822c/pytest_asyncio-0.26.0-py3-none-any.whl", hash = "sha256:7b51ed894f4fbea1340262bdae5135797ebbe21d8638978e35d31c6d19f72fb0", size = 19694 }, +] + +[[package]] +name = "pytest-order" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/66/02ae17461b14a52ce5a29ae2900156b9110d1de34721ccc16ccd79419876/pytest_order-1.3.0.tar.gz", hash = "sha256:51608fec3d3ee9c0adaea94daa124a5c4c1d2bb99b00269f098f414307f23dde", size = 47544 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/73/59b038d1aafca89f8e9936eaa8ffa6bb6138d00459d13a32ce070be4f280/pytest_order-1.3.0-py3-none-any.whl", hash = "sha256:2cd562a21380345dd8d5774aa5fd38b7849b6ee7397ca5f6999bbe6e89f07f6e", size = 14609 }, +] + +[[package]] +name = "pytest-xdist" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "execnet" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/c4/3c310a19bc1f1e9ef50075582652673ef2bfc8cd62afef9585683821902f/pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d", size = 84060 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/82/1d96bf03ee4c0fdc3c0cbe61470070e659ca78dc0086fb88b66c185e2449/pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7", size = 46108 }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 }, +] + +[[package]] +name = "pywin32" +version = "310" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/09/9c1b978ffc4ae53999e89c19c77ba882d9fce476729f23ef55211ea1c034/pywin32-310-cp313-cp313-win32.whl", hash = "sha256:5d241a659c496ada3253cd01cfaa779b048e90ce4b2b38cd44168ad555ce74ab", size = 8794384 }, + { url = "https://files.pythonhosted.org/packages/45/3c/b4640f740ffebadd5d34df35fecba0e1cfef8fde9f3e594df91c28ad9b50/pywin32-310-cp313-cp313-win_amd64.whl", hash = "sha256:667827eb3a90208ddbdcc9e860c81bde63a135710e21e4cb3348968e4bd5249e", size = 9503039 }, + { url = "https://files.pythonhosted.org/packages/b4/f4/f785020090fb050e7fb6d34b780f2231f302609dc964672f72bfaeb59a28/pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33", size = 8458152 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[package]] +name = "rich" +version = "14.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229 }, +] + +[[package]] +name = "ruff" +version = "0.9.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/8e/fafaa6f15c332e73425d9c44ada85360501045d5ab0b81400076aff27cf6/ruff-0.9.10.tar.gz", hash = "sha256:9bacb735d7bada9cfb0f2c227d3658fc443d90a727b47f206fb33f52f3c0eac7", size = 3759776 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/b2/af7c2cc9e438cbc19fafeec4f20bfcd72165460fe75b2b6e9a0958c8c62b/ruff-0.9.10-py3-none-linux_armv6l.whl", hash = "sha256:eb4d25532cfd9fe461acc83498361ec2e2252795b4f40b17e80692814329e42d", size = 10049494 }, + { url = "https://files.pythonhosted.org/packages/6d/12/03f6dfa1b95ddd47e6969f0225d60d9d7437c91938a310835feb27927ca0/ruff-0.9.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:188a6638dab1aa9bb6228a7302387b2c9954e455fb25d6b4470cb0641d16759d", size = 10853584 }, + { url = "https://files.pythonhosted.org/packages/02/49/1c79e0906b6ff551fb0894168763f705bf980864739572b2815ecd3c9df0/ruff-0.9.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5284dcac6b9dbc2fcb71fdfc26a217b2ca4ede6ccd57476f52a587451ebe450d", size = 10155692 }, + { url = "https://files.pythonhosted.org/packages/5b/01/85e8082e41585e0e1ceb11e41c054e9e36fed45f4b210991052d8a75089f/ruff-0.9.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47678f39fa2a3da62724851107f438c8229a3470f533894b5568a39b40029c0c", size = 10369760 }, + { url = "https://files.pythonhosted.org/packages/a1/90/0bc60bd4e5db051f12445046d0c85cc2c617095c0904f1aa81067dc64aea/ruff-0.9.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99713a6e2766b7a17147b309e8c915b32b07a25c9efd12ada79f217c9c778b3e", size = 9912196 }, + { url = "https://files.pythonhosted.org/packages/66/ea/0b7e8c42b1ec608033c4d5a02939c82097ddcb0b3e393e4238584b7054ab/ruff-0.9.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524ee184d92f7c7304aa568e2db20f50c32d1d0caa235d8ddf10497566ea1a12", size = 11434985 }, + { url = "https://files.pythonhosted.org/packages/d5/86/3171d1eff893db4f91755175a6e1163c5887be1f1e2f4f6c0c59527c2bfd/ruff-0.9.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:df92aeac30af821f9acf819fc01b4afc3dfb829d2782884f8739fb52a8119a16", size = 12155842 }, + { url = "https://files.pythonhosted.org/packages/89/9e/700ca289f172a38eb0bca752056d0a42637fa17b81649b9331786cb791d7/ruff-0.9.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de42e4edc296f520bb84954eb992a07a0ec5a02fecb834498415908469854a52", size = 11613804 }, + { url = "https://files.pythonhosted.org/packages/f2/92/648020b3b5db180f41a931a68b1c8575cca3e63cec86fd26807422a0dbad/ruff-0.9.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d257f95b65806104b6b1ffca0ea53f4ef98454036df65b1eda3693534813ecd1", size = 13823776 }, + { url = "https://files.pythonhosted.org/packages/5e/a6/cc472161cd04d30a09d5c90698696b70c169eeba2c41030344194242db45/ruff-0.9.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60dec7201c0b10d6d11be00e8f2dbb6f40ef1828ee75ed739923799513db24c", size = 11302673 }, + { url = "https://files.pythonhosted.org/packages/6c/db/d31c361c4025b1b9102b4d032c70a69adb9ee6fde093f6c3bf29f831c85c/ruff-0.9.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d838b60007da7a39c046fcdd317293d10b845001f38bcb55ba766c3875b01e43", size = 10235358 }, + { url = "https://files.pythonhosted.org/packages/d1/86/d6374e24a14d4d93ebe120f45edd82ad7dcf3ef999ffc92b197d81cdc2a5/ruff-0.9.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ccaf903108b899beb8e09a63ffae5869057ab649c1e9231c05ae354ebc62066c", size = 9886177 }, + { url = "https://files.pythonhosted.org/packages/00/62/a61691f6eaaac1e945a1f3f59f1eea9a218513139d5b6c2b8f88b43b5b8f/ruff-0.9.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f9567d135265d46e59d62dc60c0bfad10e9a6822e231f5b24032dba5a55be6b5", size = 10864747 }, + { url = "https://files.pythonhosted.org/packages/ee/94/2c7065e1d92a8a8a46d46d9c3cf07b0aa7e0a1e0153d74baa5e6620b4102/ruff-0.9.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5f202f0d93738c28a89f8ed9eaba01b7be339e5d8d642c994347eaa81c6d75b8", size = 11360441 }, + { url = "https://files.pythonhosted.org/packages/a7/8f/1f545ea6f9fcd7bf4368551fb91d2064d8f0577b3079bb3f0ae5779fb773/ruff-0.9.10-py3-none-win32.whl", hash = "sha256:bfb834e87c916521ce46b1788fbb8484966e5113c02df216680102e9eb960029", size = 10247401 }, + { url = "https://files.pythonhosted.org/packages/4f/18/fb703603ab108e5c165f52f5b86ee2aa9be43bb781703ec87c66a5f5d604/ruff-0.9.10-py3-none-win_amd64.whl", hash = "sha256:f2160eeef3031bf4b17df74e307d4c5fb689a6f3a26a2de3f7ef4044e3c484f1", size = 11366360 }, + { url = "https://files.pythonhosted.org/packages/35/85/338e603dc68e7d9994d5d84f24adbf69bae760ba5efd3e20f5ff2cec18da/ruff-0.9.10-py3-none-win_arm64.whl", hash = "sha256:5fd804c0327a5e5ea26615550e706942f348b197d5475ff34c19733aee4b2e69", size = 10436892 }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.40" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/c3/3f2bfa5e4dcd9938405fe2fab5b6ab94a9248a4f9536ea2fd497da20525f/sqlalchemy-2.0.40.tar.gz", hash = "sha256:d827099289c64589418ebbcaead0145cd19f4e3e8a93919a0100247af245fa00", size = 9664299 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/18/4e3a86cc0232377bc48c373a9ba6a1b3fb79ba32dbb4eda0b357f5a2c59d/sqlalchemy-2.0.40-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:915866fd50dd868fdcc18d61d8258db1bf9ed7fbd6dfec960ba43365952f3b01", size = 2107887 }, + { url = "https://files.pythonhosted.org/packages/cb/60/9fa692b1d2ffc4cbd5f47753731fd332afed30137115d862d6e9a1e962c7/sqlalchemy-2.0.40-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a4c5a2905a9ccdc67a8963e24abd2f7afcd4348829412483695c59e0af9a705", size = 2098367 }, + { url = "https://files.pythonhosted.org/packages/4c/9f/84b78357ca641714a439eb3fbbddb17297dacfa05d951dbf24f28d7b5c08/sqlalchemy-2.0.40-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55028d7a3ebdf7ace492fab9895cbc5270153f75442a0472d8516e03159ab364", size = 3184806 }, + { url = "https://files.pythonhosted.org/packages/4b/7d/e06164161b6bfce04c01bfa01518a20cccbd4100d5c951e5a7422189191a/sqlalchemy-2.0.40-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6cfedff6878b0e0d1d0a50666a817ecd85051d12d56b43d9d425455e608b5ba0", size = 3198131 }, + { url = "https://files.pythonhosted.org/packages/6d/51/354af20da42d7ec7b5c9de99edafbb7663a1d75686d1999ceb2c15811302/sqlalchemy-2.0.40-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bb19e30fdae77d357ce92192a3504579abe48a66877f476880238a962e5b96db", size = 3131364 }, + { url = "https://files.pythonhosted.org/packages/7a/2f/48a41ff4e6e10549d83fcc551ab85c268bde7c03cf77afb36303c6594d11/sqlalchemy-2.0.40-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:16d325ea898f74b26ffcd1cf8c593b0beed8714f0317df2bed0d8d1de05a8f26", size = 3159482 }, + { url = "https://files.pythonhosted.org/packages/33/ac/e5e0a807163652a35be878c0ad5cfd8b1d29605edcadfb5df3c512cdf9f3/sqlalchemy-2.0.40-cp313-cp313-win32.whl", hash = "sha256:a669cbe5be3c63f75bcbee0b266779706f1a54bcb1000f302685b87d1b8c1500", size = 2080704 }, + { url = "https://files.pythonhosted.org/packages/1c/cb/f38c61f7f2fd4d10494c1c135ff6a6ddb63508d0b47bccccd93670637309/sqlalchemy-2.0.40-cp313-cp313-win_amd64.whl", hash = "sha256:641ee2e0834812d657862f3a7de95e0048bdcb6c55496f39c6fa3d435f6ac6ad", size = 2104564 }, + { url = "https://files.pythonhosted.org/packages/d1/7c/5fc8e802e7506fe8b55a03a2e1dab156eae205c91bee46305755e086d2e2/sqlalchemy-2.0.40-py3-none-any.whl", hash = "sha256:32587e2e1e359276957e6fe5dad089758bc042a971a8a09ae8ecf7a8fe23d07a", size = 1903894 }, +] + +[[package]] +name = "starlette" +version = "0.46.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037 }, +] + +[[package]] +name = "structlog" +version = "25.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/78/b8/d3670aec25747e32d54cd5258102ae0d69b9c61c79e7aa326be61a570d0d/structlog-25.2.0.tar.gz", hash = "sha256:d9f9776944207d1035b8b26072b9b140c63702fd7aa57c2f85d28ab701bd8e92", size = 1367438 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/eb/244741c1abf7b4092686db0798a4c43491298f40ddec4226f5c4f6b5d3eb/structlog-25.2.0-py3-none-any.whl", hash = "sha256:0fecea2e345d5d491b72f3db2e5fcd6393abfc8cd06a4851f21fcd4d1a99f437", size = 68448 }, +] + +[[package]] +name = "testcontainers" +version = "4.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docker" }, + { name = "python-dotenv" }, + { name = "typing-extensions" }, + { name = "urllib3" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/49/9c618aff1c50121d183cdfbc3a4a5cf2727a2cde1893efe6ca55c7009196/testcontainers-4.10.0.tar.gz", hash = "sha256:03f85c3e505d8b4edeb192c72a961cebbcba0dd94344ae778b4a159cb6dcf8d3", size = 63327 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/0a/824b0c1ecf224802125279c3effff2e25ed785ed046e67da6e53d928de4c/testcontainers-4.10.0-py3-none-any.whl", hash = "sha256:31ed1a81238c7e131a2a29df6db8f23717d892b592fa5a1977fd0dcd0c23fc23", size = 107414 }, +] + +[[package]] +name = "tomlkit" +version = "0.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/09/a439bec5888f00a54b8b9f05fa94d7f901d6735ef4e55dcec9bc37b5d8fa/tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79", size = 192885 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/b6/a447b5e4ec71e13871be01ba81f5dfc9d0af7e473da256ff46bc0e24026f/tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", size = 37955 }, +] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 }, +] + +[[package]] +name = "urllib3" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680 }, +] + +[[package]] +name = "user-service" +version = "2.0.0" +source = { editable = "." } +dependencies = [ + { name = "adaptix" }, + { name = "aio-pika" }, + { name = "alembic" }, + { name = "asyncpg" }, + { name = "didiator", extra = ["di"] }, + { name = "fastapi" }, + { name = "orjson" }, + { name = "rich" }, + { name = "sqlalchemy" }, + { name = "structlog" }, + { name = "uuid6" }, + { name = "uvicorn" }, + { name = "uvloop" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pre-commit" }, +] +lint = [ + { name = "mypy" }, + { name = "pylint" }, + { name = "ruff" }, +] +test = [ + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-order" }, + { name = "pytest-xdist" }, + { name = "testcontainers" }, +] + +[package.metadata] +requires-dist = [ + { name = "adaptix", specifier = ">=3.0.0b10" }, + { name = "aio-pika", specifier = ">=9.5.5" }, + { name = "alembic", specifier = ">=1.15.2" }, + { name = "asyncpg", specifier = ">=0.30.0" }, + { name = "didiator", extras = ["di"], specifier = "==0.3.1" }, + { name = "fastapi", specifier = ">=0.115.12" }, + { name = "orjson", specifier = ">=3.10.16" }, + { name = "rich", specifier = ">=14.0.0" }, + { name = "sqlalchemy", specifier = ">=2.0.40" }, + { name = "structlog", specifier = ">=25.2.0" }, + { name = "uuid6", specifier = ">=2024.7.10" }, + { name = "uvicorn", specifier = ">=0.34.1" }, + { name = "uvloop", specifier = ">=0.21.0" }, +] + +[package.metadata.requires-dev] +dev = [{ name = "pre-commit", specifier = "==4.1.*" }] +lint = [ + { name = "mypy", specifier = "==1.15.*" }, + { name = "pylint", specifier = "==3.3.*" }, + { name = "ruff", specifier = "==0.9.*" }, +] +test = [ + { name = "pytest", specifier = "==8.3.*" }, + { name = "pytest-asyncio", specifier = ">=0.26.0" }, + { name = "pytest-order", specifier = ">=1.3.0" }, + { name = "pytest-xdist", specifier = ">=3.6.1" }, + { name = "testcontainers", specifier = ">=4.10.0" }, +] + +[[package]] +name = "uuid6" +version = "2024.7.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/56/2560a9f1ccab9e12b1b3478a3c870796cf4d8ee5652bb19b61751cced14a/uuid6-2024.7.10.tar.gz", hash = "sha256:2d29d7f63f593caaeea0e0d0dd0ad8129c9c663b29e19bdf882e864bedf18fb0", size = 8705 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/3e/4ae6af487ce5781ed71d5fe10aca72e7cbc4d4f45afc31b120287082a8dd/uuid6-2024.7.10-py3-none-any.whl", hash = "sha256:93432c00ba403751f722829ad21759ff9db051dea140bf81493271e8e4dd18b7", size = 6376 }, +] + +[[package]] +name = "uvicorn" +version = "0.34.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/37/dd92f1f9cedb5eaf74d9999044306e06abe65344ff197864175dbbd91871/uvicorn-0.34.1.tar.gz", hash = "sha256:af981725fc4b7ffc5cb3b0e9eda6258a90c4b52cb2a83ce567ae0a7ae1757afc", size = 76755 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/38/a5801450940a858c102a7ad9e6150146a25406a119851c993148d56ab041/uvicorn-0.34.1-py3-none-any.whl", hash = "sha256:984c3a8c7ca18ebaad15995ee7401179212c59521e67bfc390c07fa2b8d2e065", size = 62404 }, +] + +[[package]] +name = "uvloop" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/8d/2cbef610ca21539f0f36e2b34da49302029e7c9f09acef0b1c3b5839412b/uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281", size = 1468123 }, + { url = "https://files.pythonhosted.org/packages/93/0d/b0038d5a469f94ed8f2b2fce2434a18396d8fbfb5da85a0a9781ebbdec14/uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af", size = 819325 }, + { url = "https://files.pythonhosted.org/packages/50/94/0a687f39e78c4c1e02e3272c6b2ccdb4e0085fda3b8352fecd0410ccf915/uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6", size = 4582806 }, + { url = "https://files.pythonhosted.org/packages/d2/19/f5b78616566ea68edd42aacaf645adbf71fbd83fc52281fba555dc27e3f1/uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816", size = 4701068 }, + { url = "https://files.pythonhosted.org/packages/47/57/66f061ee118f413cd22a656de622925097170b9380b30091b78ea0c6ea75/uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc", size = 4454428 }, + { url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018 }, +] + +[[package]] +name = "virtualenv" +version = "20.30.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/e0/633e369b91bbc664df47dcb5454b6c7cf441e8f5b9d0c250ce9f0546401e/virtualenv-20.30.0.tar.gz", hash = "sha256:800863162bcaa5450a6e4d721049730e7f2dae07720e0902b0e4040bd6f9ada8", size = 4346945 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/ed/3cfeb48175f0671ec430ede81f628f9fb2b1084c9064ca67ebe8c0ed6a05/virtualenv-20.30.0-py3-none-any.whl", hash = "sha256:e34302959180fca3af42d1800df014b35019490b119eba981af27f2fa486e5d6", size = 4329461 }, +] + +[[package]] +name = "wrapt" +version = "1.17.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/fc/e91cc220803d7bc4db93fb02facd8461c37364151b8494762cc88b0fbcef/wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3", size = 55531 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/b9/0ffd557a92f3b11d4c5d5e0c5e4ad057bd9eb8586615cdaf901409920b14/wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125", size = 53800 }, + { url = "https://files.pythonhosted.org/packages/c0/ef/8be90a0b7e73c32e550c73cfb2fa09db62234227ece47b0e80a05073b375/wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998", size = 38824 }, + { url = "https://files.pythonhosted.org/packages/36/89/0aae34c10fe524cce30fe5fc433210376bce94cf74d05b0d68344c8ba46e/wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5", size = 38920 }, + { url = "https://files.pythonhosted.org/packages/3b/24/11c4510de906d77e0cfb5197f1b1445d4fec42c9a39ea853d482698ac681/wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8", size = 88690 }, + { url = "https://files.pythonhosted.org/packages/71/d7/cfcf842291267bf455b3e266c0c29dcb675b5540ee8b50ba1699abf3af45/wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6", size = 80861 }, + { url = "https://files.pythonhosted.org/packages/d5/66/5d973e9f3e7370fd686fb47a9af3319418ed925c27d72ce16b791231576d/wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc", size = 89174 }, + { url = "https://files.pythonhosted.org/packages/a7/d3/8e17bb70f6ae25dabc1aaf990f86824e4fd98ee9cadf197054e068500d27/wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2", size = 86721 }, + { url = "https://files.pythonhosted.org/packages/6f/54/f170dfb278fe1c30d0ff864513cff526d624ab8de3254b20abb9cffedc24/wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b", size = 79763 }, + { url = "https://files.pythonhosted.org/packages/4a/98/de07243751f1c4a9b15c76019250210dd3486ce098c3d80d5f729cba029c/wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504", size = 87585 }, + { url = "https://files.pythonhosted.org/packages/f9/f0/13925f4bd6548013038cdeb11ee2cbd4e37c30f8bfd5db9e5a2a370d6e20/wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a", size = 36676 }, + { url = "https://files.pythonhosted.org/packages/bf/ae/743f16ef8c2e3628df3ddfd652b7d4c555d12c84b53f3d8218498f4ade9b/wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845", size = 38871 }, + { url = "https://files.pythonhosted.org/packages/3d/bc/30f903f891a82d402ffb5fda27ec1d621cc97cb74c16fea0b6141f1d4e87/wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192", size = 56312 }, + { url = "https://files.pythonhosted.org/packages/8a/04/c97273eb491b5f1c918857cd26f314b74fc9b29224521f5b83f872253725/wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b", size = 40062 }, + { url = "https://files.pythonhosted.org/packages/4e/ca/3b7afa1eae3a9e7fefe499db9b96813f41828b9fdb016ee836c4c379dadb/wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0", size = 40155 }, + { url = "https://files.pythonhosted.org/packages/89/be/7c1baed43290775cb9030c774bc53c860db140397047cc49aedaf0a15477/wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306", size = 113471 }, + { url = "https://files.pythonhosted.org/packages/32/98/4ed894cf012b6d6aae5f5cc974006bdeb92f0241775addad3f8cd6ab71c8/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb", size = 101208 }, + { url = "https://files.pythonhosted.org/packages/ea/fd/0c30f2301ca94e655e5e057012e83284ce8c545df7661a78d8bfca2fac7a/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681", size = 109339 }, + { url = "https://files.pythonhosted.org/packages/75/56/05d000de894c4cfcb84bcd6b1df6214297b8089a7bd324c21a4765e49b14/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6", size = 110232 }, + { url = "https://files.pythonhosted.org/packages/53/f8/c3f6b2cf9b9277fb0813418e1503e68414cd036b3b099c823379c9575e6d/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6", size = 100476 }, + { url = "https://files.pythonhosted.org/packages/a7/b1/0bb11e29aa5139d90b770ebbfa167267b1fc548d2302c30c8f7572851738/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f", size = 106377 }, + { url = "https://files.pythonhosted.org/packages/6a/e1/0122853035b40b3f333bbb25f1939fc1045e21dd518f7f0922b60c156f7c/wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555", size = 37986 }, + { url = "https://files.pythonhosted.org/packages/09/5e/1655cf481e079c1f22d0cabdd4e51733679932718dc23bf2db175f329b76/wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c", size = 40750 }, + { url = "https://files.pythonhosted.org/packages/2d/82/f56956041adef78f849db6b289b282e72b55ab8045a75abad81898c28d19/wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8", size = 23594 }, +] + +[[package]] +name = "yarl" +version = "1.19.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/4d/8a8f57caccce49573e567744926f88c6ab3ca0b47a257806d1cf88584c5f/yarl-1.19.0.tar.gz", hash = "sha256:01e02bb80ae0dbed44273c304095295106e1d9470460e773268a27d11e594892", size = 184396 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/a7/222144efa2f4a47363a5fee27d8a1d24851283b5a7f628890805fe7f7a66/yarl-1.19.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:59281b9ed27bc410e0793833bcbe7fc149739d56ffa071d1e0fe70536a4f7b61", size = 144789 }, + { url = "https://files.pythonhosted.org/packages/72/4f/3ee8de3f94baa33c0716260b0048b1fd5306f104b3efc6e1713693e7063e/yarl-1.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d27a6482ad5e05e8bafd47bf42866f8a1c0c3345abcb48d4511b3c29ecc197dc", size = 96685 }, + { url = "https://files.pythonhosted.org/packages/3e/7c/fbeebf875c1ededd872d6fefabd8a8526ef8aba6e9e8bcdf230d895d487b/yarl-1.19.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7a8e19fd5a6fdf19a91f2409665c7a089ffe7b9b5394ab33c0eec04cbecdd01f", size = 94307 }, + { url = "https://files.pythonhosted.org/packages/f3/ff/b7a9c1d7df37e594b43b7a8030e228ccd4ce361eeff24a92b17fe210e57d/yarl-1.19.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cda34ab19099c3a1685ad48fe45172536610c312b993310b5f1ca3eb83453b36", size = 342811 }, + { url = "https://files.pythonhosted.org/packages/79/e2/9e092876b2156c1d386e4864e85eba541ccabf2b9dcc47da64624bad0cc9/yarl-1.19.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7908a25d33f94852b479910f9cae6cdb9e2a509894e8d5f416c8342c0253c397", size = 336928 }, + { url = "https://files.pythonhosted.org/packages/71/24/648d99c134f2e14fc01ba790ad36ab56815e00069e60a12a4af893448b83/yarl-1.19.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e66c14d162bac94973e767b24de5d7e6c5153f7305a64ff4fcba701210bcd638", size = 351021 }, + { url = "https://files.pythonhosted.org/packages/0c/ee/7278d475784d407d1990a5939722e66a0fef057046fb5f1721f0a6eb156c/yarl-1.19.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c03607bf932aa4cfae371e2dc9ca8b76faf031f106dac6a6ff1458418140c165", size = 354454 }, + { url = "https://files.pythonhosted.org/packages/15/ae/242546114e052a7de21a75bd7d4860266439f90bbc21c5e4dd696866d91d/yarl-1.19.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9931343d1c1f4e77421687b6b94bbebd8a15a64ab8279adf6fbb047eff47e536", size = 347594 }, + { url = "https://files.pythonhosted.org/packages/46/2c/35f4347f76ea4c986e9c1f774b085f489b3a1bf1503c67a4dfc5d8e68e92/yarl-1.19.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:262087a8a0d73e1d169d45c2baf968126f93c97cf403e1af23a7d5455d52721f", size = 334113 }, + { url = "https://files.pythonhosted.org/packages/20/89/3086bc8ec8d7bd505531c51056452d7ae6af906d29c427374f1170ac1938/yarl-1.19.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:70f384921c24e703d249a6ccdabeb57dd6312b568b504c69e428a8dd3e8e68ca", size = 361037 }, + { url = "https://files.pythonhosted.org/packages/a1/5b/2c9765524a70d1c51922b41c91caa30c8094a416734349166e1a3d8de055/yarl-1.19.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:756b9ea5292a2c180d1fe782a377bc4159b3cfefaca7e41b5b0a00328ef62fa9", size = 361025 }, + { url = "https://files.pythonhosted.org/packages/ca/f8/c4a190bcc3cd98fb428d1dd31519e58004153dc7f2acd1236ecae54e3433/yarl-1.19.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cbeb9c145d534c240a63b6ecc8a8dd451faeb67b3dc61d729ec197bb93e29497", size = 364397 }, + { url = "https://files.pythonhosted.org/packages/6b/fb/f65b1347be8e12ac4e3e37a9bb880e6b9b604f252aaafd88e4879b1e9348/yarl-1.19.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:087ae8f8319848c18e0d114d0f56131a9c017f29200ab1413b0137ad7c83e2ae", size = 374065 }, + { url = "https://files.pythonhosted.org/packages/1c/c5/102cc3b9baad1a76f9127453ad08e0f5bc9c996c18128b1e28fe03817d6c/yarl-1.19.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362f5480ba527b6c26ff58cff1f229afe8b7fdd54ee5ffac2ab827c1a75fc71c", size = 381341 }, + { url = "https://files.pythonhosted.org/packages/f7/ce/f5dc0439320dfe59fadab8cdd24ac324be19cf6ae4736422c7e2a510ddf3/yarl-1.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f408d4b4315e814e5c3668094e33d885f13c7809cbe831cbdc5b1bb8c7a448f4", size = 376552 }, + { url = "https://files.pythonhosted.org/packages/a9/4a/4833a134c76af987eff3ce8cb71e42932234120e6be061eb2555061e8844/yarl-1.19.0-cp313-cp313-win32.whl", hash = "sha256:24e4c367ad69988a2283dd45ea88172561ca24b2326b9781e164eb46eea68345", size = 85878 }, + { url = "https://files.pythonhosted.org/packages/32/e9/59327daab3af8f79221638a8f0d11474d20f6a8fbc41e9da80c5ef69e688/yarl-1.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:0110f91c57ab43d1538dfa92d61c45e33b84df9257bd08fcfcda90cce931cbc9", size = 92448 }, + { url = "https://files.pythonhosted.org/packages/a4/06/ae25a353e8f032322df6f30d6bb1fc329773ee48e1a80a2196ccb8d1206b/yarl-1.19.0-py3-none-any.whl", hash = "sha256:a727101eb27f66727576630d02985d8a065d09cd0b5fcbe38a5793f71b2a97ef", size = 45990 }, +] From 3d039a9dcf88dcb855099e5e222da7bc41db37ba Mon Sep 17 00:00:00 2001 From: SamWarden Date: Mon, 14 Apr 2025 14:55:15 +0300 Subject: [PATCH 07/20] refactor(pyproject): remove unused linter config --- pyproject.toml | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0a44e17..6ddbe94 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,29 +49,6 @@ Homepage = "https://github.com/SamWarden/user_service" Repository = "https://github.com/SamWarden/user_service" Issues = "https://github.com/SamWarden/user_service/issues" -[tool.black] -line-length = 120 -target-version = ["py311"] -include_trailing_comma = true -exclude = ''' -( - \.eggs - | \.git - | build - | dist - | venv - | docs -) -''' - -[tool.isort] -profile = "black" -multi_line_output = 3 -line_length = 120 -include_trailing_comma = true -combine_as_imports = true -remove_redundant_aliases = true - [tool.pytest.ini_options] testpaths = ["tests"] asyncio_mode = "auto" From e57c70d9c80e2756429ffc6e1abcd53949370765 Mon Sep 17 00:00:00 2001 From: SamWarden Date: Mon, 14 Apr 2025 15:04:49 +0300 Subject: [PATCH 08/20] fix(alembic): adopt the `alembic` tool to use src-layout --- alembic.ini | 2 +- tests/integration/test_stairway.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/alembic.ini b/alembic.ini index 0cab6c0..6f89e83 100644 --- a/alembic.ini +++ b/alembic.ini @@ -4,7 +4,7 @@ # sqlalchemy.url = driver://user:pass@localhost/dbname # path to migration scripts -script_location = ./src/infrastructure/db/migrations +script_location = user_service:infrastructure/db/migrations # Format for migration versions filenames file_template = %%(year)d%%(month).2d%%(day).2d-%%(hour).2d%%(minute).2d%%(second).2d_%%(rev)s_%%(slug)s # sys.path path, will be prepended to sys.path if present. diff --git a/tests/integration/test_stairway.py b/tests/integration/test_stairway.py index 1ab7c53..763ddb9 100644 --- a/tests/integration/test_stairway.py +++ b/tests/integration/test_stairway.py @@ -15,7 +15,7 @@ def get_revisions(alembic_config: AlembicConfig) -> list[Script]: # Get directory object with Alembic migrations - revisions_dir = ScriptDirectory(alembic_config.get_main_option("script_location")) + revisions_dir = ScriptDirectory.from_config(alembic_config) # Get & sort migrations, from first to last revisions: list[Script] = list(revisions_dir.walk_revisions("base", "heads")) From 5c430a8bae0825aae6bded061483c3be6dd36a81 Mon Sep 17 00:00:00 2001 From: SamWarden Date: Mon, 14 Apr 2025 20:02:26 +0300 Subject: [PATCH 09/20] feat(app): add the domain service and imperative mapping --- Dockerfile | 4 +- docker-compose.yaml | 7 +- src/user_service/application/common/event.py | 2 +- src/user_service/application/user/__init__.py | 7 +- .../application/user/commands/create_user.py | 17 ++- .../application/user/commands/delete_user.py | 14 +-- .../user/commands/set_user_full_name.py | 14 +-- .../user/commands/set_user_username.py | 14 +-- .../application/user/dto/__init__.py | 10 -- src/user_service/application/user/dto/user.py | 20 ---- .../application/user/dto/users.py | 9 -- .../application/user/interfaces/__init__.py | 7 +- .../user/interfaces/persistence/__init__.py | 2 - .../user/interfaces/persistence/reader.py | 4 +- .../user/queries/get_user_by_id.py | 10 +- .../user/queries/get_user_by_username.py | 4 + .../domain/common/entities/__init__.py | 7 -- .../domain/common/{entities => }/entity.py | 0 .../domain/common/{events => }/event.py | 0 .../domain/common/events/__init__.py | 3 - .../{exceptions/base.py => exceptions.py} | 0 .../domain/common/exceptions/__init__.py | 6 - .../aggregate_root.py => service.py} | 13 +-- .../base.py => value_objects.py} | 0 .../domain/common/value_objects/__init__.py | 6 - .../domain/user/entities/__init__.py | 3 - .../domain/user/events/full_name_updated.py | 2 +- .../domain/user/events/user_created.py | 2 +- .../domain/user/events/user_deleted.py | 2 +- .../domain/user/events/username_updated.py | 2 +- .../domain/user/interfaces/__init__.py | 0 .../user/interfaces}/repo.py | 8 +- src/user_service/domain/user/service.py | 86 ++++++++++++++ .../domain/user/value_objects/__init__.py | 2 + .../{deleted_status.py => deletion_time.py} | 0 .../domain/user/value_objects/full_name.py | 2 +- .../domain/user/value_objects/user_id.py | 2 +- .../domain/user/value_objects/username.py | 2 +- .../infrastructure/db/converters/__init__.py | 0 ...dd_deleted_at_column_to_the_users_table.py | 4 +- .../infrastructure/db/models/__init__.py | 6 +- .../infrastructure/db/models/user.py | 21 ---- .../infrastructure/db/readers/__init__.py | 3 + .../infrastructure/db/readers/base.py | 6 + .../infrastructure/db/readers/user.py | 71 +++++++++++ .../db/repositories/__init__.py | 7 +- .../infrastructure/db/repositories/user.py | 110 +++--------------- src/user_service/infrastructure/di/main.py | 6 +- .../infrastructure/event_bus/event_handler.py | 2 +- .../infrastructure/log/event_handler.py | 2 +- .../infrastructure/mediator/main.py | 2 +- .../presentation/api/controllers/user.py | 19 ++- tests/integration/conftest.py | 2 +- tests/mocks/user_reader.py | 20 ++-- tests/mocks/user_repo.py | 18 +-- .../application/commands/test_create_user.py | 9 +- .../application/commands/test_delete_user.py | 22 ++-- .../commands/test_set_user_full_name.py | 18 +-- .../commands/test_set_user_username.py | 26 ++--- tests/unit/application/conftest.py | 7 ++ .../queries/test_get_user_by_id.py | 12 +- .../queries/test_get_user_by_username.py | 2 + .../application/queries/test_get_users.py | 12 +- 63 files changed, 340 insertions(+), 360 deletions(-) delete mode 100644 src/user_service/application/user/dto/__init__.py delete mode 100644 src/user_service/application/user/dto/user.py delete mode 100644 src/user_service/application/user/dto/users.py delete mode 100644 src/user_service/domain/common/entities/__init__.py rename src/user_service/domain/common/{entities => }/entity.py (100%) rename src/user_service/domain/common/{events => }/event.py (100%) delete mode 100644 src/user_service/domain/common/events/__init__.py rename src/user_service/domain/common/{exceptions/base.py => exceptions.py} (100%) delete mode 100644 src/user_service/domain/common/exceptions/__init__.py rename src/user_service/domain/common/{entities/aggregate_root.py => service.py} (50%) rename src/user_service/domain/common/{value_objects/base.py => value_objects.py} (100%) delete mode 100644 src/user_service/domain/common/value_objects/__init__.py delete mode 100644 src/user_service/domain/user/entities/__init__.py create mode 100644 src/user_service/domain/user/interfaces/__init__.py rename src/user_service/{application/user/interfaces/persistence => domain/user/interfaces}/repo.py (71%) create mode 100644 src/user_service/domain/user/service.py rename src/user_service/domain/user/value_objects/{deleted_status.py => deletion_time.py} (100%) create mode 100644 src/user_service/infrastructure/db/converters/__init__.py delete mode 100644 src/user_service/infrastructure/db/models/user.py create mode 100644 src/user_service/infrastructure/db/readers/__init__.py create mode 100644 src/user_service/infrastructure/db/readers/base.py create mode 100644 src/user_service/infrastructure/db/readers/user.py diff --git a/Dockerfile b/Dockerfile index 15e261a..d96c70b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,14 +11,14 @@ ENV PYTHONUNBUFFERED=1 \ ENV VIRTUAL_ENV="$APP_PATH/.venv" ENV PATH="$VIRTUAL_ENV/bin:$PATH" +WORKDIR $APP_PATH + FROM python-base AS builder RUN apt-get update \ && apt-get install -y --no-install-recommends gcc git \ && rm -rf /var/lib/apt/lists/ -WORKDIR $APP_PATH - RUN pip install --no-cache-dir "uv==$UV_VERSION" COPY ./pyproject.toml ./uv.lock ./ diff --git a/docker-compose.yaml b/docker-compose.yaml index 0545d40..c8884e9 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -19,9 +19,9 @@ services: - user_service.postgres.network - user_service.rabbitmq.network volumes: - - ./config:/config:ro + - ./config:/app/config:ro environment: - - CONFIG_PATH=${CONFIG_PATH:-/config/prod_config.toml} + - CONFIG_PATH=${CONFIG_PATH:-/app/config/prod_config.toml} healthcheck: test: ["CMD-SHELL", "curl -fsSL http://localhost:5000/healthcheck"] interval: 10s @@ -44,7 +44,6 @@ services: volumes: - ./config:/app/config:ro - ./alembic.ini:/app/alembic.ini:ro - - ./src/infrastructure/db/migrations:/app/src/infrastructure/db/migrations:ro environment: - CONFIG_PATH=${CONFIG_PATH:-./config/prod_config.toml} command: ["python", "-m", "alembic", "upgrade", "head"] @@ -52,7 +51,7 @@ services: postgres: profiles: [ "api", "migration" ] container_name: user_service.postgres - image: "postgres:16-alpine" + image: "postgres:17-alpine" hostname: user_service.postgres restart: unless-stopped expose: diff --git a/src/user_service/application/common/event.py b/src/user_service/application/common/event.py index 82fb123..eeeb861 100644 --- a/src/user_service/application/common/event.py +++ b/src/user_service/application/common/event.py @@ -3,7 +3,7 @@ import didiator -from user_service.domain.common.events.event import Event +from user_service.domain.common.event import Event E = TypeVar("E", bound=Event) diff --git a/src/user_service/application/user/__init__.py b/src/user_service/application/user/__init__.py index 38db213..5a7d5b2 100644 --- a/src/user_service/application/user/__init__.py +++ b/src/user_service/application/user/__init__.py @@ -1,6 +1,3 @@ -from .dto import DeletedUser, User +from .dto import User -__all__ = ( - "DeletedUser", - "User", -) +__all__ = ("User",) diff --git a/src/user_service/application/user/commands/create_user.py b/src/user_service/application/user/commands/create_user.py index 8ce278d..f69d721 100644 --- a/src/user_service/application/user/commands/create_user.py +++ b/src/user_service/application/user/commands/create_user.py @@ -6,8 +6,7 @@ from user_service.application.common.command import Command, CommandHandler from user_service.application.common.interfaces.uow import UnitOfWork -from user_service.application.user.interfaces import UserRepo -from user_service.domain.user.entities import User +from user_service.domain.user.service import UserService from user_service.domain.user.value_objects import FullName, UserId, Username logger = logging.getLogger(__name__) @@ -25,11 +24,11 @@ class CreateUser(Command[UUID]): class CreateUserHandler(CommandHandler[CreateUser, UUID]): def __init__( self, - user_repo: UserRepo, + user_service: UserService, uow: UnitOfWork, mediator: EventMediator, ) -> None: - self._user_repo = user_repo + self._user_service = user_service self._uow = uow self._mediator = mediator @@ -38,12 +37,10 @@ async def __call__(self, command: CreateUser) -> UUID: username = Username(command.username) full_name = FullName(command.first_name, command.last_name, command.middle_name) - existing_usernames = await self._user_repo.get_existing_usernames() - user = User.create(user_id, username, full_name, existing_usernames) - await self._user_repo.add_user(user) - await self._mediator.publish(user.pull_events()) + user = await self._user_service.create_user(user_id, username, full_name) + await self._mediator.publish(self._user_service.pull_events()) await self._uow.commit() - logger.info("User created", extra={"user": user}) + logger.info("User created", extra={"user_id": user.id.to_raw(), "user": user}) - return command.user_id + return user.id.to_raw() diff --git a/src/user_service/application/user/commands/delete_user.py b/src/user_service/application/user/commands/delete_user.py index 901463d..d307f4c 100644 --- a/src/user_service/application/user/commands/delete_user.py +++ b/src/user_service/application/user/commands/delete_user.py @@ -6,7 +6,7 @@ from user_service.application.common.command import Command, CommandHandler from user_service.application.common.interfaces.uow import UnitOfWork -from user_service.application.user.interfaces import UserRepo +from user_service.domain.user.service import UserService from user_service.domain.user.value_objects import UserId logger = logging.getLogger(__name__) @@ -20,21 +20,19 @@ class DeleteUser(Command[None]): class DeleteUserHandler(CommandHandler[DeleteUser, None]): def __init__( self, - user_repo: UserRepo, + user_service: UserService, uow: UnitOfWork, mediator: EventMediator, ) -> None: - self._user_repo = user_repo + self._user_service = user_service self._uow = uow self._mediator = mediator async def __call__(self, command: DeleteUser) -> None: user_id = UserId(command.user_id) - user = await self._user_repo.acquire_user_by_id(user_id) - user.delete() - await self._user_repo.update_user(user) - await self._mediator.publish(user.pull_events()) + await self._user_service.delete_user(user_id) + await self._mediator.publish(self._user_service.pull_events()) await self._uow.commit() - logger.info("User deleted", extra={"user": user}) + logger.info("User deleted", extra={"user_id": user_id.to_raw()}) diff --git a/src/user_service/application/user/commands/set_user_full_name.py b/src/user_service/application/user/commands/set_user_full_name.py index f52473d..d3d624e 100644 --- a/src/user_service/application/user/commands/set_user_full_name.py +++ b/src/user_service/application/user/commands/set_user_full_name.py @@ -7,7 +7,7 @@ from user_service.application.common.command import Command, CommandHandler from user_service.application.common.interfaces.uow import UnitOfWork from user_service.application.user import dto -from user_service.application.user.interfaces import UserRepo +from user_service.domain.user.service import UserService from user_service.domain.user.value_objects import FullName, UserId logger = logging.getLogger(__name__) @@ -24,11 +24,11 @@ class SetUserFullName(Command[dto.User]): class SetUserFullNameHandler(CommandHandler[SetUserFullName, None]): def __init__( self, - user_repo: UserRepo, + user_service: UserService, uow: UnitOfWork, mediator: EventMediator, ) -> None: - self._user_repo = user_repo + self._user_service = user_service self._uow = uow self._mediator = mediator @@ -36,10 +36,8 @@ async def __call__(self, command: SetUserFullName) -> None: user_id = UserId(command.user_id) full_name = FullName(command.first_name, command.last_name, command.middle_name) - user = await self._user_repo.acquire_user_by_id(user_id) - user.set_full_name(full_name) - await self._user_repo.update_user(user) - await self._mediator.publish(user.pull_events()) + await self._user_service.set_user_full_name(user_id, full_name) + await self._mediator.publish(self._user_service.pull_events()) await self._uow.commit() - logger.info("Full name updated", extra={"user": user}) + logger.info("Full name updated", extra={"user_id": user_id.to_raw(), "full_name": str(full_name)}) diff --git a/src/user_service/application/user/commands/set_user_username.py b/src/user_service/application/user/commands/set_user_username.py index ef27843..14c6043 100644 --- a/src/user_service/application/user/commands/set_user_username.py +++ b/src/user_service/application/user/commands/set_user_username.py @@ -6,7 +6,7 @@ from user_service.application.common.command import Command, CommandHandler from user_service.application.common.interfaces.uow import UnitOfWork -from user_service.application.user.interfaces import UserRepo +from user_service.domain.user.service import UserService from user_service.domain.user.value_objects import UserId, Username logger = logging.getLogger(__name__) @@ -21,11 +21,11 @@ class SetUserUsername(Command[None]): class SetUserUsernameHandler(CommandHandler[SetUserUsername, None]): def __init__( self, - user_repo: UserRepo, + user_service: UserService, uow: UnitOfWork, mediator: EventMediator, ) -> None: - self._user_repo = user_repo + self._user_service = user_service self._uow = uow self._mediator = mediator @@ -33,10 +33,8 @@ async def __call__(self, command: SetUserUsername) -> None: user_id = UserId(command.user_id) username = Username(command.username) - user = await self._user_repo.acquire_user_by_id(user_id) - user.set_username(username) - await self._user_repo.update_user(user) - await self._mediator.publish(user.pull_events()) + await self._user_service.set_user_username(user_id, username) + await self._mediator.publish(self._user_service.pull_events()) await self._uow.commit() - logger.info("Username updated", extra={"user": user}) + logger.info("Username updated", extra={"user_id": user_id.to_raw(), "username": username.to_raw()}) diff --git a/src/user_service/application/user/dto/__init__.py b/src/user_service/application/user/dto/__init__.py deleted file mode 100644 index 7c35573..0000000 --- a/src/user_service/application/user/dto/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from .deleted_user import DeletedUser -from .user import User -from .users import UserDTOs, Users - -__all__ = ( - "DeletedUser", - "User", - "UserDTOs", - "Users", -) diff --git a/src/user_service/application/user/dto/user.py b/src/user_service/application/user/dto/user.py deleted file mode 100644 index 2a6cf4b..0000000 --- a/src/user_service/application/user/dto/user.py +++ /dev/null @@ -1,20 +0,0 @@ -from dataclasses import dataclass, field -from uuid import UUID - -from user_service.application.common.dto import DTO - - -@dataclass(frozen=True) -class User(DTO): - id: UUID - username: str - first_name: str - last_name: str - middle_name: str | None - deleted_at: None = field(default=None, init=False) - - @property - def full_name(self) -> str: - if self.middle_name: - return f"{self.first_name} {self.middle_name} {self.last_name}" - return f"{self.first_name} {self.last_name}" diff --git a/src/user_service/application/user/dto/users.py b/src/user_service/application/user/dto/users.py deleted file mode 100644 index 7c7ef48..0000000 --- a/src/user_service/application/user/dto/users.py +++ /dev/null @@ -1,9 +0,0 @@ -from typing import TypeAlias - -from user_service.application.common.pagination.dto import PaginatedItemsDTO - -from .deleted_user import DeletedUser -from .user import User - -UserDTOs: TypeAlias = User | DeletedUser -Users: TypeAlias = PaginatedItemsDTO[UserDTOs] diff --git a/src/user_service/application/user/interfaces/__init__.py b/src/user_service/application/user/interfaces/__init__.py index 4342df7..b3369da 100644 --- a/src/user_service/application/user/interfaces/__init__.py +++ b/src/user_service/application/user/interfaces/__init__.py @@ -1,6 +1,3 @@ -from .persistence import UserReader, UserRepo +from .persistence import UserReader -__all__ = ( - "UserReader", - "UserRepo", -) +__all__ = ("UserReader",) diff --git a/src/user_service/application/user/interfaces/persistence/__init__.py b/src/user_service/application/user/interfaces/persistence/__init__.py index a1598ba..d6b5e90 100644 --- a/src/user_service/application/user/interfaces/persistence/__init__.py +++ b/src/user_service/application/user/interfaces/persistence/__init__.py @@ -1,8 +1,6 @@ from .reader import GetUsersFilters, UserReader -from .repo import UserRepo __all__ = ( "GetUsersFilters", "UserReader", - "UserRepo", ) diff --git a/src/user_service/application/user/interfaces/persistence/reader.py b/src/user_service/application/user/interfaces/persistence/reader.py index 01e9bfe..5430d4a 100644 --- a/src/user_service/application/user/interfaces/persistence/reader.py +++ b/src/user_service/application/user/interfaces/persistence/reader.py @@ -13,10 +13,10 @@ class GetUsersFilters: class UserReader(Protocol): - async def get_user_by_id(self, user_id: UUID) -> dto.UserDTOs: + async def get_user_by_id(self, user_id: UUID) -> dto.User | None: raise NotImplementedError - async def get_user_by_username(self, username: str) -> dto.User: + async def get_user_by_username(self, username: str) -> dto.User | None: raise NotImplementedError async def get_users(self, filters: GetUsersFilters, pagination: Pagination) -> dto.Users: diff --git a/src/user_service/application/user/queries/get_user_by_id.py b/src/user_service/application/user/queries/get_user_by_id.py index fa686d8..a1347a0 100644 --- a/src/user_service/application/user/queries/get_user_by_id.py +++ b/src/user_service/application/user/queries/get_user_by_id.py @@ -4,21 +4,25 @@ from user_service.application.common.query import Query, QueryHandler from user_service.application.user import dto +from user_service.application.user.exceptions import UserIdNotExistError from user_service.application.user.interfaces import UserReader logger = logging.getLogger(__name__) @dataclass(frozen=True) -class GetUserById(Query[dto.UserDTOs]): +class GetUserById(Query[dto.User]): user_id: UUID -class GetUserByIdHandler(QueryHandler[GetUserById, dto.UserDTOs]): +class GetUserByIdHandler(QueryHandler[GetUserById, dto.User]): def __init__(self, user_reader: UserReader) -> None: self._user_reader = user_reader - async def __call__(self, query: GetUserById) -> dto.UserDTOs: + async def __call__(self, query: GetUserById) -> dto.User: user = await self._user_reader.get_user_by_id(query.user_id) + if user is None: + raise UserIdNotExistError(query.user_id) + logger.debug("Get user by id", extra={"user_id": query.user_id, "user": user}) return user diff --git a/src/user_service/application/user/queries/get_user_by_username.py b/src/user_service/application/user/queries/get_user_by_username.py index c6797ef..db2d550 100644 --- a/src/user_service/application/user/queries/get_user_by_username.py +++ b/src/user_service/application/user/queries/get_user_by_username.py @@ -3,6 +3,7 @@ from user_service.application.common.query import Query, QueryHandler from user_service.application.user import dto +from user_service.application.user.exceptions import UsernameNotExistError from user_service.application.user.interfaces import UserReader logger = logging.getLogger(__name__) @@ -19,5 +20,8 @@ def __init__(self, user_reader: UserReader) -> None: async def __call__(self, query: GetUserByUsername) -> dto.User: user = await self._user_reader.get_user_by_username(query.username) + if user is None: + raise UsernameNotExistError(query.username) + logger.debug("Get user by username", extra={"username": query.username, "user": user}) return user diff --git a/src/user_service/domain/common/entities/__init__.py b/src/user_service/domain/common/entities/__init__.py deleted file mode 100644 index 744c907..0000000 --- a/src/user_service/domain/common/entities/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from .aggregate_root import AggregateRoot -from .entity import Entity - -__all__ = ( - "Entity", - "AggregateRoot", -) diff --git a/src/user_service/domain/common/entities/entity.py b/src/user_service/domain/common/entity.py similarity index 100% rename from src/user_service/domain/common/entities/entity.py rename to src/user_service/domain/common/entity.py diff --git a/src/user_service/domain/common/events/event.py b/src/user_service/domain/common/event.py similarity index 100% rename from src/user_service/domain/common/events/event.py rename to src/user_service/domain/common/event.py diff --git a/src/user_service/domain/common/events/__init__.py b/src/user_service/domain/common/events/__init__.py deleted file mode 100644 index 77290f5..0000000 --- a/src/user_service/domain/common/events/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .event import Event - -__all__ = ("Event",) diff --git a/src/user_service/domain/common/exceptions/base.py b/src/user_service/domain/common/exceptions.py similarity index 100% rename from src/user_service/domain/common/exceptions/base.py rename to src/user_service/domain/common/exceptions.py diff --git a/src/user_service/domain/common/exceptions/__init__.py b/src/user_service/domain/common/exceptions/__init__.py deleted file mode 100644 index dc4f03c..0000000 --- a/src/user_service/domain/common/exceptions/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .base import AppError, DomainError - -__all__ = ( - "AppError", - "DomainError", -) diff --git a/src/user_service/domain/common/entities/aggregate_root.py b/src/user_service/domain/common/service.py similarity index 50% rename from src/user_service/domain/common/entities/aggregate_root.py rename to src/user_service/domain/common/service.py index bca9915..116b327 100644 --- a/src/user_service/domain/common/entities/aggregate_root.py +++ b/src/user_service/domain/common/service.py @@ -1,16 +1,13 @@ from abc import ABC -from dataclasses import dataclass, field -from user_service.domain.common.events.event import Event +from user_service.domain.common.event import Event -from .entity import Entity +class BaseService(ABC): + def __init__(self) -> None: + self._events: list[Event] = [] -@dataclass -class AggregateRoot(Entity, ABC): - _events: list[Event] = field(default_factory=list, init=False, repr=False, hash=False, compare=False) - - def record_event(self, event: Event) -> None: + def _record_event(self, event: Event) -> None: self._events.append(event) def get_events(self) -> list[Event]: diff --git a/src/user_service/domain/common/value_objects/base.py b/src/user_service/domain/common/value_objects.py similarity index 100% rename from src/user_service/domain/common/value_objects/base.py rename to src/user_service/domain/common/value_objects.py diff --git a/src/user_service/domain/common/value_objects/__init__.py b/src/user_service/domain/common/value_objects/__init__.py deleted file mode 100644 index 0946588..0000000 --- a/src/user_service/domain/common/value_objects/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .base import BaseValueObject, ValueObject - -__all__ = ( - "BaseValueObject", - "ValueObject", -) diff --git a/src/user_service/domain/user/entities/__init__.py b/src/user_service/domain/user/entities/__init__.py deleted file mode 100644 index bbf284b..0000000 --- a/src/user_service/domain/user/entities/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .user import User - -__all__ = ("User",) diff --git a/src/user_service/domain/user/events/full_name_updated.py b/src/user_service/domain/user/events/full_name_updated.py index d9341bb..00ddc02 100644 --- a/src/user_service/domain/user/events/full_name_updated.py +++ b/src/user_service/domain/user/events/full_name_updated.py @@ -1,7 +1,7 @@ import dataclasses from uuid import UUID -from user_service.domain.common.events.event import Event +from user_service.domain.common.event import Event @dataclasses.dataclass(frozen=True) diff --git a/src/user_service/domain/user/events/user_created.py b/src/user_service/domain/user/events/user_created.py index e9d562b..70c4b8c 100644 --- a/src/user_service/domain/user/events/user_created.py +++ b/src/user_service/domain/user/events/user_created.py @@ -1,7 +1,7 @@ import dataclasses from uuid import UUID -from user_service.domain.common.events.event import Event +from user_service.domain.common.event import Event @dataclasses.dataclass(frozen=True) diff --git a/src/user_service/domain/user/events/user_deleted.py b/src/user_service/domain/user/events/user_deleted.py index 9a2a948..8c0db5c 100644 --- a/src/user_service/domain/user/events/user_deleted.py +++ b/src/user_service/domain/user/events/user_deleted.py @@ -1,7 +1,7 @@ import dataclasses from uuid import UUID -from user_service.domain.common.events.event import Event +from user_service.domain.common.event import Event @dataclasses.dataclass(frozen=True) diff --git a/src/user_service/domain/user/events/username_updated.py b/src/user_service/domain/user/events/username_updated.py index 7928629..947e90e 100644 --- a/src/user_service/domain/user/events/username_updated.py +++ b/src/user_service/domain/user/events/username_updated.py @@ -1,7 +1,7 @@ import dataclasses from uuid import UUID -from user_service.domain.common.events.event import Event +from user_service.domain.common.event import Event @dataclasses.dataclass(frozen=True) diff --git a/src/user_service/domain/user/interfaces/__init__.py b/src/user_service/domain/user/interfaces/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/user_service/application/user/interfaces/persistence/repo.py b/src/user_service/domain/user/interfaces/repo.py similarity index 71% rename from src/user_service/application/user/interfaces/persistence/repo.py rename to src/user_service/domain/user/interfaces/repo.py index 57e4a5f..ff649b0 100644 --- a/src/user_service/application/user/interfaces/persistence/repo.py +++ b/src/user_service/domain/user/interfaces/repo.py @@ -7,7 +7,7 @@ class UserRepo(Protocol): @abc.abstractmethod - async def acquire_user_by_id(self, user_id: UserId) -> entities.User: + async def acquire_user_by_id(self, user_id: UserId) -> entities.User | None: raise NotImplementedError @abc.abstractmethod @@ -15,9 +15,5 @@ async def add_user(self, user: entities.User) -> None: raise NotImplementedError @abc.abstractmethod - async def update_user(self, user: entities.User) -> None: - raise NotImplementedError - - @abc.abstractmethod - async def get_existing_usernames(self) -> set[Username]: + async def check_username_exists(self, username: Username) -> bool: raise NotImplementedError diff --git a/src/user_service/domain/user/service.py b/src/user_service/domain/user/service.py new file mode 100644 index 0000000..2a5ae6a --- /dev/null +++ b/src/user_service/domain/user/service.py @@ -0,0 +1,86 @@ +from user_service.application.user.exceptions import UserIdNotExistError +from user_service.domain.common.service import BaseService +from user_service.domain.user import entities +from user_service.domain.user.events import FullNameUpdated, UserCreated, UserDeleted, UsernameUpdated +from user_service.domain.user.exceptions import UserIsDeletedError, UsernameAlreadyExistsError +from user_service.domain.user.interfaces.repo import UserRepo +from user_service.domain.user.value_objects import FullName, UserId, Username +from user_service.domain.user.value_objects.deletion_time import DeletionTime + + +class UserService(BaseService): + def __init__(self, user_repo: UserRepo) -> None: + super().__init__() + self._user_repo = user_repo + + async def create_user( + self, + user_id: UserId, + username: Username, + full_name: FullName, + ) -> entities.User: + username_exists = await self._user_repo.check_username_exists(username) + if username_exists: + raise UsernameAlreadyExistsError(username.to_raw()) + + user = entities.User(user_id, username, full_name) + await self._user_repo.add_user(user) + self._record_event( + UserCreated( + user_id.to_raw(), + username.to_raw(), + full_name.first_name, + full_name.last_name, + full_name.middle_name, + ), + ) + return user + + async def set_user_username(self, user_id: UserId, username: Username) -> None: + user = await self._user_repo.acquire_user_by_id(user_id) + if user is None: + raise UserIdNotExistError(user_id.to_raw()) + self._validate_user_not_deleted(user) + + if username != user.username: + await self._ensure_username_not_exist(username) + user.username = username + + self._record_event(UsernameUpdated(user.id.to_raw(), user.username.to_raw())) + + async def set_user_full_name(self, user_id: UserId, full_name: FullName) -> None: + user = await self._user_repo.acquire_user_by_id(user_id) + if user is None: + raise UserIdNotExistError(user_id.to_raw()) + self._validate_user_not_deleted(user) + + user.full_name = full_name + + self._record_event( + FullNameUpdated( + user.id.to_raw(), + user.full_name.first_name, + user.full_name.last_name, + user.full_name.middle_name, + ), + ) + + async def delete_user(self, user_id: UserId) -> None: + user = await self._user_repo.acquire_user_by_id(user_id) + if user is None: + raise UserIdNotExistError(user_id.to_raw()) + self._validate_user_not_deleted(user) + + user.username = Username(None) + user.deleted_at = DeletionTime.create_deleted() + + self._record_event(UserDeleted(user.id.to_raw())) + + async def _ensure_username_not_exist(self, username: Username) -> None: + username_exists = await self._user_repo.check_username_exists(username) + if username_exists: + raise UsernameAlreadyExistsError(username.to_raw()) + + def _validate_user_not_deleted(self, user: entities.User) -> None: + if user.deleted_at.is_deleted(): + raise UserIsDeletedError(user.id.to_raw()) diff --git a/src/user_service/domain/user/value_objects/__init__.py b/src/user_service/domain/user/value_objects/__init__.py index 0715720..11853f2 100644 --- a/src/user_service/domain/user/value_objects/__init__.py +++ b/src/user_service/domain/user/value_objects/__init__.py @@ -1,8 +1,10 @@ +from .deletion_time import DeletionTime from .full_name import FullName from .user_id import UserId from .username import Username __all__ = ( + "DeletionTime", "UserId", "Username", "FullName", diff --git a/src/user_service/domain/user/value_objects/deleted_status.py b/src/user_service/domain/user/value_objects/deletion_time.py similarity index 100% rename from src/user_service/domain/user/value_objects/deleted_status.py rename to src/user_service/domain/user/value_objects/deletion_time.py diff --git a/src/user_service/domain/user/value_objects/full_name.py b/src/user_service/domain/user/value_objects/full_name.py index 7e2eed7..51eca33 100644 --- a/src/user_service/domain/user/value_objects/full_name.py +++ b/src/user_service/domain/user/value_objects/full_name.py @@ -2,7 +2,7 @@ from dataclasses import dataclass from user_service.domain.common.exceptions import DomainError -from user_service.domain.common.value_objects.base import BaseValueObject +from user_service.domain.common.value_objects import BaseValueObject MAX_NAME_LENGTH = 32 NAME_PATTERN = re.compile(r"[A-Za-z]+") diff --git a/src/user_service/domain/user/value_objects/user_id.py b/src/user_service/domain/user/value_objects/user_id.py index 09de87a..8473bfd 100644 --- a/src/user_service/domain/user/value_objects/user_id.py +++ b/src/user_service/domain/user/value_objects/user_id.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from uuid import UUID -from user_service.domain.common.value_objects.base import ValueObject +from user_service.domain.common.value_objects import ValueObject @dataclass(frozen=True) diff --git a/src/user_service/domain/user/value_objects/username.py b/src/user_service/domain/user/value_objects/username.py index 9f2d0f0..2c74e3e 100644 --- a/src/user_service/domain/user/value_objects/username.py +++ b/src/user_service/domain/user/value_objects/username.py @@ -2,7 +2,7 @@ from dataclasses import dataclass from user_service.domain.common.exceptions import DomainError -from user_service.domain.common.value_objects.base import ValueObject +from user_service.domain.common.value_objects import ValueObject MAX_USERNAME_LENGTH = 32 USERNAME_PATTERN = re.compile(r"[A-Za-z][A-Za-z1-9_]+") diff --git a/src/user_service/infrastructure/db/converters/__init__.py b/src/user_service/infrastructure/db/converters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/user_service/infrastructure/db/migrations/versions/20231018-144621_36112198c668_add_deleted_at_column_to_the_users_table.py b/src/user_service/infrastructure/db/migrations/versions/20231018-144621_36112198c668_add_deleted_at_column_to_the_users_table.py index 91c148a..fbf77ff 100644 --- a/src/user_service/infrastructure/db/migrations/versions/20231018-144621_36112198c668_add_deleted_at_column_to_the_users_table.py +++ b/src/user_service/infrastructure/db/migrations/versions/20231018-144621_36112198c668_add_deleted_at_column_to_the_users_table.py @@ -17,7 +17,9 @@ def upgrade() -> None: - op.add_column("users", sa.Column("deleted_at", sa.DateTime(), server_default=sa.text("NULL"), nullable=True)) + op.add_column( + "users", sa.Column("deleted_at", sa.DateTime(timezone=True), server_default=sa.text("NULL"), nullable=True) + ) # set deleted_at to the updated_at value for all existing users op.execute("UPDATE users SET deleted_at = updated_at WHERE deleted IS true") op.drop_column("users", "deleted") diff --git a/src/user_service/infrastructure/db/models/__init__.py b/src/user_service/infrastructure/db/models/__init__.py index f219897..9770e31 100644 --- a/src/user_service/infrastructure/db/models/__init__.py +++ b/src/user_service/infrastructure/db/models/__init__.py @@ -1,7 +1,3 @@ from .base import BaseModel -from .user import User -__all__ = ( - "BaseModel", - "User", -) +__all__ = ("BaseModel",) diff --git a/src/user_service/infrastructure/db/models/user.py b/src/user_service/infrastructure/db/models/user.py deleted file mode 100644 index fe9e98f..0000000 --- a/src/user_service/infrastructure/db/models/user.py +++ /dev/null @@ -1,21 +0,0 @@ -from datetime import datetime -from typing import Any, ClassVar -from uuid import UUID - -import sqlalchemy as sa -from sqlalchemy.orm import Mapped, mapped_column -from uuid6 import uuid7 - -from .base import TimedBaseModel - - -class User(TimedBaseModel): - __tablename__ = "users" - __mapper_args__: ClassVar[dict[Any, Any]] = {"eager_defaults": True} - - id: Mapped[UUID] = mapped_column(primary_key=True, default=uuid7, server_default=sa.func.uuid_generate_v7()) - username: Mapped[str | None] = mapped_column(unique=True) - first_name: Mapped[str] - last_name: Mapped[str] - middle_name: Mapped[str | None] - deleted_at: Mapped[datetime | None] = mapped_column(default=None, server_default=sa.Null()) diff --git a/src/user_service/infrastructure/db/readers/__init__.py b/src/user_service/infrastructure/db/readers/__init__.py new file mode 100644 index 0000000..b03e204 --- /dev/null +++ b/src/user_service/infrastructure/db/readers/__init__.py @@ -0,0 +1,3 @@ +from .user import UserReaderImpl + +__all__ = ("UserReaderImpl",) diff --git a/src/user_service/infrastructure/db/readers/base.py b/src/user_service/infrastructure/db/readers/base.py new file mode 100644 index 0000000..538f953 --- /dev/null +++ b/src/user_service/infrastructure/db/readers/base.py @@ -0,0 +1,6 @@ +from sqlalchemy.ext.asyncio import AsyncSession + + +class SQLAlchemyReader: + def __init__(self, session: AsyncSession) -> None: + self._session = session diff --git a/src/user_service/infrastructure/db/readers/user.py b/src/user_service/infrastructure/db/readers/user.py new file mode 100644 index 0000000..5c248b4 --- /dev/null +++ b/src/user_service/infrastructure/db/readers/user.py @@ -0,0 +1,71 @@ +from collections.abc import Iterable +from typing import Any +from uuid import UUID + +from sqlalchemy import func, select + +from user_service.application.common.pagination.dto import Pagination, PaginationResult, SortOrder +from user_service.application.user import dto +from user_service.application.user.interfaces.persistence import GetUsersFilters, UserReader +from user_service.domain.common.constants import Empty +from user_service.infrastructure.db.converters.user import convert_db_row_to_user_dto +from user_service.infrastructure.db.exception_mapper import exception_mapper +from user_service.infrastructure.db.models.user import USERS_TABLE +from user_service.infrastructure.db.readers.base import SQLAlchemyReader + + +class UserReaderImpl(SQLAlchemyReader, UserReader): + @exception_mapper + async def get_user_by_id(self, user_id: UUID) -> dto.User | None: + result = await self._session.execute(select(USERS_TABLE).where(USERS_TABLE.c.id == user_id)) + user_row = result.mappings().one_or_none() + if user_row is None: + return None + + return convert_db_row_to_user_dto(user_row) + + @exception_mapper + async def get_user_by_username(self, username: str) -> dto.User | None: + result = await self._session.execute(select(USERS_TABLE).where(USERS_TABLE.c.username == username)) + user_row = result.mappings().one_or_none() + if user_row is None: + return None + + return convert_db_row_to_user_dto(user_row) + + @exception_mapper + async def get_users(self, filters: GetUsersFilters, pagination: Pagination) -> dto.Users: + query = select(USERS_TABLE) + + if pagination.order is SortOrder.ASC: + query = query.order_by(USERS_TABLE.c.id.desc()) + else: + query = query.order_by(USERS_TABLE.c.id.asc()) + + if filters.deleted is not Empty.UNSET: + if filters.deleted: + query = query.where(USERS_TABLE.c.deleted_at.is_not(None)) + else: + query = query.where(USERS_TABLE.c.deleted_at.is_(None)) + + if pagination.offset is not Empty.UNSET: + query = query.offset(pagination.offset) + if pagination.limit is not Empty.UNSET: + query = query.limit(pagination.limit) + + users_rows: Iterable[Any] = await self._session.execute(query) + users = [convert_db_row_to_user_dto(user_row) for user_row in users_rows] + users_count = await self._get_users_count(filters) + return dto.Users(data=users, pagination=PaginationResult.from_pagination(pagination, total=users_count)) + + async def _get_users_count(self, filters: GetUsersFilters) -> int: + query = select(func.count(USERS_TABLE.c.id)) + + if filters.deleted is not Empty.UNSET: + if filters.deleted: + query = query.where(USERS_TABLE.c.deleted_at.is_not(None)) + else: + query = query.where(USERS_TABLE.c.deleted_at.is_(None)) + + users_count: int = await self._session.scalar(query) + return users_count diff --git a/src/user_service/infrastructure/db/repositories/__init__.py b/src/user_service/infrastructure/db/repositories/__init__.py index 177cbce..dcd6bf7 100644 --- a/src/user_service/infrastructure/db/repositories/__init__.py +++ b/src/user_service/infrastructure/db/repositories/__init__.py @@ -1,6 +1,3 @@ -from .user import UserReaderImpl, UserRepoImpl +from .user import UserRepoImpl -__all__ = ( - "UserReaderImpl", - "UserRepoImpl", -) +__all__ = ("UserRepoImpl",) diff --git a/src/user_service/infrastructure/db/repositories/user.py b/src/user_service/infrastructure/db/repositories/user.py index 4dda3f0..3fed634 100644 --- a/src/user_service/infrastructure/db/repositories/user.py +++ b/src/user_service/infrastructure/db/repositories/user.py @@ -1,121 +1,39 @@ -from collections.abc import Iterable from typing import NoReturn -from uuid import UUID -from sqlalchemy import func, select +from sqlalchemy import exists, select from sqlalchemy.exc import DBAPIError, IntegrityError from user_service.application.common.exceptions import RepoError -from user_service.application.common.pagination.dto import Pagination, PaginationResult, SortOrder -from user_service.application.user import dto -from user_service.application.user.exceptions import ( - UserIdAlreadyExistsError, - UserIdNotExistError, - UsernameNotExistError, -) -from user_service.application.user.interfaces.persistence import GetUsersFilters, UserReader, UserRepo -from user_service.domain.common.constants import Empty +from user_service.application.user.exceptions import UserIdAlreadyExistsError from user_service.domain.user import entities from user_service.domain.user.exceptions import UsernameAlreadyExistsError +from user_service.domain.user.interfaces.repo import UserRepo from user_service.domain.user.value_objects import UserId, Username -from user_service.infrastructure.db.converters import ( - convert_db_model_to_active_user_dto, - convert_db_model_to_user_dto, - convert_db_model_to_user_entity, - convert_user_entity_to_db_model, -) from user_service.infrastructure.db.exception_mapper import exception_mapper -from user_service.infrastructure.db.models.user import User +from user_service.infrastructure.db.models.user import USERS_TABLE from user_service.infrastructure.db.repositories.base import SQLAlchemyRepo -class UserReaderImpl(SQLAlchemyRepo, UserReader): - @exception_mapper - async def get_user_by_id(self, user_id: UUID) -> dto.UserDTOs: - user: User | None = await self._session.get(User, user_id) - if user is None: - raise UserIdNotExistError(user_id) - - return convert_db_model_to_user_dto(user) - - @exception_mapper - async def get_user_by_username(self, username: str) -> dto.User: - user: User | None = await self._session.scalar(select(User).where(User.username == username)) - if user is None: - raise UsernameNotExistError(username) - - return convert_db_model_to_active_user_dto(user) - - @exception_mapper - async def get_users(self, filters: GetUsersFilters, pagination: Pagination) -> dto.Users: - query = select(User) - - if pagination.order is SortOrder.ASC: - query = query.order_by(User.id.desc()) - else: - query = query.order_by(User.id.asc()) - - if filters.deleted is not Empty.UNSET: - if filters.deleted: - query = query.where(User.deleted_at.is_not(None)) - else: - query = query.where(User.deleted_at.is_(None)) - - if pagination.offset is not Empty.UNSET: - query = query.offset(pagination.offset) - if pagination.limit is not Empty.UNSET: - query = query.limit(pagination.limit) - - result: Iterable[User] = await self._session.scalars(query) - users = [convert_db_model_to_user_dto(user) for user in result] - users_count = await self._get_users_count(filters) - return dto.Users(data=users, pagination=PaginationResult.from_pagination(pagination, total=users_count)) - - async def _get_users_count(self, filters: GetUsersFilters) -> int: - query = select(func.count(User.id)) - - if filters.deleted is not Empty.UNSET: - if filters.deleted: - query = query.where(User.deleted_at.is_not(None)) - else: - query = query.where(User.deleted_at.is_(None)) - - users_count: int = await self._session.scalar(query) - return users_count - - class UserRepoImpl(SQLAlchemyRepo, UserRepo): @exception_mapper - async def acquire_user_by_id(self, user_id: UserId) -> entities.User: - user: User | None = await self._session.get(User, user_id.to_raw(), with_for_update=True) - if user is None: - raise UserIdNotExistError(user_id.to_raw()) - - existing_usernames = await self.get_existing_usernames() - return convert_db_model_to_user_entity(user, existing_usernames) + async def acquire_user_by_id(self, user_id: UserId) -> entities.User | None: + user: entities.User | None = await self._session.get(entities.User, user_id.to_raw(), with_for_update=True) + return user @exception_mapper async def add_user(self, user: entities.User) -> None: - db_user = convert_user_entity_to_db_model(user) - self._session.add(db_user) - try: - await self._session.flush((db_user,)) - except IntegrityError as err: - self._parse_error(err, user) - - @exception_mapper - async def update_user(self, user: entities.User) -> None: - db_user = convert_user_entity_to_db_model(user) + self._session.add(user) try: - await self._session.merge(db_user) + await self._session.flush((user,)) except IntegrityError as err: self._parse_error(err, user) @exception_mapper - async def get_existing_usernames(self) -> set[Username]: - result: Iterable[str] = await self._session.scalars(select(User.username).where(User.username.is_not(None))) - existing_usernames = {Username(username) for username in result} - return existing_usernames + async def check_username_exists(self, username: Username) -> bool: + result: bool | None = await self._session.scalar( + select(exists().where(USERS_TABLE.c.username == username.to_raw())) + ) + return result or False def _parse_error(self, err: DBAPIError, user: entities.User) -> NoReturn: match err.__cause__.__cause__.constraint_name: # type: ignore diff --git a/src/user_service/infrastructure/di/main.py b/src/user_service/infrastructure/di/main.py index 0dccdb1..19875c3 100644 --- a/src/user_service/infrastructure/di/main.py +++ b/src/user_service/infrastructure/di/main.py @@ -10,9 +10,11 @@ from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker from user_service.application.common.interfaces.uow import UnitOfWork -from user_service.application.user.interfaces.persistence import UserReader, UserRepo +from user_service.application.user.interfaces.persistence import UserReader +from user_service.domain.user.interfaces.repo import UserRepo from user_service.infrastructure.db.main import build_sa_engine, build_sa_session, build_sa_session_factory -from user_service.infrastructure.db.repositories.user import UserReaderImpl, UserRepoImpl +from user_service.infrastructure.db.readers import UserReaderImpl +from user_service.infrastructure.db.repositories.user import UserRepoImpl from user_service.infrastructure.di import DiScope from user_service.infrastructure.event_bus.event_bus import EventBusImpl from user_service.infrastructure.mediator import get_mediator diff --git a/src/user_service/infrastructure/event_bus/event_handler.py b/src/user_service/infrastructure/event_bus/event_handler.py index 2d0ebdf..1242057 100644 --- a/src/user_service/infrastructure/event_bus/event_handler.py +++ b/src/user_service/infrastructure/event_bus/event_handler.py @@ -1,6 +1,6 @@ from user_service.application.common.event import EventHandler from user_service.application.common.exceptions import MappingError -from user_service.domain.common.events import Event +from user_service.domain.common.event import Event from user_service.infrastructure.event_bus.converters import convert_domain_event_to_integration from user_service.infrastructure.event_bus.event_bus import EventBusImpl diff --git a/src/user_service/infrastructure/log/event_handler.py b/src/user_service/infrastructure/log/event_handler.py index 94f09c9..bd385d6 100644 --- a/src/user_service/infrastructure/log/event_handler.py +++ b/src/user_service/infrastructure/log/event_handler.py @@ -1,7 +1,7 @@ import logging from user_service.application.common.event import EventHandler -from user_service.domain.common.events import Event +from user_service.domain.common.event import Event logger = logging.getLogger(__name__) diff --git a/src/user_service/infrastructure/mediator/main.py b/src/user_service/infrastructure/mediator/main.py index 2e0c964..c03440c 100644 --- a/src/user_service/infrastructure/mediator/main.py +++ b/src/user_service/infrastructure/mediator/main.py @@ -18,7 +18,7 @@ from user_service.application.user.queries.get_user_by_id import GetUserById, GetUserByIdHandler from user_service.application.user.queries.get_user_by_username import GetUserByUsername, GetUserByUsernameHandler from user_service.application.user.queries.get_users import GetUsers, GetUsersHandler -from user_service.domain.common.events import Event +from user_service.domain.common.event import Event from user_service.infrastructure.di import DiScope from user_service.infrastructure.event_bus.event_handler import EventHandlerPublisher from user_service.infrastructure.log.event_handler import EventLogger diff --git a/src/user_service/presentation/api/controllers/user.py b/src/user_service/presentation/api/controllers/user.py index 8a0f668..fc86039 100644 --- a/src/user_service/presentation/api/controllers/user.py +++ b/src/user_service/presentation/api/controllers/user.py @@ -37,7 +37,7 @@ @user_router.post( "", responses={ - status.HTTP_201_CREATED: {"model": dto.User}, + status.HTTP_201_CREATED: {"model": OkResponse[None]}, status.HTTP_400_BAD_REQUEST: { "model": ErrorResponse[TooLongUsernameError | EmptyUsernameError | WrongUsernameFormatError], }, @@ -50,10 +50,9 @@ async def create_user( create_user_command: CreateUser, mediator: Annotated[Mediator, Depends(Stub(Mediator))], -) -> OkResponse[dto.UserDTOs]: - user_id = await mediator.send(create_user_command) - user = await mediator.query(GetUserById(user_id=user_id)) - return OkResponse(result=user) +) -> OkResponse[dto.User]: + await mediator.send(create_user_command) + return OkResponse() @user_router.get( @@ -74,14 +73,14 @@ async def get_user_by_username( @user_router.get( "/{user_id}", responses={ - status.HTTP_200_OK: {"model": dto.UserDTOs}, + status.HTTP_200_OK: {"model": dto.User}, status.HTTP_404_NOT_FOUND: {"model": ErrorResponse[UserIdNotExistError]}, }, ) async def get_user_by_id( user_id: UUID, mediator: Annotated[QueryMediator, Depends(Stub(QueryMediator))], -) -> OkResponse[dto.UserDTOs]: +) -> OkResponse[dto.User]: user = await mediator.query(GetUserById(user_id=user_id)) return OkResponse(result=user) @@ -111,7 +110,7 @@ async def get_users( @user_router.put( "/{user_id}/username", responses={ - status.HTTP_200_OK: {"model": dto.User}, + status.HTTP_200_OK: {"model": OkResponse[None]}, status.HTTP_400_BAD_REQUEST: { "model": ErrorResponse[ UserIdNotExistError | TooLongUsernameError | EmptyUsernameError | WrongUsernameFormatError @@ -133,7 +132,7 @@ async def set_user_username( @user_router.put( "/{user_id}/full-name", responses={ - status.HTTP_200_OK: {"model": dto.User}, + status.HTTP_200_OK: {"model": OkResponse[None]}, status.HTTP_400_BAD_REQUEST: { "model": ErrorResponse[UserIdNotExistError | EmptyNameError | WrongNameFormatError | TooLongNameError], }, @@ -158,7 +157,7 @@ async def set_user_full_name( @user_router.delete( "/{user_id}", responses={ - status.HTTP_200_OK: {"model": dto.DeletedUser}, + status.HTTP_200_OK: {"model": OkResponse[None]}, status.HTTP_404_NOT_FOUND: {"model": ErrorResponse[UserIdNotExistError]}, status.HTTP_409_CONFLICT: {"model": ErrorResponse[UserIsDeletedError]}, }, diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 99cd462..c27c32f 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -15,7 +15,7 @@ @pytest.fixture(scope="session") def postgres_url() -> Generator[str, None, None]: - postgres = PostgresContainer("postgres:15-alpine") + postgres = PostgresContainer("postgres:17-alpine") if os.name == "nt": # TODO: workaround from testcontainers/testcontainers-python#108 postgres.get_container_host_ip = lambda: "localhost" try: diff --git a/tests/mocks/user_reader.py b/tests/mocks/user_reader.py index 5d0862a..56265f1 100644 --- a/tests/mocks/user_reader.py +++ b/tests/mocks/user_reader.py @@ -3,7 +3,6 @@ from user_service.application.common.pagination.dto import Pagination, PaginationResult, SortOrder from user_service.application.user import dto -from user_service.application.user.exceptions import UserIdNotExistError, UsernameNotExistError from user_service.application.user.interfaces import UserReader from user_service.application.user.interfaces.persistence import GetUsersFilters from user_service.domain.common.constants import Empty @@ -11,20 +10,17 @@ class UserReaderMock(UserReader): def __init__(self) -> None: - self.users: dict[UUID, dto.UserDTOs] = {} + self.users: dict[UUID, dto.User] = {} - async def get_user_by_id(self, user_id: UUID) -> dto.UserDTOs: - if user_id not in self.users: - raise UserIdNotExistError(user_id) - - user = self.users[user_id] + async def get_user_by_id(self, user_id: UUID) -> dto.User | None: + user = self.users.get(user_id) return user - async def get_user_by_username(self, username: str) -> dto.User: + async def get_user_by_username(self, username: str) -> dto.User | None: for user in self.users.values(): - if isinstance(user, dto.User) and user.username == username: + if user.username == username: return user - raise UsernameNotExistError(username) + return None async def get_users(self, filters: GetUsersFilters, pagination: Pagination) -> dto.Users: users = list(self.users.values()) @@ -55,9 +51,9 @@ async def _get_users_count(self, filters: GetUsersFilters) -> int: count = len(self.users) return count - async def add_user(self, user: dto.UserDTOs) -> None: + async def add_user(self, user: dto.User) -> None: self.users[user.id] = user - async def add_users(self, users: Sequence[dto.UserDTOs]) -> None: + async def add_users(self, users: Sequence[dto.User]) -> None: for user in users: await self.add_user(user) diff --git a/tests/mocks/user_repo.py b/tests/mocks/user_repo.py index 288def9..8b9ae26 100644 --- a/tests/mocks/user_repo.py +++ b/tests/mocks/user_repo.py @@ -1,6 +1,5 @@ -from user_service.application.user.exceptions import UserIdNotExistError -from user_service.application.user.interfaces import UserRepo from user_service.domain.user import entities +from user_service.domain.user.interfaces.repo import UserRepo from user_service.domain.user.value_objects import UserId, Username @@ -8,17 +7,12 @@ class UserRepoMock(UserRepo): def __init__(self) -> None: self.users: dict[UserId, entities.User] = {} - async def acquire_user_by_id(self, user_id: UserId) -> entities.User: - if user_id not in self.users: - raise UserIdNotExistError(user_id.to_raw()) - return self.users[user_id] + async def acquire_user_by_id(self, user_id: UserId) -> entities.User | None: + return self.users.get(user_id) async def add_user(self, user: entities.User) -> None: self.users[user.id] = user - async def update_user(self, user: entities.User) -> None: - self.users[user.id] = user - - async def get_existing_usernames(self) -> set[Username]: - usernames = {user.username for user in self.users.values()} - return usernames + async def check_username_exists(self, username: Username) -> bool: + username_exists = next((True for user in self.users.values() if user.username == username), False) + return username_exists diff --git a/tests/unit/application/commands/test_create_user.py b/tests/unit/application/commands/test_create_user.py index f7f380c..88a52ab 100644 --- a/tests/unit/application/commands/test_create_user.py +++ b/tests/unit/application/commands/test_create_user.py @@ -5,8 +5,9 @@ from user_service.domain.user.entities import User from user_service.domain.user.events import UserCreated from user_service.domain.user.exceptions import UsernameAlreadyExistsError +from user_service.domain.user.service import UserService from user_service.domain.user.value_objects import FullName, UserId, Username -from user_service.domain.user.value_objects.deleted_status import DeletionTime +from user_service.domain.user.value_objects.deletion_time import DeletionTime from tests.mocks import EventMediatorMock, UserRepoMock from tests.mocks.uow import UnitOfWorkMock @@ -14,10 +15,11 @@ async def test_create_user_handler_success( user_repo: UserRepoMock, + user_service: UserService, uow: UnitOfWorkMock, event_mediator: EventMediatorMock, ) -> None: - handler = CreateUserHandler(user_repo, uow, event_mediator) + handler = CreateUserHandler(user_service, uow, event_mediator) user_id = UUID("123e4567-e89b-12d3-a456-426614174000") command = CreateUser( @@ -53,10 +55,11 @@ async def test_create_user_handler_success( async def test_create_user_handler_existing_username( user_repo: UserRepoMock, + user_service: UserService, uow: UnitOfWorkMock, event_mediator: EventMediatorMock, ) -> None: - handler = CreateUserHandler(user_repo, uow, event_mediator) + handler = CreateUserHandler(user_service, uow, event_mediator) user_id = UUID("123e4567-e89b-12d3-a456-426614174000") existing_user = User( diff --git a/tests/unit/application/commands/test_delete_user.py b/tests/unit/application/commands/test_delete_user.py index a795d05..da0ac7e 100644 --- a/tests/unit/application/commands/test_delete_user.py +++ b/tests/unit/application/commands/test_delete_user.py @@ -6,8 +6,9 @@ from user_service.domain.user import User from user_service.domain.user.events import UserDeleted from user_service.domain.user.exceptions import UserIsDeletedError +from user_service.domain.user.service import UserService from user_service.domain.user.value_objects import FullName, UserId, Username -from user_service.domain.user.value_objects.deleted_status import DeletionTime +from user_service.domain.user.value_objects.deletion_time import DeletionTime from tests.mocks import EventMediatorMock, UserRepoMock from tests.mocks.uow import UnitOfWorkMock @@ -15,10 +16,11 @@ async def test_delete_user_handler_success( user_repo: UserRepoMock, + user_service: UserService, uow: UnitOfWorkMock, event_mediator: EventMediatorMock, ) -> None: - handler = DeleteUserHandler(user_repo, uow, event_mediator) + handler = DeleteUserHandler(user_service, uow, event_mediator) user_id = UserId(UUID("123e4567-e89b-12d3-a456-426614174000")) username = Username("john_doe") @@ -27,8 +29,7 @@ async def test_delete_user_handler_success( id=user_id, username=username, full_name=full_name, - deleted_at=DeletionTime(None), - existing_usernames={username}, + deleted_at=DeletionTime.create_not_deleted(), ) await user_repo.add_user(user) @@ -38,7 +39,7 @@ async def test_delete_user_handler_success( assert user.id == user_id assert user.username == Username(None) assert user.full_name == full_name - assert user.deleted_at != DeletionTime(None) + assert user.deleted_at != DeletionTime.create_not_deleted() assert len(event_mediator.published_events) == 1 published_event = event_mediator.published_events[0] @@ -50,10 +51,11 @@ async def test_delete_user_handler_success( async def test_delete_user_handler_user_not_found( user_repo: UserRepoMock, + user_service: UserService, uow: UnitOfWorkMock, event_mediator: EventMediatorMock, ) -> None: - handler = DeleteUserHandler(user_repo, uow, event_mediator) + handler = DeleteUserHandler(user_service, uow, event_mediator) user_id = UUID("123e4567-e89b-12d3-a456-426614174000") @@ -69,19 +71,19 @@ async def test_delete_user_handler_user_not_found( async def test_delete_user_handler_user_already_deleted( user_repo: UserRepoMock, + user_service: UserService, uow: UnitOfWorkMock, event_mediator: EventMediatorMock, ) -> None: - handler = DeleteUserHandler(user_repo, uow, event_mediator) + handler = DeleteUserHandler(user_service, uow, event_mediator) user_id = UUID("123e4567-e89b-12d3-a456-426614174000") user = User( id=UserId(user_id), - username=Username("john_doe"), + username=Username(None), full_name=FullName("John", "Doe"), - existing_usernames={Username("john_doe")}, + deleted_at=DeletionTime.create_deleted(), ) - user.delete() await user_repo.add_user(user) command = DeleteUser(user_id=user_id) diff --git a/tests/unit/application/commands/test_set_user_full_name.py b/tests/unit/application/commands/test_set_user_full_name.py index 8e0ac82..018e9bd 100644 --- a/tests/unit/application/commands/test_set_user_full_name.py +++ b/tests/unit/application/commands/test_set_user_full_name.py @@ -6,7 +6,8 @@ from user_service.domain.user import User from user_service.domain.user.events import FullNameUpdated from user_service.domain.user.exceptions import UserIsDeletedError -from user_service.domain.user.value_objects import FullName, UserId, Username +from user_service.domain.user.service import UserService +from user_service.domain.user.value_objects import DeletionTime, FullName, UserId, Username from tests.mocks import EventMediatorMock, UserRepoMock from tests.mocks.uow import UnitOfWorkMock @@ -14,10 +15,11 @@ async def test_set_user_full_name_handler_success( user_repo: UserRepoMock, + user_service: UserService, uow: UnitOfWorkMock, event_mediator: EventMediatorMock, ) -> None: - handler = SetUserFullNameHandler(user_repo, uow, event_mediator) + handler = SetUserFullNameHandler(user_service, uow, event_mediator) user_id = UserId(UUID("123e4567-e89b-12d3-a456-426614174000")) username = Username("john_doe") @@ -54,10 +56,11 @@ async def test_set_user_full_name_handler_success( async def test_set_user_full_name_handler_user_not_found( user_repo: UserRepoMock, + user_service: UserService, uow: UnitOfWorkMock, event_mediator: EventMediatorMock, ) -> None: - handler = SetUserFullNameHandler(user_repo, uow, event_mediator) + handler = SetUserFullNameHandler(user_service, uow, event_mediator) user_id = UUID("123e4567-e89b-12d3-a456-426614174000") @@ -78,20 +81,19 @@ async def test_set_user_full_name_handler_user_not_found( async def test_set_user_full_name_handler_user_deleted( user_repo: UserRepoMock, + user_service: UserService, uow: UnitOfWorkMock, event_mediator: EventMediatorMock, ) -> None: - handler = SetUserFullNameHandler(user_repo, uow, event_mediator) + handler = SetUserFullNameHandler(user_service, uow, event_mediator) user_id = UUID("123e4567-e89b-12d3-a456-426614174000") - username = Username("john_doe") user = User( id=UserId(user_id), - username=username, + username=Username(None), full_name=FullName("John", "Doe"), - existing_usernames={username}, + deleted_at=DeletionTime.create_deleted(), ) - user.delete() await user_repo.add_user(user) command = SetUserFullName( diff --git a/tests/unit/application/commands/test_set_user_username.py b/tests/unit/application/commands/test_set_user_username.py index 101abe3..54f9955 100644 --- a/tests/unit/application/commands/test_set_user_username.py +++ b/tests/unit/application/commands/test_set_user_username.py @@ -6,7 +6,8 @@ from user_service.domain.user import User from user_service.domain.user.events import UsernameUpdated from user_service.domain.user.exceptions import UserIsDeletedError, UsernameAlreadyExistsError -from user_service.domain.user.value_objects import FullName, UserId, Username +from user_service.domain.user.service import UserService +from user_service.domain.user.value_objects import DeletionTime, FullName, UserId, Username from tests.mocks import EventMediatorMock, UserRepoMock from tests.mocks.uow import UnitOfWorkMock @@ -14,10 +15,11 @@ async def test_set_user_username_handler_success( user_repo: UserRepoMock, + user_service: UserService, uow: UnitOfWorkMock, event_mediator: EventMediatorMock, ) -> None: - handler = SetUserUsernameHandler(user_repo, uow, event_mediator) + handler = SetUserUsernameHandler(user_service, uow, event_mediator) user_id = UserId(UUID("123e4567-e89b-12d3-a456-426614174000")) old_username = Username("old_username") @@ -27,7 +29,6 @@ async def test_set_user_username_handler_success( id=user_id, username=old_username, full_name=full_name, - existing_usernames={old_username}, ) await user_repo.add_user(user) @@ -49,10 +50,11 @@ async def test_set_user_username_handler_success( async def test_set_user_username_handler_username_exists( user_repo: UserRepoMock, + user_service: UserService, uow: UnitOfWorkMock, event_mediator: EventMediatorMock, ) -> None: - handler = SetUserUsernameHandler(user_repo, uow, event_mediator) + handler = SetUserUsernameHandler(user_service, uow, event_mediator) user_id = UserId(UUID("123e4567-e89b-12d3-a456-426614174000")) old_username = Username("old_username") @@ -62,7 +64,6 @@ async def test_set_user_username_handler_username_exists( id=user_id, username=old_username, full_name=full_name, - existing_usernames={old_username, Username(new_username)}, ) user2 = User( id=UserId(UUID("123e4567-e89b-12d3-a456-426614174001")), @@ -84,10 +85,11 @@ async def test_set_user_username_handler_username_exists( async def test_set_user_username_handler_user_not_found( user_repo: UserRepoMock, + user_service: UserService, uow: UnitOfWorkMock, event_mediator: EventMediatorMock, ) -> None: - handler = SetUserUsernameHandler(user_repo, uow, event_mediator) + handler = SetUserUsernameHandler(user_service, uow, event_mediator) user_id = UUID("123e4567-e89b-12d3-a456-426614174000") new_username = "john_doe" @@ -104,22 +106,20 @@ async def test_set_user_username_handler_user_not_found( async def test_set_user_username_handler_user_deleted( user_repo: UserRepoMock, + user_service: UserService, uow: UnitOfWorkMock, event_mediator: EventMediatorMock, ) -> None: - handler = SetUserUsernameHandler(user_repo, uow, event_mediator) + handler = SetUserUsernameHandler(user_service, uow, event_mediator) user_id = UserId(UUID("123e4567-e89b-12d3-a456-426614174000")) - old_username = Username("old_username") new_username = "new_username" - full_name = FullName("John", "Doe") user = User( id=user_id, - username=old_username, - full_name=full_name, - existing_usernames={old_username}, + username=Username(None), + full_name=FullName("John", "Doe"), + deleted_at=DeletionTime.create_deleted(), ) - user.delete() await user_repo.add_user(user) command = SetUserUsername(user_id=user_id.value, username=new_username) diff --git a/tests/unit/application/conftest.py b/tests/unit/application/conftest.py index e706003..8db0911 100644 --- a/tests/unit/application/conftest.py +++ b/tests/unit/application/conftest.py @@ -1,4 +1,6 @@ import pytest +from user_service.domain.user.interfaces.repo import UserRepo +from user_service.domain.user.service import UserService from tests.mocks import EventMediatorMock, UserRepoMock from tests.mocks.uow import UnitOfWorkMock @@ -23,3 +25,8 @@ def event_mediator() -> EventMediatorMock: @pytest.fixture() def uow() -> UnitOfWorkMock: return UnitOfWorkMock() + + +@pytest.fixture() +def user_service(user_repo: UserRepo) -> UserService: + return UserService(user_repo) diff --git a/tests/unit/application/queries/test_get_user_by_id.py b/tests/unit/application/queries/test_get_user_by_id.py index 62a8160..c5a58bb 100644 --- a/tests/unit/application/queries/test_get_user_by_id.py +++ b/tests/unit/application/queries/test_get_user_by_id.py @@ -11,11 +11,7 @@ async def test_get_user_by_id_handler(user_reader: UserReaderMock) -> None: user_id = UUID("123e4567-e89b-12d3-a456-426614174000") user = dto.User( - id=user_id, - username="john_doe", - first_name="John", - last_name="Doe", - middle_name=None, + id=user_id, username="john_doe", first_name="John", last_name="Doe", middle_name=None, deleted_at=None ) await user_reader.add_user(user) handler = GetUserByIdHandler(user_reader) @@ -29,11 +25,7 @@ async def test_get_user_by_id_handler(user_reader: UserReaderMock) -> None: async def test_get_user_by_id_handler_user_not_found(user_reader: UserReaderMock) -> None: user_id = UUID("123e4567-e89b-12d3-a456-426614174000") user = dto.User( - id=user_id, - username="john_doe", - first_name="John", - last_name="Doe", - middle_name=None, + id=user_id, username="john_doe", first_name="John", last_name="Doe", middle_name=None, deleted_at=None ) await user_reader.add_user(user) handler = GetUserByIdHandler(user_reader) diff --git a/tests/unit/application/queries/test_get_user_by_username.py b/tests/unit/application/queries/test_get_user_by_username.py index c8f1a80..bea6cf9 100644 --- a/tests/unit/application/queries/test_get_user_by_username.py +++ b/tests/unit/application/queries/test_get_user_by_username.py @@ -16,6 +16,7 @@ async def test_get_user_by_username_handler_success(user_reader: UserReaderMock) first_name="John", last_name="Doe", middle_name=None, + deleted_at=None, ) await user_reader.add_user(user) handler = GetUserByUsernameHandler(user_reader) @@ -34,6 +35,7 @@ async def test_get_user_by_username_handler_user_not_found(user_reader: UserRead first_name="John", last_name="Doe", middle_name=None, + deleted_at=None, ) await user_reader.add_user(user) handler = GetUserByUsernameHandler(user_reader) diff --git a/tests/unit/application/queries/test_get_users.py b/tests/unit/application/queries/test_get_users.py index 0048625..578f03f 100644 --- a/tests/unit/application/queries/test_get_users.py +++ b/tests/unit/application/queries/test_get_users.py @@ -12,17 +12,19 @@ @pytest.fixture() -def users() -> list[dto.UserDTOs]: - users: list[dto.UserDTOs] = [ +def users() -> list[dto.User]: + users: list[dto.User] = [ dto.User( id=UUID("123e4567-e89b-12d3-a456-426614174000"), username="john_doe", first_name="John", last_name="Doe", middle_name=None, + deleted_at=None, ), - dto.DeletedUser( + dto.User( id=UUID("123e4567-e89b-12d3-a456-426614174001"), + username=None, first_name="Jane", last_name="Smith", middle_name=None, @@ -32,7 +34,7 @@ def users() -> list[dto.UserDTOs]: return users -async def test_get_users_handler_success(users: list[dto.UserDTOs], user_reader: UserReaderMock) -> None: +async def test_get_users_handler_success(users: list[dto.User], user_reader: UserReaderMock) -> None: await user_reader.add_users(users) handler = GetUsersHandler(user_reader) @@ -53,7 +55,7 @@ async def test_get_users_handler_success(users: list[dto.UserDTOs], user_reader: assert result.pagination.order == query.pagination.order -async def test_get_users_handler_no_users(users: list[dto.UserDTOs], user_reader: UserReaderMock) -> None: +async def test_get_users_handler_no_users(users: list[dto.User], user_reader: UserReaderMock) -> None: handler = GetUsersHandler(user_reader) await user_reader.add_users(users) From c400e524c95a66b842d9d8eef1b5e3ebee7fe9e8 Mon Sep 17 00:00:00 2001 From: SamWarden Date: Mon, 14 Apr 2025 20:02:46 +0300 Subject: [PATCH 10/20] feat(app): add the domain service and imperative mapping --- .../user/{dto/deleted_user.py => dto.py} | 13 ++- src/user_service/domain/user/entities.py | 13 +++ src/user_service/domain/user/entities/user.py | 81 ------------------- .../infrastructure/db/converters.py | 71 ---------------- .../infrastructure/db/converters/user.py | 14 ++++ .../infrastructure/db/models/user.py | 35 ++++++++ 6 files changed, 71 insertions(+), 156 deletions(-) rename src/user_service/application/user/{dto/deleted_user.py => dto.py} (61%) create mode 100644 src/user_service/domain/user/entities.py delete mode 100644 src/user_service/domain/user/entities/user.py delete mode 100644 src/user_service/infrastructure/db/converters.py create mode 100644 src/user_service/infrastructure/db/converters/user.py create mode 100644 src/user_service/infrastructure/db/models/user.py diff --git a/src/user_service/application/user/dto/deleted_user.py b/src/user_service/application/user/dto.py similarity index 61% rename from src/user_service/application/user/dto/deleted_user.py rename to src/user_service/application/user/dto.py index 746e1db..6916346 100644 --- a/src/user_service/application/user/dto/deleted_user.py +++ b/src/user_service/application/user/dto.py @@ -1,21 +1,26 @@ -from dataclasses import dataclass, field +from dataclasses import dataclass from datetime import datetime +from typing import TypeAlias from uuid import UUID from user_service.application.common.dto import DTO +from user_service.application.common.pagination.dto import PaginatedItemsDTO @dataclass(frozen=True) -class DeletedUser(DTO): +class User(DTO): id: UUID + username: str | None first_name: str last_name: str middle_name: str | None - deleted_at: datetime - username: None = field(init=False, default=None) + deleted_at: None | datetime @property def full_name(self) -> str: if self.middle_name: return f"{self.first_name} {self.middle_name} {self.last_name}" return f"{self.first_name} {self.last_name}" + + +Users: TypeAlias = PaginatedItemsDTO[User] diff --git a/src/user_service/domain/user/entities.py b/src/user_service/domain/user/entities.py new file mode 100644 index 0000000..8dee414 --- /dev/null +++ b/src/user_service/domain/user/entities.py @@ -0,0 +1,13 @@ +import dataclasses + +from user_service.domain.common.entity import Entity +from user_service.domain.user.value_objects import FullName, UserId, Username +from user_service.domain.user.value_objects.deletion_time import DeletionTime + + +@dataclasses.dataclass +class User(Entity): + id: UserId + username: Username + full_name: FullName + deleted_at: DeletionTime = dataclasses.field(default=DeletionTime.create_not_deleted(), kw_only=True) diff --git a/src/user_service/domain/user/entities/user.py b/src/user_service/domain/user/entities/user.py deleted file mode 100644 index 3aeaa16..0000000 --- a/src/user_service/domain/user/entities/user.py +++ /dev/null @@ -1,81 +0,0 @@ -import dataclasses -from typing import Self - -from user_service.domain.common.entities.aggregate_root import AggregateRoot -from user_service.domain.user.events import FullNameUpdated -from user_service.domain.user.events.user_created import UserCreated -from user_service.domain.user.events.user_deleted import UserDeleted -from user_service.domain.user.events.username_updated import UsernameUpdated -from user_service.domain.user.exceptions import UserIsDeletedError, UsernameAlreadyExistsError -from user_service.domain.user.value_objects import FullName, UserId, Username -from user_service.domain.user.value_objects.deleted_status import DeletionTime - - -@dataclasses.dataclass -class User(AggregateRoot): - id: UserId - username: Username - full_name: FullName - existing_usernames: set[Username] = dataclasses.field(default_factory=set) - deleted_at: DeletionTime = dataclasses.field(default=DeletionTime.create_not_deleted(), kw_only=True) - - @classmethod - def create( - cls, - user_id: UserId, - username: Username, - full_name: FullName, - existing_usernames: set[Username], - ) -> Self: - if username in existing_usernames: - raise UsernameAlreadyExistsError(username.to_raw()) - - existing_usernames.add(username) - user = cls(user_id, username, full_name, existing_usernames) - user.record_event( - UserCreated( - user_id.to_raw(), - username.to_raw(), - full_name.first_name, - full_name.last_name, - full_name.middle_name, - ), - ) - return user - - def set_username(self, username: Username) -> None: - self._validate_not_deleted() - - if username != self.username: - if username in self.existing_usernames: - raise UsernameAlreadyExistsError(username.to_raw()) - - self.existing_usernames.remove(self.username) - self.existing_usernames.add(username) - self.username = username - self.record_event(UsernameUpdated(self.id.to_raw(), self.username.to_raw())) - - def set_full_name(self, full_name: FullName) -> None: - self._validate_not_deleted() - - self.full_name = full_name - self.record_event( - FullNameUpdated( - self.id.to_raw(), - self.full_name.first_name, - self.full_name.last_name, - self.full_name.middle_name, - ), - ) - - def delete(self) -> None: - self._validate_not_deleted() - - self.existing_usernames.remove(self.username) - self.username = Username(None) - self.deleted_at = DeletionTime.create_deleted() - self.record_event(UserDeleted(self.id.to_raw())) - - def _validate_not_deleted(self) -> None: - if self.deleted_at.is_deleted(): - raise UserIsDeletedError(self.id.to_raw()) diff --git a/src/user_service/infrastructure/db/converters.py b/src/user_service/infrastructure/db/converters.py deleted file mode 100644 index 296ddae..0000000 --- a/src/user_service/infrastructure/db/converters.py +++ /dev/null @@ -1,71 +0,0 @@ -from datetime import datetime -from typing import cast - -from user_service.application.common.exceptions import MappingError -from user_service.application.user import dto -from user_service.domain.user import entities, value_objects as vo -from user_service.domain.user.value_objects import Username -from user_service.domain.user.value_objects.deleted_status import DeletionTime -from user_service.infrastructure.db import models - - -def convert_user_entity_to_db_model(user: entities.User) -> models.User: - return models.User( - id=user.id.to_raw(), - username=user.username.to_raw(), - first_name=user.full_name.first_name, - last_name=user.full_name.last_name, - middle_name=user.full_name.middle_name, - deleted_at=user.deleted_at.to_raw(), - ) - - -def convert_db_model_to_user_entity(user: models.User, existing_usernames: set[Username]) -> entities.User: - full_name = vo.FullName( - first_name=user.first_name, - last_name=user.last_name, - middle_name=user.middle_name, - ) - return entities.User( - id=vo.UserId(user.id), - username=vo.Username(user.username), - full_name=full_name, - deleted_at=DeletionTime(user.deleted_at), - existing_usernames=existing_usernames, - ) - - -def convert_db_model_to_active_user_dto(user: models.User) -> dto.User: - if user.deleted_at is not None: - raise MappingError(f"User {user} is deleted") - - return dto.User( - id=user.id, - username=cast(str, user.username), - first_name=user.first_name, - last_name=user.last_name, - middle_name=user.middle_name, - ) - - -def convert_db_model_to_deleted_user_dto(user: models.User) -> dto.DeletedUser: - if user.deleted_at is None: - raise MappingError(f"User {user} isn't deleted") - - return dto.DeletedUser( - id=user.id, - first_name=user.first_name, - last_name=user.last_name, - middle_name=user.middle_name, - deleted_at=cast(datetime, user.deleted_at), - ) - - -def convert_db_model_to_user_dto(user: models.User) -> dto.UserDTOs: - match user: - case models.User(deleted_at=None): - return convert_db_model_to_active_user_dto(user) - case models.User(deleted_at=True): - return convert_db_model_to_deleted_user_dto(user) - case _: - raise MappingError(f"User {user} is invalid") diff --git a/src/user_service/infrastructure/db/converters/user.py b/src/user_service/infrastructure/db/converters/user.py new file mode 100644 index 0000000..8656e1e --- /dev/null +++ b/src/user_service/infrastructure/db/converters/user.py @@ -0,0 +1,14 @@ +from typing import Any, cast + +from user_service.application.user import dto + + +def convert_db_row_to_user_dto(user_row: Any) -> dto.User: + return dto.User( + id=user_row.id, + username=cast(str, user_row.username), + first_name=user_row.first_name, + last_name=user_row.last_name, + middle_name=user_row.middle_name, + deleted_at=user_row.deleted_at, + ) diff --git a/src/user_service/infrastructure/db/models/user.py b/src/user_service/infrastructure/db/models/user.py new file mode 100644 index 0000000..fea0334 --- /dev/null +++ b/src/user_service/infrastructure/db/models/user.py @@ -0,0 +1,35 @@ +import sqlalchemy as sa +from sqlalchemy.orm import composite +from uuid6 import uuid7 + +from user_service.domain.user import entities, value_objects as vo + +from .base import TimedBaseModel, mapper_registry + +USERS_TABLE = sa.Table( + "users", + TimedBaseModel.metadata, + sa.Column("id", sa.UUID(as_uuid=True), primary_key=True, default=uuid7, server_default=sa.func.uuid_generate_v7()), + sa.Column("username", sa.String, unique=True), + sa.Column("first_name", sa.String), + sa.Column("last_name", sa.String), + sa.Column("middle_name", sa.String), + sa.Column("deleted_at", sa.DateTime(timezone=True), server_default=sa.text("NULL"), nullable=True), +) + +mapper_registry.map_imperatively( + entities.User, + USERS_TABLE, + properties={ + "id": composite(vo.UserId, USERS_TABLE.c.id), + "username": composite(vo.Username, USERS_TABLE.c.username), + "full_name": composite( + vo.FullName, + USERS_TABLE.c.first_name, + USERS_TABLE.c.last_name, + USERS_TABLE.c.middle_name, + ), + "deleted_at": composite(vo.DeletionTime, USERS_TABLE.c.deleted_at), + }, + column_prefix="_", +) From 20809ea00407da17de049593d4d0665a937aba67 Mon Sep 17 00:00:00 2001 From: SamWarden Date: Mon, 14 Apr 2025 20:08:42 +0300 Subject: [PATCH 11/20] refactor(db): remove `exception_mapper` --- .../infrastructure/db/exception_mapper.py | 24 ------------------- .../infrastructure/db/readers/user.py | 4 ---- .../infrastructure/db/repositories/user.py | 4 ---- 3 files changed, 32 deletions(-) delete mode 100644 src/user_service/infrastructure/db/exception_mapper.py diff --git a/src/user_service/infrastructure/db/exception_mapper.py b/src/user_service/infrastructure/db/exception_mapper.py deleted file mode 100644 index 7669239..0000000 --- a/src/user_service/infrastructure/db/exception_mapper.py +++ /dev/null @@ -1,24 +0,0 @@ -from collections.abc import Callable, Coroutine -from functools import wraps -from typing import Any, ParamSpec, TypeVar - -from sqlalchemy.exc import SQLAlchemyError - -from user_service.application.common.exceptions import RepoError - -Param = ParamSpec("Param") -ReturnType = TypeVar("ReturnType") -Func = Callable[Param, ReturnType] - - -def exception_mapper( - func: Callable[Param, Coroutine[Any, Any, ReturnType]], -) -> Callable[Param, Coroutine[Any, Any, ReturnType]]: - @wraps(func) - async def wrapped(*args: Param.args, **kwargs: Param.kwargs) -> ReturnType: - try: - return await func(*args, **kwargs) - except SQLAlchemyError as err: - raise RepoError from err - - return wrapped diff --git a/src/user_service/infrastructure/db/readers/user.py b/src/user_service/infrastructure/db/readers/user.py index 5c248b4..646521e 100644 --- a/src/user_service/infrastructure/db/readers/user.py +++ b/src/user_service/infrastructure/db/readers/user.py @@ -9,13 +9,11 @@ from user_service.application.user.interfaces.persistence import GetUsersFilters, UserReader from user_service.domain.common.constants import Empty from user_service.infrastructure.db.converters.user import convert_db_row_to_user_dto -from user_service.infrastructure.db.exception_mapper import exception_mapper from user_service.infrastructure.db.models.user import USERS_TABLE from user_service.infrastructure.db.readers.base import SQLAlchemyReader class UserReaderImpl(SQLAlchemyReader, UserReader): - @exception_mapper async def get_user_by_id(self, user_id: UUID) -> dto.User | None: result = await self._session.execute(select(USERS_TABLE).where(USERS_TABLE.c.id == user_id)) user_row = result.mappings().one_or_none() @@ -24,7 +22,6 @@ async def get_user_by_id(self, user_id: UUID) -> dto.User | None: return convert_db_row_to_user_dto(user_row) - @exception_mapper async def get_user_by_username(self, username: str) -> dto.User | None: result = await self._session.execute(select(USERS_TABLE).where(USERS_TABLE.c.username == username)) user_row = result.mappings().one_or_none() @@ -33,7 +30,6 @@ async def get_user_by_username(self, username: str) -> dto.User | None: return convert_db_row_to_user_dto(user_row) - @exception_mapper async def get_users(self, filters: GetUsersFilters, pagination: Pagination) -> dto.Users: query = select(USERS_TABLE) diff --git a/src/user_service/infrastructure/db/repositories/user.py b/src/user_service/infrastructure/db/repositories/user.py index 3fed634..b60dfc2 100644 --- a/src/user_service/infrastructure/db/repositories/user.py +++ b/src/user_service/infrastructure/db/repositories/user.py @@ -9,18 +9,15 @@ from user_service.domain.user.exceptions import UsernameAlreadyExistsError from user_service.domain.user.interfaces.repo import UserRepo from user_service.domain.user.value_objects import UserId, Username -from user_service.infrastructure.db.exception_mapper import exception_mapper from user_service.infrastructure.db.models.user import USERS_TABLE from user_service.infrastructure.db.repositories.base import SQLAlchemyRepo class UserRepoImpl(SQLAlchemyRepo, UserRepo): - @exception_mapper async def acquire_user_by_id(self, user_id: UserId) -> entities.User | None: user: entities.User | None = await self._session.get(entities.User, user_id.to_raw(), with_for_update=True) return user - @exception_mapper async def add_user(self, user: entities.User) -> None: self._session.add(user) try: @@ -28,7 +25,6 @@ async def add_user(self, user: entities.User) -> None: except IntegrityError as err: self._parse_error(err, user) - @exception_mapper async def check_username_exists(self, username: Username) -> bool: result: bool | None = await self._session.scalar( select(exists().where(USERS_TABLE.c.username == username.to_raw())) From 5ebfc469ce5dd53ecaf0d48dd972a7a964927e4b Mon Sep 17 00:00:00 2001 From: SamWarden Date: Mon, 14 Apr 2025 20:46:24 +0300 Subject: [PATCH 12/20] refactor(di): add the `main` dir for di container and mediator configuration --- src/user_service/__main__.py | 40 ++++++++++++++++- src/user_service/infrastructure/db/main.py | 9 +--- .../infrastructure/message_broker/main.py | 19 ++------ src/user_service/main/__init__.py | 0 .../{infrastructure => main}/di/__init__.py | 0 .../{infrastructure => main}/di/constants.py | 0 src/user_service/main/di/db.py | 8 ++++ .../{infrastructure => main}/di/main.py | 43 +++++++++++-------- src/user_service/main/di/message_broker.py | 20 +++++++++ .../mediator/__init__.py | 0 .../{infrastructure => main}/mediator/main.py | 2 +- .../mediator/utils.py | 0 src/user_service/presentation/api/__main__.py | 38 ---------------- src/user_service/presentation/api/config.py | 13 ------ .../presentation/api/providers/di.py | 2 +- .../presentation/api/providers/mediator.py | 2 +- 16 files changed, 100 insertions(+), 96 deletions(-) create mode 100644 src/user_service/main/__init__.py rename src/user_service/{infrastructure => main}/di/__init__.py (100%) rename src/user_service/{infrastructure => main}/di/constants.py (100%) create mode 100644 src/user_service/main/di/db.py rename src/user_service/{infrastructure => main}/di/main.py (71%) create mode 100644 src/user_service/main/di/message_broker.py rename src/user_service/{infrastructure => main}/mediator/__init__.py (100%) rename src/user_service/{infrastructure => main}/mediator/main.py (97%) rename src/user_service/{infrastructure => main}/mediator/utils.py (100%) delete mode 100644 src/user_service/presentation/api/__main__.py diff --git a/src/user_service/__main__.py b/src/user_service/__main__.py index f65e62a..9fb7926 100644 --- a/src/user_service/__main__.py +++ b/src/user_service/__main__.py @@ -1,6 +1,44 @@ import asyncio +import logging + +from user_service.infrastructure.config_loader import load_config +from user_service.infrastructure.db.main import build_sa_engine, build_sa_session_factory +from user_service.infrastructure.event_bus.exchanges import declare_exchanges +from user_service.infrastructure.log import configure_logging +from user_service.infrastructure.message_broker.main import build_rq_channel_pool, build_rq_connection_pool +from user_service.main.di import DiScope, init_di_builder, setup_di_builder +from user_service.main.mediator import init_mediator, setup_mediator +from user_service.presentation.api.config import Config +from user_service.presentation.api.main import init_api, run_api + +logger = logging.getLogger(__name__) + + +async def main() -> None: + config = load_config(Config) + configure_logging(config.logging) + + logger.info("Launch app") + + async with ( + build_sa_engine(config.db) as db_engine, + build_rq_connection_pool(config.event_bus) as rq_connection_pool, + build_rq_channel_pool(rq_connection_pool) as rq_channel_pool, + ): + session_factory = build_sa_session_factory(db_engine) + di_builder = init_di_builder() + setup_di_builder(di_builder, db_engine, session_factory, rq_connection_pool, rq_channel_pool) + + mediator = init_mediator(di_builder) + setup_mediator(mediator) + + async with di_builder.enter_scope(DiScope.APP) as di_state: + async with di_builder.enter_scope(DiScope.REQUEST, state=di_state) as request_di_state: + await di_builder.execute(declare_exchanges, DiScope.REQUEST, state=request_di_state) + + app = init_api(mediator, di_builder, di_state, config.api.debug) + await run_api(app, config.api) -from user_service.presentation.api.__main__ import main if __name__ == "__main__": asyncio.run(main()) diff --git a/src/user_service/infrastructure/db/main.py b/src/user_service/infrastructure/db/main.py index 6394891..3cf9f4e 100644 --- a/src/user_service/infrastructure/db/main.py +++ b/src/user_service/infrastructure/db/main.py @@ -1,4 +1,5 @@ from collections.abc import AsyncGenerator +from contextlib import asynccontextmanager import orjson from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker, create_async_engine @@ -6,6 +7,7 @@ from .config import DBConfig +@asynccontextmanager async def build_sa_engine(db_config: DBConfig) -> AsyncGenerator[AsyncEngine, None]: engine = create_async_engine( db_config.full_url, @@ -23,10 +25,3 @@ async def build_sa_engine(db_config: DBConfig) -> AsyncGenerator[AsyncEngine, No def build_sa_session_factory(engine: AsyncEngine) -> async_sessionmaker[AsyncSession]: session_factory = async_sessionmaker(bind=engine, autoflush=False, expire_on_commit=False) return session_factory - - -async def build_sa_session( - session_factory: async_sessionmaker[AsyncSession], -) -> AsyncGenerator[AsyncSession, None]: - async with session_factory() as session: - yield session diff --git a/src/user_service/infrastructure/message_broker/main.py b/src/user_service/infrastructure/message_broker/main.py index 7e6dbad..262bcb0 100644 --- a/src/user_service/infrastructure/message_broker/main.py +++ b/src/user_service/infrastructure/message_broker/main.py @@ -1,4 +1,5 @@ from collections.abc import AsyncGenerator +from contextlib import asynccontextmanager import aio_pika from aio_pika.pool import Pool @@ -8,6 +9,7 @@ from .config import EventBusConfig +@asynccontextmanager async def build_rq_connection_pool( event_bus_config: EventBusConfig, ) -> AsyncGenerator[Pool[aio_pika.abc.AbstractConnection], None]: @@ -16,25 +18,10 @@ async def build_rq_connection_pool( yield rq_connection_pool +@asynccontextmanager async def build_rq_channel_pool( rq_connection_pool: Pool[aio_pika.abc.AbstractConnection], ) -> AsyncGenerator[Pool[aio_pika.abc.AbstractChannel], None]: rq_channel_pool = Pool(ChannelFactory(rq_connection_pool).get_channel, max_size=100) async with rq_channel_pool: yield rq_channel_pool - - -async def build_rq_channel( - rq_channel_pool: Pool[aio_pika.abc.AbstractChannel], -) -> AsyncGenerator[aio_pika.abc.AbstractChannel, None]: - async with rq_channel_pool.acquire() as channel: - yield channel - channel.transaction() - - -async def build_rq_transaction( - rq_channel: aio_pika.abc.AbstractChannel, -) -> aio_pika.abc.AbstractTransaction: - rq_transaction = rq_channel.transaction() - await rq_transaction.select() - return rq_transaction diff --git a/src/user_service/main/__init__.py b/src/user_service/main/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/user_service/infrastructure/di/__init__.py b/src/user_service/main/di/__init__.py similarity index 100% rename from src/user_service/infrastructure/di/__init__.py rename to src/user_service/main/di/__init__.py diff --git a/src/user_service/infrastructure/di/constants.py b/src/user_service/main/di/constants.py similarity index 100% rename from src/user_service/infrastructure/di/constants.py rename to src/user_service/main/di/constants.py diff --git a/src/user_service/main/di/db.py b/src/user_service/main/di/db.py new file mode 100644 index 0000000..a7484af --- /dev/null +++ b/src/user_service/main/di/db.py @@ -0,0 +1,8 @@ +from collections.abc import AsyncGenerator + +from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker + + +async def build_sa_session(session_factory: async_sessionmaker[AsyncSession]) -> AsyncGenerator[AsyncSession, None]: + async with session_factory() as session: + yield session diff --git a/src/user_service/infrastructure/di/main.py b/src/user_service/main/di/main.py similarity index 71% rename from src/user_service/infrastructure/di/main.py rename to src/user_service/main/di/main.py index 19875c3..7b15d3d 100644 --- a/src/user_service/infrastructure/di/main.py +++ b/src/user_service/main/di/main.py @@ -12,21 +12,16 @@ from user_service.application.common.interfaces.uow import UnitOfWork from user_service.application.user.interfaces.persistence import UserReader from user_service.domain.user.interfaces.repo import UserRepo -from user_service.infrastructure.db.main import build_sa_engine, build_sa_session, build_sa_session_factory from user_service.infrastructure.db.readers import UserReaderImpl from user_service.infrastructure.db.repositories.user import UserRepoImpl -from user_service.infrastructure.di import DiScope from user_service.infrastructure.event_bus.event_bus import EventBusImpl -from user_service.infrastructure.mediator import get_mediator from user_service.infrastructure.message_broker.interface import MessageBroker -from user_service.infrastructure.message_broker.main import ( - build_rq_channel, - build_rq_channel_pool, - build_rq_connection_pool, - build_rq_transaction, -) from user_service.infrastructure.message_broker.message_broker import MessageBrokerImpl from user_service.infrastructure.uow import build_uow +from user_service.main.di import DiScope +from user_service.main.di.db import build_sa_session +from user_service.main.di.message_broker import build_rq_channel, build_rq_transaction +from user_service.main.mediator import get_mediator def init_di_builder() -> DiBuilder: @@ -37,12 +32,18 @@ def init_di_builder() -> DiBuilder: return di_builder -def setup_di_builder(di_builder: DiBuilder) -> None: +def setup_di_builder( + di_builder: DiBuilder, + di_engine: AsyncEngine, + session_factory: async_sessionmaker[AsyncSession], + rq_connection_pool: aio_pika.pool.Pool[aio_pika.abc.AbstractConnection], + rq_channel_pool: aio_pika.pool.Pool[aio_pika.abc.AbstractChannel], +) -> None: di_builder.bind(bind_by_type(Dependent(lambda *args: di_builder, scope=DiScope.APP), DiBuilder)) di_builder.bind(bind_by_type(Dependent(build_uow, scope=DiScope.REQUEST), UnitOfWork)) setup_mediator_factory(di_builder, get_mediator, DiScope.REQUEST) - setup_db_factories(di_builder) - setup_event_bus_factories(di_builder) + setup_db_factories(di_builder, di_engine, session_factory) + setup_event_bus_factories(di_builder, rq_connection_pool, rq_channel_pool) def setup_mediator_factory( @@ -56,11 +57,13 @@ def setup_mediator_factory( di_builder.bind(bind_by_type(Dependent(mediator_factory, scope=scope), EventMediator)) -def setup_db_factories(di_builder: DiBuilder) -> None: - di_builder.bind(bind_by_type(Dependent(build_sa_engine, scope=DiScope.APP), AsyncEngine)) +def setup_db_factories( + di_builder: DiBuilder, db_engine: AsyncEngine, session_factory: async_sessionmaker[AsyncSession] +) -> None: + di_builder.bind(bind_by_type(Dependent(lambda *args: db_engine, scope=DiScope.APP), AsyncEngine)) di_builder.bind( bind_by_type( - Dependent(build_sa_session_factory, scope=DiScope.APP), + Dependent(lambda *args: session_factory, scope=DiScope.APP), async_sessionmaker[AsyncSession], ), ) @@ -69,16 +72,20 @@ def setup_db_factories(di_builder: DiBuilder) -> None: di_builder.bind(bind_by_type(Dependent(UserReaderImpl, scope=DiScope.REQUEST), UserReader, covariant=True)) -def setup_event_bus_factories(di_builder: DiBuilder) -> None: +def setup_event_bus_factories( + di_builder: DiBuilder, + rq_connection_pool: aio_pika.pool.Pool[aio_pika.abc.AbstractConnection], + rq_channel_pool: aio_pika.pool.Pool[aio_pika.abc.AbstractChannel], +) -> None: di_builder.bind( bind_by_type( - Dependent(build_rq_connection_pool, scope=DiScope.APP), + Dependent(lambda *args: rq_connection_pool, scope=DiScope.APP), aio_pika.pool.Pool[aio_pika.abc.AbstractConnection], ), ) di_builder.bind( bind_by_type( - Dependent(build_rq_channel_pool, scope=DiScope.APP), + Dependent(lambda *args: rq_channel_pool, scope=DiScope.APP), aio_pika.pool.Pool[aio_pika.abc.AbstractChannel], ), ) diff --git a/src/user_service/main/di/message_broker.py b/src/user_service/main/di/message_broker.py new file mode 100644 index 0000000..4190797 --- /dev/null +++ b/src/user_service/main/di/message_broker.py @@ -0,0 +1,20 @@ +from collections.abc import AsyncGenerator + +import aio_pika +from aio_pika.pool import Pool + + +async def build_rq_channel( + rq_channel_pool: Pool[aio_pika.abc.AbstractChannel], +) -> AsyncGenerator[aio_pika.abc.AbstractChannel, None]: + async with rq_channel_pool.acquire() as channel: + yield channel + channel.transaction() + + +async def build_rq_transaction( + rq_channel: aio_pika.abc.AbstractChannel, +) -> aio_pika.abc.AbstractTransaction: + rq_transaction = rq_channel.transaction() + await rq_transaction.select() + return rq_transaction diff --git a/src/user_service/infrastructure/mediator/__init__.py b/src/user_service/main/mediator/__init__.py similarity index 100% rename from src/user_service/infrastructure/mediator/__init__.py rename to src/user_service/main/mediator/__init__.py diff --git a/src/user_service/infrastructure/mediator/main.py b/src/user_service/main/mediator/main.py similarity index 97% rename from src/user_service/infrastructure/mediator/main.py rename to src/user_service/main/mediator/main.py index c03440c..618414c 100644 --- a/src/user_service/infrastructure/mediator/main.py +++ b/src/user_service/main/mediator/main.py @@ -19,9 +19,9 @@ from user_service.application.user.queries.get_user_by_username import GetUserByUsername, GetUserByUsernameHandler from user_service.application.user.queries.get_users import GetUsers, GetUsersHandler from user_service.domain.common.event import Event -from user_service.infrastructure.di import DiScope from user_service.infrastructure.event_bus.event_handler import EventHandlerPublisher from user_service.infrastructure.log.event_handler import EventLogger +from user_service.main.di import DiScope def init_mediator(di_builder: DiBuilder) -> Mediator: diff --git a/src/user_service/infrastructure/mediator/utils.py b/src/user_service/main/mediator/utils.py similarity index 100% rename from src/user_service/infrastructure/mediator/utils.py rename to src/user_service/main/mediator/utils.py diff --git a/src/user_service/presentation/api/__main__.py b/src/user_service/presentation/api/__main__.py deleted file mode 100644 index 61a50a6..0000000 --- a/src/user_service/presentation/api/__main__.py +++ /dev/null @@ -1,38 +0,0 @@ -import asyncio -import logging - -from user_service.infrastructure.config_loader import load_config -from user_service.infrastructure.di import DiScope, init_di_builder, setup_di_builder -from user_service.infrastructure.event_bus.exchanges import declare_exchanges -from user_service.infrastructure.log import configure_logging -from user_service.infrastructure.mediator import init_mediator, setup_mediator -from user_service.presentation.api.main import init_api, run_api - -from .config import Config, setup_di_builder_config - -logger = logging.getLogger(__name__) - - -async def main() -> None: - config = load_config(Config) - configure_logging(config.logging) - - logger.info("Launch app", extra={"config": config}) - - di_builder = init_di_builder() - setup_di_builder(di_builder) - setup_di_builder_config(di_builder, config) - - async with di_builder.enter_scope(DiScope.APP) as di_state: - mediator = await di_builder.execute(init_mediator, DiScope.APP, state=di_state) - setup_mediator(mediator) - - async with di_builder.enter_scope(DiScope.REQUEST, state=di_state) as request_di_state: - await di_builder.execute(declare_exchanges, DiScope.REQUEST, state=request_di_state) - - app = init_api(mediator, di_builder, di_state, config.api.debug) - await run_api(app, config.api) - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/src/user_service/presentation/api/config.py b/src/user_service/presentation/api/config.py index a10bf26..ad4c7e2 100644 --- a/src/user_service/presentation/api/config.py +++ b/src/user_service/presentation/api/config.py @@ -1,11 +1,6 @@ from dataclasses import dataclass, field -from di import bind_by_type -from di.dependent import Dependent -from didiator.interface.utils.di_builder import DiBuilder - from user_service.infrastructure.db import DBConfig -from user_service.infrastructure.di import DiScope from user_service.infrastructure.log import LoggingConfig from user_service.infrastructure.message_broker.config import EventBusConfig @@ -23,11 +18,3 @@ class Config: logging: LoggingConfig = field(default_factory=LoggingConfig) api: APIConfig = field(default_factory=APIConfig) event_bus: EventBusConfig = field(default_factory=EventBusConfig) - - -def setup_di_builder_config(di_builder: DiBuilder, config: Config) -> None: - di_builder.bind(bind_by_type(Dependent(lambda *args: config, scope=DiScope.APP), Config)) - di_builder.bind(bind_by_type(Dependent(lambda *args: config.db, scope=DiScope.APP), DBConfig)) - di_builder.bind(bind_by_type(Dependent(lambda *args: config.logging, scope=DiScope.APP), LoggingConfig)) - di_builder.bind(bind_by_type(Dependent(lambda *args: config.api, scope=DiScope.APP), APIConfig)) - di_builder.bind(bind_by_type(Dependent(lambda *args: config.event_bus, scope=DiScope.APP), EventBusConfig)) diff --git a/src/user_service/presentation/api/providers/di.py b/src/user_service/presentation/api/providers/di.py index 45b5a53..17464a8 100644 --- a/src/user_service/presentation/api/providers/di.py +++ b/src/user_service/presentation/api/providers/di.py @@ -4,7 +4,7 @@ from didiator.interface.utils.di_builder import DiBuilder from fastapi import Depends -from user_service.infrastructure.di import DiScope +from user_service.main.di import DiScope def get_di_builder() -> DiBuilder: diff --git a/src/user_service/presentation/api/providers/mediator.py b/src/user_service/presentation/api/providers/mediator.py index 82bb234..db01075 100644 --- a/src/user_service/presentation/api/providers/mediator.py +++ b/src/user_service/presentation/api/providers/mediator.py @@ -4,7 +4,7 @@ from didiator import Mediator from fastapi import Depends -from user_service.infrastructure.mediator import get_mediator +from user_service.main.mediator import get_mediator from .di import get_di_state From 6d9147b6d395377cc55e982908470ce2e041ac1c Mon Sep 17 00:00:00 2001 From: SamWarden Date: Mon, 14 Apr 2025 20:50:30 +0300 Subject: [PATCH 13/20] fix(justfile): use `uv` instead of `poetry` --- justfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/justfile b/justfile index aeda0e2..72ab931 100644 --- a/justfile +++ b/justfile @@ -6,7 +6,7 @@ help: # Install package with dependencies install: - poetry install --with dev,test,lint + uv sync --all-extras --all-groups # Run pre-commit lint: @@ -37,4 +37,4 @@ migrate: docker compose --profile migration up --build _py *args: - poetry run {{args}} + uv run {{args}} From b5520587b848c10269ce8473c1f267f4dd0fb31a Mon Sep 17 00:00:00 2001 From: SamWarden Date: Tue, 22 Apr 2025 12:42:20 +0300 Subject: [PATCH 14/20] refactor(uow): move uow factory to the `di` dir --- src/user_service/main/di/main.py | 2 +- src/user_service/{infrastructure => main/di}/uow.py | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/user_service/{infrastructure => main/di}/uow.py (100%) diff --git a/src/user_service/main/di/main.py b/src/user_service/main/di/main.py index 7b15d3d..a14af33 100644 --- a/src/user_service/main/di/main.py +++ b/src/user_service/main/di/main.py @@ -17,10 +17,10 @@ from user_service.infrastructure.event_bus.event_bus import EventBusImpl from user_service.infrastructure.message_broker.interface import MessageBroker from user_service.infrastructure.message_broker.message_broker import MessageBrokerImpl -from user_service.infrastructure.uow import build_uow from user_service.main.di import DiScope from user_service.main.di.db import build_sa_session from user_service.main.di.message_broker import build_rq_channel, build_rq_transaction +from user_service.main.di.uow import build_uow from user_service.main.mediator import get_mediator diff --git a/src/user_service/infrastructure/uow.py b/src/user_service/main/di/uow.py similarity index 100% rename from src/user_service/infrastructure/uow.py rename to src/user_service/main/di/uow.py From acbd4b8ef2958ef908ef51dd9eeaea99c95c9c48 Mon Sep 17 00:00:00 2001 From: SamWarden Date: Tue, 22 Apr 2025 12:51:27 +0300 Subject: [PATCH 15/20] refactor(monitoring): move monitoring configs to the `monitoring` dir --- .hadolint.yaml | 1 - docker-compose.yaml | 27 ++++++++++++------- .../provisioning/datasources/loki.yml | 0 {loki => monitoring/loki}/config.yaml | 0 {vector => monitoring/vector}/vector.toml | 0 5 files changed, 17 insertions(+), 11 deletions(-) rename {grafana => monitoring/grafana}/provisioning/datasources/loki.yml (100%) rename {loki => monitoring/loki}/config.yaml (100%) rename {vector => monitoring/vector}/vector.toml (100%) diff --git a/.hadolint.yaml b/.hadolint.yaml index b116e5d..178b0be 100644 --- a/.hadolint.yaml +++ b/.hadolint.yaml @@ -1,5 +1,4 @@ ignored: - DL3008 - - DL3059 trustedRegistries: - docker.io diff --git a/docker-compose.yaml b/docker-compose.yaml index c8884e9..a060d4e 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -133,7 +133,7 @@ services: - user_service.grafana.network volumes: - user_service.grafana.data:/var/lib/grafana:rw - - ./grafana/provisioning:/etc/grafana/provisioning:rw + - ./monitoring/grafana/provisioning:/etc/grafana/provisioning:rw environment: - GF_SECURITY_ADMIN_USER=${GRAFANA_USER:-admin} - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD:-admin} @@ -150,7 +150,7 @@ services: expose: - "3100" volumes: - - ./loki/config.yaml:/etc/loki/config.yaml:ro + - ./monitoring/loki/config.yaml:/etc/loki/config.yaml:ro - user_service.loki.data:/tmp/:rw command: -config.file=/etc/loki/config.yaml restart: unless-stopped @@ -169,19 +169,26 @@ services: - user_service.grafana.network volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - - ./vector/vector.toml:/etc/vector/vector.toml:ro + - ./monitoring/vector/vector.toml:/etc/vector/vector.toml:ro logging: driver: "json-file" options: max-size: "10m" volumes: - user_service.postgres.data: {} - user_service.rabbitmq.data: {} - user_service.grafana.data: {} - user_service.loki.data: {} + user_service.postgres.data: + name: user_service.postgres.data + user_service.rabbitmq.data: + name: user_service.rabbitmq.data + user_service.grafana.data: + name: user_service.grafana.data + user_service.loki.data: + name: user_service.loki.data networks: - user_service.postgres.network: {} - user_service.rabbitmq.network: {} - user_service.grafana.network: {} + user_service.postgres.network: + name: user_service.postgres.network + user_service.rabbitmq.network: + name: user_service.rabbitmq.network + user_service.grafana.network: + name: user_service.grafana.network diff --git a/grafana/provisioning/datasources/loki.yml b/monitoring/grafana/provisioning/datasources/loki.yml similarity index 100% rename from grafana/provisioning/datasources/loki.yml rename to monitoring/grafana/provisioning/datasources/loki.yml diff --git a/loki/config.yaml b/monitoring/loki/config.yaml similarity index 100% rename from loki/config.yaml rename to monitoring/loki/config.yaml diff --git a/vector/vector.toml b/monitoring/vector/vector.toml similarity index 100% rename from vector/vector.toml rename to monitoring/vector/vector.toml From fd1c5af9bfc6dfac0508415b728a3b00d3507115 Mon Sep 17 00:00:00 2001 From: SamWarden Date: Tue, 22 Apr 2025 13:26:42 +0300 Subject: [PATCH 16/20] fix(app): fix typehints --- .../infrastructure/event_bus/events/full_name_updated.py | 2 +- src/user_service/presentation/api/controllers/user.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/user_service/infrastructure/event_bus/events/full_name_updated.py b/src/user_service/infrastructure/event_bus/events/full_name_updated.py index 9a64b25..5db971c 100644 --- a/src/user_service/infrastructure/event_bus/events/full_name_updated.py +++ b/src/user_service/infrastructure/event_bus/events/full_name_updated.py @@ -7,7 +7,7 @@ @dataclass(frozen=True) -@integration_event("UsernameUpdated", exchange=USER_EXCHANGE) +@integration_event("FullNameUpdated", exchange=USER_EXCHANGE) class FullNameUpdated(IntegrationEvent): user_id: UUID first_name: str diff --git a/src/user_service/presentation/api/controllers/user.py b/src/user_service/presentation/api/controllers/user.py index fc86039..0da946c 100644 --- a/src/user_service/presentation/api/controllers/user.py +++ b/src/user_service/presentation/api/controllers/user.py @@ -50,7 +50,7 @@ async def create_user( create_user_command: CreateUser, mediator: Annotated[Mediator, Depends(Stub(Mediator))], -) -> OkResponse[dto.User]: +) -> OkResponse[None]: await mediator.send(create_user_command) return OkResponse() From ae47d73d514569c57a6360790c9fd5841d259e72 Mon Sep 17 00:00:00 2001 From: SamWarden Date: Mon, 19 May 2025 16:24:23 +0300 Subject: [PATCH 17/20] feat(tests): add using Postgres templates to tests --- .../infrastructure/db/migrations/env.py | 77 ++++++++----- tests/conftest.py | 1 + tests/fixtures/__init__.py | 0 tests/fixtures/db.py | 104 ++++++++++++++++++ tests/integration/conftest.py | 61 ---------- tests/integration/test_stairway.py | 20 ++-- 6 files changed, 166 insertions(+), 97 deletions(-) create mode 100644 tests/conftest.py create mode 100644 tests/fixtures/__init__.py create mode 100644 tests/fixtures/db.py delete mode 100644 tests/integration/conftest.py diff --git a/src/user_service/infrastructure/db/migrations/env.py b/src/user_service/infrastructure/db/migrations/env.py index ad96402..f715246 100644 --- a/src/user_service/infrastructure/db/migrations/env.py +++ b/src/user_service/infrastructure/db/migrations/env.py @@ -3,7 +3,7 @@ from alembic import context from sqlalchemy import engine_from_config, pool -from sqlalchemy.engine import Connection +from sqlalchemy.engine import Connection, Engine from sqlalchemy.ext.asyncio import AsyncEngine from user_service.infrastructure.config_loader import load_config @@ -19,10 +19,10 @@ if config.config_file_name is not None: fileConfig(config.config_file_name) -target_metadata = BaseModel.metadata +if not (FULL_URL := config.get_main_option("sqlalchemy.url")): + FULL_URL = load_config(DBConfig, "db").full_url -if not (full_url := config.get_main_option("sqlalchemy.url")): - full_url = load_config(DBConfig, "db").full_url +TARGET_METADATA = BaseModel.metadata def run_migrations_offline() -> None: @@ -38,8 +38,8 @@ def run_migrations_offline() -> None: """ context.configure( - url=full_url, - target_metadata=target_metadata, + url=FULL_URL, + target_metadata=TARGET_METADATA, literal_binds=True, dialect_opts={"paramstyle": "named"}, ) @@ -49,43 +49,62 @@ def run_migrations_offline() -> None: def do_run_migrations(connection: Connection) -> None: - context.configure(connection=connection, target_metadata=target_metadata) + context.configure(connection=connection, target_metadata=TARGET_METADATA) with context.begin_transaction(): context.run_migrations() -async def run_async_migrations() -> None: - connectable = AsyncEngine( - engine_from_config( - config.get_section(config.config_ini_section), - prefix="sqlalchemy.", - poolclass=pool.NullPool, - future=True, - url=full_url, - ), - ) +def run_migrations(engine: Engine) -> None: + with engine.connect() as connection: + do_run_migrations(connection) + + engine.dispose() + - async with connectable.connect() as connection: +async def run_async_migrations(engine: AsyncEngine) -> None: + async with engine.connect() as connection: await connection.run_sync(do_run_migrations) - await connectable.dispose() + await engine.dispose() + + +def setup_engine() -> Engine: + return engine_from_config( + config.get_section(config.config_ini_section) or {}, + prefix="sqlalchemy.", + poolclass=pool.NullPool, + future=True, + url=FULL_URL, + ) def run_migrations_online() -> None: """Run migrations in 'online' mode. - In this scenario we need to create an Engine - and associate a connection with the context. + In this scenario we need to create an Engine or receive a connection + and associate the connection with the context. """ - connectable = config.attributes.get("connection", None) - if connectable is None: - asyncio.run(run_async_migrations()) + connection: Connection | None = config.attributes.get("connection", None) + match connection: + case None: + engine = setup_engine() + if engine.driver == "asyncpg": + async_engine = AsyncEngine(engine) + asyncio.run(run_async_migrations(async_engine)) + else: + run_migrations(engine) + case Connection(): # type: ignore + do_run_migrations(connection) # type: ignore + case _: + raise TypeError(f"Unexpected connection type: {type(connection)}. Expected Connection") + + +def main() -> None: + if context.is_offline_mode(): + run_migrations_offline() else: - do_run_migrations(connectable) + run_migrations_online() -if context.is_offline_mode(): - run_migrations_offline() -else: - run_migrations_online() +main() diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..11a37f5 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1 @@ +pytest_plugins = ("tests.fixtures.db",) diff --git a/tests/fixtures/__init__.py b/tests/fixtures/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/db.py b/tests/fixtures/db.py new file mode 100644 index 0000000..8c9e157 --- /dev/null +++ b/tests/fixtures/db.py @@ -0,0 +1,104 @@ +import uuid +from collections.abc import AsyncGenerator, Generator +from typing import cast + +import alembic.command +import pytest +from alembic.config import Config as AlembicConfig +from sqlalchemy import URL, Connection, text +from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker, create_async_engine +from testcontainers.postgres import PostgresContainer + + +class PostgresDbManager: + def __init__(self, engine: AsyncEngine) -> None: + self._engine = engine + + async def create_database(self, database: str, template: str = "template1") -> None: + async with self._engine.connect() as connection: + # "template1" is the default template name for the `CREATE DATABASE` statement + await connection.execute(text(f'CREATE DATABASE "{database}" ENCODING \'utf8\' TEMPLATE "{template}"')) + + async def drop_database(self, database: str) -> None: + async with self._engine.connect() as connection: + await connection.execute(text(f'DROP DATABASE "{database}"')) + + +@pytest.fixture(scope="session") +def postgres() -> Generator[PostgresContainer, None, None]: + with PostgresContainer("postgres:17.5", dbname="template-db", driver="asyncpg") as postgres: + yield postgres + + +def get_alembic_config() -> AlembicConfig: + return AlembicConfig(file_="alembic.ini") + + +def run_migrations(connection: Connection) -> None: + alembic_config = get_alembic_config() + alembic_config.attributes["connection"] = connection + alembic.command.upgrade(config=alembic_config, revision="head") + + +@pytest.fixture(scope="session") +async def template_db_engine(postgres: PostgresContainer) -> AsyncGenerator[AsyncEngine, None]: + postgres_url = postgres.get_connection_url() + engine = create_async_engine(postgres_url) + async with engine.connect() as connection: + await connection.run_sync(run_migrations) + + # Connections have to be disposed to allow to use the database as a template + await engine.dispose() + yield engine + await engine.dispose() + + +@pytest.fixture() +async def test_postgres_url( + request: pytest.FixtureRequest, template_db_engine: AsyncEngine +) -> AsyncGenerator[URL, None]: + postgres_engine = create_async_engine(template_db_engine.url.set(database="postgres"), isolation_level="AUTOCOMMIT") + postgres_db_manager = PostgresDbManager(postgres_engine) + test_postgres_id = uuid.uuid4().hex + database_name = f"test-db-{test_postgres_id}" + + try: + empty_database = request.param.get("empty_database", False) + except AttributeError: + empty_database = False + + if empty_database: + await postgres_db_manager.create_database(database_name) + else: + template_name = cast(str, template_db_engine.url.database) + await postgres_db_manager.create_database(database_name, template=template_name) + + postgres_url = template_db_engine.url.set(database=database_name) + yield postgres_url + + await postgres_db_manager.drop_database(database_name) + await postgres_engine.dispose() + + +@pytest.fixture() +async def engine(test_postgres_url: str) -> AsyncGenerator[AsyncEngine, None]: + engine = create_async_engine(test_postgres_url) + yield engine + await engine.dispose() + + +@pytest.fixture() +async def session_factory(engine: AsyncEngine) -> async_sessionmaker[AsyncSession]: + return async_sessionmaker( + engine, + autoflush=False, + autocommit=False, + expire_on_commit=False, + class_=AsyncSession, + ) + + +@pytest.fixture() +async def session(session_factory: async_sessionmaker[AsyncSession]) -> AsyncGenerator[AsyncSession, None]: + async with session_factory() as session: + yield session diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py deleted file mode 100644 index c27c32f..0000000 --- a/tests/integration/conftest.py +++ /dev/null @@ -1,61 +0,0 @@ -import logging -import os -from collections.abc import AsyncGenerator, Generator - -import orjson -import pytest -import pytest_asyncio -from alembic.command import upgrade -from alembic.config import Config as AlembicConfig -from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine -from testcontainers.postgres import PostgresContainer - -logger = logging.getLogger(__name__) - - -@pytest.fixture(scope="session") -def postgres_url() -> Generator[str, None, None]: - postgres = PostgresContainer("postgres:17-alpine") - if os.name == "nt": # TODO: workaround from testcontainers/testcontainers-python#108 - postgres.get_container_host_ip = lambda: "localhost" - try: - postgres.start() - postgres_url_ = postgres.get_connection_url().replace("psycopg2", "asyncpg") - logger.info("postgres url %s", postgres_url_) - yield postgres_url_ - finally: - postgres.stop() - - -@pytest.fixture(scope="session") -def alembic_config(postgres_url: str) -> AlembicConfig: - alembic_cfg = AlembicConfig("alembic.ini") - alembic_cfg.set_main_option("sqlalchemy.url", postgres_url) - return alembic_cfg - - -@pytest.fixture(scope="session", autouse=True) -def _upgrade_schema_db(alembic_config: AlembicConfig) -> None: - upgrade(alembic_config, "head") - - -@pytest_asyncio.fixture -async def session_factory(postgres_url: str) -> AsyncGenerator[async_sessionmaker[AsyncSession], None]: - engine = create_async_engine( - url=postgres_url, - json_serializer=lambda data: orjson.dumps(data).decode(), - json_deserializer=orjson.loads, - ) - session_factory_: async_sessionmaker[AsyncSession] = async_sessionmaker( - bind=engine, - expire_on_commit=False, - autoflush=False, - ) - yield session_factory_ - await engine.dispose() - - -@pytest_asyncio.fixture -async def session(session_factory: async_sessionmaker[AsyncSession]) -> AsyncGenerator[AsyncSession, None]: - async with session_factory() as session_: - yield session_ diff --git a/tests/integration/test_stairway.py b/tests/integration/test_stairway.py index 763ddb9..aa2e218 100644 --- a/tests/integration/test_stairway.py +++ b/tests/integration/test_stairway.py @@ -11,6 +11,10 @@ from alembic.command import downgrade, upgrade from alembic.config import Config as AlembicConfig from alembic.script import Script, ScriptDirectory +from sqlalchemy import Connection +from sqlalchemy.ext.asyncio import AsyncSession + +from tests.fixtures.db import get_alembic_config def get_revisions(alembic_config: AlembicConfig) -> list[Script]: @@ -23,16 +27,18 @@ def get_revisions(alembic_config: AlembicConfig) -> list[Script]: return revisions -@pytest.fixture(scope="module", autouse=True) -def _drop_db(alembic_config: AlembicConfig) -> None: - downgrade(alembic_config, "base") - - -@pytest.mark.order("first") -def test_migrations_stairway(alembic_config: AlembicConfig) -> None: +def run_revisions(connection: Connection) -> None: + alembic_config = get_alembic_config() + alembic_config.attributes["connection"] = connection for revision in get_revisions(alembic_config): upgrade(alembic_config, revision.revision) # We need -1 for downgrading first migration (its down_revision is None) downgrade(alembic_config, revision.down_revision or "-1") upgrade(alembic_config, revision.revision) + + +@pytest.mark.order("first") +async def test_migrations_stairway(session: AsyncSession) -> None: + connection = await session.connection() + await connection.run_sync(run_revisions) From 2ad4654bf09eb50b11dd91dc913cb16d8c481823 Mon Sep 17 00:00:00 2001 From: SamWarden Date: Mon, 19 May 2025 16:50:18 +0300 Subject: [PATCH 18/20] refactor(app): use sync `main` to launch the api --- Dockerfile | 3 ++- pyproject.toml | 3 +++ src/user_service/__main__.py | 8 ++++++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index d96c70b..63c97a5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,7 @@ FROM python:3.13-slim-bookworm AS python-base ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 \ + PYTHONOPTIMIZE=1 \ PIP_NO_CACHE_DIR=1 \ PIP_DISABLE_PIP_VERSION_CHECK=1 \ PIP_DEFAULT_TIMEOUT=100 \ @@ -31,4 +32,4 @@ FROM python-base AS runner COPY --from=builder $VIRTUAL_ENV $VIRTUAL_ENV -CMD ["python", "-Om", "user_service"] +CMD ["user_service"] diff --git a/pyproject.toml b/pyproject.toml index 6ddbe94..f3c52d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,9 @@ lint = [ "pylint==3.3.*", ] +[project.scripts] +user_service = "user_service.__main__:main" + [project.urls] Homepage = "https://github.com/SamWarden/user_service" Repository = "https://github.com/SamWarden/user_service" diff --git a/src/user_service/__main__.py b/src/user_service/__main__.py index 9fb7926..4766d00 100644 --- a/src/user_service/__main__.py +++ b/src/user_service/__main__.py @@ -14,7 +14,7 @@ logger = logging.getLogger(__name__) -async def main() -> None: +async def async_main() -> None: config = load_config(Config) configure_logging(config.logging) @@ -40,5 +40,9 @@ async def main() -> None: await run_api(app, config.api) +def main() -> None: + asyncio.run(async_main()) + + if __name__ == "__main__": - asyncio.run(main()) + main() From 3802bb35419d8818b23aa4acd30e41ff3a58f42b Mon Sep 17 00:00:00 2001 From: SamWarden Date: Mon, 19 May 2025 17:05:20 +0300 Subject: [PATCH 19/20] fix(env): make `FULL_URL` default if file not exists --- src/user_service/infrastructure/db/migrations/env.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/user_service/infrastructure/db/migrations/env.py b/src/user_service/infrastructure/db/migrations/env.py index f715246..e8f65f0 100644 --- a/src/user_service/infrastructure/db/migrations/env.py +++ b/src/user_service/infrastructure/db/migrations/env.py @@ -20,7 +20,10 @@ fileConfig(config.config_file_name) if not (FULL_URL := config.get_main_option("sqlalchemy.url")): - FULL_URL = load_config(DBConfig, "db").full_url + try: + FULL_URL = load_config(DBConfig, "db").full_url + except FileNotFoundError: + FULL_URL = None TARGET_METADATA = BaseModel.metadata From 9122dbac2d1b4edbf93c573518ca9d74fcd40b0c Mon Sep 17 00:00:00 2001 From: SamWarden <56770986+SamWarden@users.noreply.github.com> Date: Wed, 4 Jun 2025 13:22:19 +0300 Subject: [PATCH 20/20] fix!(postgres): change storage dir for postgres --- docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index a060d4e..e532071 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -65,7 +65,7 @@ services: POSTGRES_USER: ${POSTGRES_USER:-$USER} POSTGRES_DB: ${POSTGRES_DB:-$USER} volumes: - - user_service.postgres.data:/var/lib/postgresql/users:rw + - user_service.postgres.data:/var/lib/postgresql/data:rw healthcheck: test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"] interval: 10s