From f7afe32e13c6d7c2e998d20c1eb89d30a987434d Mon Sep 17 00:00:00 2001 From: ivan-borovets <130386813+ivan-borovets@users.noreply.github.com> Date: Sat, 28 Feb 2026 14:23:25 +0300 Subject: [PATCH] [refactor] --- .dockerignore | 7 +- .github/workflows/ci.yaml | 35 +- .gitignore | 24 +- .pre-commit-config.yaml | 64 +- .yamlfmt | 2 +- Dockerfile | 41 + Makefile | 221 ++-- README.md | 781 +------------ alembic.ini | 81 +- config/dev/.gitkeep | 0 config/local/.env.local | 11 - config/local/.gitkeep | 0 config/local/.secrets.toml | 17 - config/local/Dockerfile | 25 - config/local/config.toml | 43 - config/local/docker-compose.yaml | 45 - config/local/export.toml | 13 - config/prod/.gitkeep | 0 config/toml_config_manager.py | 298 ----- docker-compose.test.yml | 8 + docker-compose.yml | 36 + docker-entrypoint.sh | 14 + docs/Robert_Martin_CA.png | Bin 376479 -> 0 bytes docs/application_controller_interactor.svg | 4 - docs/application_interactor.svg | 4 - docs/application_interactor_adapter.svg | 4 - docs/dep_graph_basic.svg | 4 - docs/dep_graph_inv_correct.svg | 4 - docs/dep_graph_inv_correct_di.svg | 4 - docs/dep_graph_inv_corrupted.svg | 4 - docs/domain_adapter.svg | 4 - .../application_controller_interactor.drawio | 40 - docs/draw.io/application_interactor.drawio | 53 - .../application_interactor_adapter.drawio | 84 -- docs/draw.io/dep_graph_basic.drawio | 43 - docs/draw.io/dep_graph_inv_correct.drawio | 38 - docs/draw.io/dep_graph_inv_correct_di.drawio | 75 -- docs/draw.io/dep_graph_inv_corrupted.drawio | 24 - docs/draw.io/domain_adapter.drawio | 37 - docs/draw.io/identity_provider.drawio | 70 -- .../infrastructure_controller_handler.drawio | 40 - docs/draw.io/infrastructure_handler.drawio | 61 - docs/draw.io/toml_config_manager.drawio | 73 -- docs/handlers.png | Bin 401394 -> 0 bytes docs/identity_provider.svg | 4 - docs/infrastructure_controller_handler.svg | 4 - docs/infrastructure_handler.svg | 4 - docs/onion_1.svg | 4 - docs/onion_2.svg | 4 - docs/toml_config_manager.svg | 4 - env.example | 21 + pyproject.toml | 309 ++--- scripts/dishka/plot_dependencies_data.py | 34 +- scripts/makefile/pycache_del.sh | 3 +- src/app/application/commands/activate_user.py | 93 -- src/app/application/commands/create_user.py | 98 -- .../application/commands/deactivate_user.py | 103 -- src/app/application/commands/grant_admin.py | 83 -- src/app/application/commands/revoke_admin.py | 81 -- .../application/commands/set_user_password.py | 99 -- .../common/exceptions/authorization.py | 5 - src/app/application/common/exceptions/base.py | 2 - .../application/common/exceptions/query.py | 9 - src/app/application/common/ports/flusher.py | 17 - .../common/ports/identity_provider.py | 10 - .../common/ports/user_command_gateway.py | 28 - .../common/ports/user_query_gateway.py | 32 - .../common/query_params/offset_pagination.py | 20 - .../services/authorization/authorize.py | 16 - .../application/common/services/constants.py | 6 - .../common/services/current_user.py | 43 - src/app/application/queries/list_users.py | 81 -- {config => src/app/config}/__init__.py | 0 src/app/config/loader.py | 82 ++ src/app/config/logging_.py | 20 + src/app/config/settings.py | 79 ++ src/app/{application => core}/__init__.py | 0 .../commands/__init__.py | 0 src/app/core/commands/activate_user.py | 82 ++ src/app/core/commands/create_user.py | 98 ++ src/app/core/commands/deactivate_user.py | 89 ++ src/app/core/commands/exceptions.py | 11 + src/app/core/commands/grant_admin.py | 82 ++ .../commands/ports}/__init__.py | 0 src/app/core/commands/ports/flusher.py | 10 + .../commands}/ports/transaction_manager.py | 6 +- .../core/commands/ports/user_tx_storage.py | 20 + src/app/core/commands/ports/utc_timer.py | 10 + src/app/core/commands/revoke_admin.py | 82 ++ src/app/core/commands/set_user_password.py | 85 ++ .../exceptions => core/common}/__init__.py | 0 .../common/authorization}/__init__.py | 0 .../core/common/authorization/authorize.py | 11 + .../common}/authorization/base.py | 2 +- .../common}/authorization/composite.py | 5 +- .../authorization/current_user_service.py | 33 + .../core/common/authorization/exceptions.py | 7 + .../common}/authorization/permissions.py | 31 +- src/app/core/common/authorization/ports.py | 10 + .../common}/authorization/role_hierarchy.py | 4 +- .../common/entities}/__init__.py | 0 .../{domain => core/common}/entities/base.py | 0 src/app/core/common/entities/types_.py | 16 + src/app/core/common/entities/user.py | 29 + src/app/core/common/exceptions.py | 25 + .../common/factories}/__init__.py | 0 src/app/core/common/factories/id_factory.py | 9 + .../common/ports}/__init__.py | 0 .../common/ports/access_revoker.py | 5 +- .../core/common/ports/identity_provider.py | 9 + src/app/core/common/ports/password_hasher.py | 13 + .../common/services}/__init__.py | 0 src/app/core/common/services/user.py | 105 ++ .../common/value_objects}/__init__.py | 0 .../common}/value_objects/base.py | 23 +- .../core/common/value_objects/raw_password.py | 21 + src/app/core/common/value_objects/username.py | 41 + .../core/common/value_objects/utc_datetime.py | 23 + .../entities => core/queries}/__init__.py | 0 src/app/core/queries/list_users.py | 71 ++ .../enums => core/queries/models}/__init__.py | 0 src/app/core/queries/models/user.py | 13 + .../queries/ports}/__init__.py | 0 src/app/core/queries/ports/user_reader.py | 23 + .../queries/query_support}/__init__.py | 0 .../core/queries/query_support/exceptions.py | 9 + .../query_support/offset_pagination.py | 26 + .../queries/query_support}/sorting.py | 0 src/app/domain/entities/user.py | 22 - src/app/domain/enums/user_role.py | 15 - src/app/domain/exceptions/base.py | 6 - src/app/domain/exceptions/user.py | 44 - src/app/domain/ports/password_hasher.py | 19 - src/app/domain/ports/user_id_generator.py | 8 - src/app/domain/services/user.py | 75 -- src/app/domain/value_objects/raw_password.py | 26 - src/app/domain/value_objects/user_id.py | 9 - .../value_objects/user_password_hash.py | 8 - src/app/domain/value_objects/username.py | 69 -- .../adapters/auth_session_access_revoker.py | 11 + .../auth_session_identity_provider.py | 11 + ...er_bcrypt.py => bcrypt_password_hasher.py} | 30 +- src/app/infrastructure/adapters/constants.py | 8 - src/app/infrastructure/adapters/exceptions.py | 5 + .../adapters/main_flusher_sqla.py | 43 - .../adapters/main_transaction_manager_sqla.py | 30 - .../infrastructure/adapters/sqla_flusher.py | 43 + .../adapters/sqla_transaction_manager.py | 26 + .../adapters/sqla_user_reader.py | 76 ++ .../adapters/sqla_user_tx_storage.py | 34 + .../adapters/system_utc_timer.py | 10 + src/app/infrastructure/adapters/types.py | 9 - .../adapters/user_data_mapper_sqla.py | 54 - .../adapters/user_id_generator_uuid.py | 9 - .../adapters/user_reader_sqla.py | 77 -- .../auth/adapters/access_revoker.py | 12 - .../auth/adapters/data_mapper_sqla.py | 61 - .../auth/adapters/identity_provider.py | 12 - .../auth/adapters/transaction_manager_sqla.py | 30 - src/app/infrastructure/auth/exceptions.py | 17 - .../auth/handlers/change_password.py | 75 -- .../infrastructure/auth/handlers/constants.py | 10 - .../infrastructure/auth/handlers/log_in.py | 95 -- .../infrastructure/auth/handlers/log_out.py | 38 - .../infrastructure/auth/handlers/sign_up.py | 90 -- .../auth/session/id_generator_str.py | 6 - .../auth/session/ports/gateway.py | 32 - .../auth/session/ports/transaction_manager.py | 21 - .../auth/session/ports/transport.py | 15 - .../infrastructure/auth/session/service.py | 242 ---- .../infrastructure/auth/session/timer_utc.py | 19 - .../auth_ctx}/__init__.py | 0 .../infrastructure/auth_ctx/cookie_manager.py | 22 + src/app/infrastructure/auth_ctx/exceptions.py | 19 + .../auth_ctx/handlers}/__init__.py | 0 .../auth_ctx/handlers/change_password.py | 61 + .../auth_ctx/handlers/log_in.py | 73 ++ .../auth_ctx/handlers/log_out.py | 29 + .../auth_ctx/handlers/sign_up.py | 78 ++ src/app/infrastructure/auth_ctx/id_factory.py | 7 + .../infrastructure/auth_ctx/jwt_processor.py | 33 + src/app/infrastructure/auth_ctx/jwt_types.py | 10 + .../{auth/session => auth_ctx}/model.py | 13 +- src/app/infrastructure/auth_ctx/service.py | 74 ++ .../auth_ctx/sqla_transaction_manager.py | 25 + .../auth_ctx/sqla_tx_storage.py | 54 + .../auth_ctx/sqla_user_tx_storage.py | 34 + .../adapters/types.py => auth_ctx/types_.py} | 0 src/app/infrastructure/auth_ctx/utc_timer.py | 29 + src/app/infrastructure/exceptions.py | 9 + src/app/infrastructure/exceptions/base.py | 2 - src/app/infrastructure/exceptions/gateway.py | 9 - .../exceptions/password_hasher.py | 5 - .../persistence_sqla/alembic/README | 2 +- .../persistence_sqla/alembic/env.py | 7 +- .../persistence_sqla/alembic/script.py.mako | 4 +- ...2025_06_11_2058-e325187c1eeb_users_auth.py | 52 - .../versions/2026-03-02_221518_users.py | 40 + .../2026-03-02_230628_auth_sessions.py | 38 + .../persistence_sqla/constraint_names.py | 3 + .../persistence_sqla/mappings/all.py | 7 +- .../persistence_sqla/mappings/auth_session.py | 19 +- .../persistence_sqla/mappings/user.py | 31 +- .../{infrastructure/auth => main}/__init__.py | 0 .../auth/adapters => main/ioc}/__init__.py | 0 src/app/main/ioc/core.py | 81 ++ src/app/main/ioc/infrastructure.py | 177 +++ src/app/main/ioc/provider_registry.py | 13 + src/app/main/run.py | 135 +++ src/app/main/setup.py | 36 + .../http/account}/__init__.py | 0 .../http/account/change_password.py | 62 + src/app/presentation/http/account/log_in.py | 44 + src/app/presentation/http/account/log_out.py | 36 + src/app/presentation/http/account/router.py | 15 + src/app/presentation/http/account/sign_up.py | 43 + src/app/presentation/http/api_v1_router.py | 11 + .../http/auth/access_token_processor_jwt.py | 69 -- .../adapters/session_transport_jwt_cookie.py | 61 - .../presentation/http/auth/asgi_middleware.py | 101 -- src/app/presentation/http/auth/constants.py | 18 - .../presentation/http/auth/cookie_params.py | 12 - .../presentation/http/auth/openapi_marker.py | 9 - .../http/auth_cookie_middleware.py | 58 + .../controllers/account/change_password.py | 68 -- .../http/controllers/account/log_in.py | 57 - .../http/controllers/account/log_out.py | 47 - .../http/controllers/account/router.py | 24 - .../http/controllers/account/sign_up.py | 64 -- .../http/controllers/api_v1_router.py | 13 - .../http/controllers/general/health.py | 16 - .../http/controllers/general/router.py | 11 - .../http/controllers/root_router.py | 19 - .../http/controllers/users/activate_user.py | 57 - .../http/controllers/users/create_user.py | 83 -- .../http/controllers/users/deactivate_user.py | 57 - .../http/controllers/users/grant_admin.py | 57 - .../http/controllers/users/list_users.py | 80 -- .../http/controllers/users/revoke_admin.py | 57 - .../http/controllers/users/router.py | 36 - .../controllers/users/set_user_password.py | 67 -- src/app/presentation/http/errors/callbacks.py | 8 +- src/app/presentation/http/errors/rules.py | 11 + .../presentation/http/errors/translators.py | 4 +- .../http/health}/__init__.py | 0 src/app/presentation/http/health/checks.py | 13 + src/app/presentation/http/health/router.py | 43 + src/app/presentation/http/root_router.py | 20 + .../http/users}/__init__.py | 0 .../presentation/http/users/activate_user.py | 42 + .../presentation/http/users/create_user.py | 46 + .../http/users/deactivate_user.py | 42 + .../presentation/http/users/grant_admin.py | 42 + src/app/presentation/http/users/list_users.py | 66 ++ .../presentation/http/users/revoke_admin.py | 42 + src/app/presentation/http/users/router.py | 26 + .../http/users/set_user_password.py | 63 + src/app/run.py | 36 - src/app/setup/app_factory.py | 48 - src/app/setup/config/database.py | 51 - src/app/setup/config/loader.py | 104 -- src/app/setup/config/logs.py | 42 - src/app/setup/config/security.py | 50 - src/app/setup/config/settings.py | 22 - src/app/setup/ioc/application.py | 66 -- src/app/setup/ioc/domain.py | 40 - src/app/setup/ioc/infrastructure.py | 182 --- src/app/setup/ioc/presentation.py | 28 - src/app/setup/ioc/provider_registry.py | 19 - src/app/setup/ioc/settings.py | 28 - .../app/integration/setup/test_cfg_loader.py | 5 - .../authz_service/test_permissions.py | 113 -- tests/app/unit/domain/entities/__init__.py | 0 tests/app/unit/domain/enums/__init__.py | 0 tests/app/unit/domain/enums/test_user_role.py | 27 - tests/app/unit/domain/services/__init__.py | 0 tests/app/unit/domain/services/conftest.py | 21 - tests/app/unit/domain/services/test_user.py | 217 ---- .../app/unit/domain/value_objects/__init__.py | 0 tests/app/unit/factories/__init__.py | 0 tests/app/unit/factories/named_entity.py | 42 - tests/app/unit/factories/settings_data.py | 63 - tests/app/unit/factories/tagged_entity.py | 23 - tests/app/unit/factories/user_entity.py | 26 - tests/app/unit/factories/value_objects.py | 44 - tests/app/unit/infrastructure/__init__.py | 0 tests/app/unit/setup/__init__.py | 0 tests/app/unit/setup/test_cfg_database.py | 60 - tests/app/unit/setup/test_cfg_loader.py | 163 --- tests/app/unit/setup/test_cfg_logs.py | 36 - tests/app/unit/setup/test_cfg_security.py | 36 - .../integration}/__init__.py | 0 .../integration/no_infra}/__init__.py | 0 .../integration/with_infra}/__init__.py | 0 tests/integration/with_infra/conftest.py | 95 ++ tests/integration/with_infra/test_stairway.py | 68 ++ .../performance}/__init__.py | 0 .../profile_bcrypt_password_hasher.py} | 10 +- .../account => tests/sanity}/__init__.py | 0 .../sanity/config}/__init__.py | 0 tests/sanity/config/test_loader.py | 5 + .../users => tests/smoke}/__init__.py | 0 {src/app/setup => tests/unit}/__init__.py | 0 .../setup => tests/unit}/config/__init__.py | 0 tests/unit/config/test_loader.py | 118 ++ tests/unit/config/test_settings.py | 18 + .../setup/ioc => tests/unit/core}/__init__.py | 0 tests/{app => unit/core/common}/__init__.py | 0 .../core/common/authorization}/__init__.py | 0 .../core/common/authorization/factories.py | 15 + .../common/authorization}/permission_stubs.py | 8 +- .../common/authorization}/test_authorize.py | 12 +- .../common/authorization}/test_composite.py | 11 +- .../common/authorization/test_permissions.py | 107 ++ .../core/common/entities}/__init__.py | 0 tests/unit/core/common/entities/factories.py | 42 + .../core/common}/entities/test_base.py | 8 +- tests/unit/core/common/entities/types_.py | 32 + .../core/common/services}/__init__.py | 0 tests/unit/core/common/services/conftest.py | 12 + tests/unit/core/common/services/factories.py | 88 ++ .../core/common}/services/mock_types.py | 6 +- tests/unit/core/common/services/stubs.py | 13 + tests/unit/core/common/services/test_stubs.py | 30 + tests/unit/core/common/services/test_user.py | 264 +++++ .../core/common/value_objects}/__init__.py | 0 .../core/common}/value_objects/test_base.py | 74 +- .../value_objects/test_raw_password.py | 6 +- .../common}/value_objects/test_username.py | 8 +- .../common/value_objects/test_utc_datetime.py | 33 + .../unit/core/common/value_objects/types_.py | 8 + .../core/queries}/__init__.py | 0 .../core/queries/query_support}/__init__.py | 0 .../query_support/test_offset_pagination.py | 37 + .../infrastructure}/__init__.py | 0 .../{app => }/unit/infrastructure/conftest.py | 11 +- .../test_bcrypt_password_hasher.py} | 6 +- uv.lock | 1010 +++++++++++------ 338 files changed, 5756 insertions(+), 7567 deletions(-) create mode 100644 Dockerfile delete mode 100644 config/dev/.gitkeep delete mode 100644 config/local/.env.local delete mode 100644 config/local/.gitkeep delete mode 100644 config/local/.secrets.toml delete mode 100644 config/local/Dockerfile delete mode 100644 config/local/config.toml delete mode 100644 config/local/docker-compose.yaml delete mode 100644 config/local/export.toml delete mode 100644 config/prod/.gitkeep delete mode 100644 config/toml_config_manager.py create mode 100644 docker-compose.test.yml create mode 100644 docker-compose.yml create mode 100755 docker-entrypoint.sh delete mode 100644 docs/Robert_Martin_CA.png delete mode 100644 docs/application_controller_interactor.svg delete mode 100644 docs/application_interactor.svg delete mode 100644 docs/application_interactor_adapter.svg delete mode 100644 docs/dep_graph_basic.svg delete mode 100644 docs/dep_graph_inv_correct.svg delete mode 100644 docs/dep_graph_inv_correct_di.svg delete mode 100644 docs/dep_graph_inv_corrupted.svg delete mode 100644 docs/domain_adapter.svg delete mode 100644 docs/draw.io/application_controller_interactor.drawio delete mode 100644 docs/draw.io/application_interactor.drawio delete mode 100644 docs/draw.io/application_interactor_adapter.drawio delete mode 100644 docs/draw.io/dep_graph_basic.drawio delete mode 100644 docs/draw.io/dep_graph_inv_correct.drawio delete mode 100644 docs/draw.io/dep_graph_inv_correct_di.drawio delete mode 100644 docs/draw.io/dep_graph_inv_corrupted.drawio delete mode 100644 docs/draw.io/domain_adapter.drawio delete mode 100644 docs/draw.io/identity_provider.drawio delete mode 100644 docs/draw.io/infrastructure_controller_handler.drawio delete mode 100644 docs/draw.io/infrastructure_handler.drawio delete mode 100644 docs/draw.io/toml_config_manager.drawio delete mode 100644 docs/handlers.png delete mode 100644 docs/identity_provider.svg delete mode 100644 docs/infrastructure_controller_handler.svg delete mode 100644 docs/infrastructure_handler.svg delete mode 100644 docs/onion_1.svg delete mode 100644 docs/onion_2.svg delete mode 100644 docs/toml_config_manager.svg create mode 100644 env.example delete mode 100644 src/app/application/commands/activate_user.py delete mode 100644 src/app/application/commands/create_user.py delete mode 100644 src/app/application/commands/deactivate_user.py delete mode 100644 src/app/application/commands/grant_admin.py delete mode 100644 src/app/application/commands/revoke_admin.py delete mode 100644 src/app/application/commands/set_user_password.py delete mode 100644 src/app/application/common/exceptions/authorization.py delete mode 100644 src/app/application/common/exceptions/base.py delete mode 100644 src/app/application/common/exceptions/query.py delete mode 100644 src/app/application/common/ports/flusher.py delete mode 100644 src/app/application/common/ports/identity_provider.py delete mode 100644 src/app/application/common/ports/user_command_gateway.py delete mode 100644 src/app/application/common/ports/user_query_gateway.py delete mode 100644 src/app/application/common/query_params/offset_pagination.py delete mode 100644 src/app/application/common/services/authorization/authorize.py delete mode 100644 src/app/application/common/services/constants.py delete mode 100644 src/app/application/common/services/current_user.py delete mode 100644 src/app/application/queries/list_users.py rename {config => src/app/config}/__init__.py (100%) create mode 100644 src/app/config/loader.py create mode 100644 src/app/config/logging_.py create mode 100644 src/app/config/settings.py rename src/app/{application => core}/__init__.py (100%) rename src/app/{application => core}/commands/__init__.py (100%) create mode 100644 src/app/core/commands/activate_user.py create mode 100644 src/app/core/commands/create_user.py create mode 100644 src/app/core/commands/deactivate_user.py create mode 100644 src/app/core/commands/exceptions.py create mode 100644 src/app/core/commands/grant_admin.py rename src/app/{application/common => core/commands/ports}/__init__.py (100%) create mode 100644 src/app/core/commands/ports/flusher.py rename src/app/{application/common => core/commands}/ports/transaction_manager.py (73%) create mode 100644 src/app/core/commands/ports/user_tx_storage.py create mode 100644 src/app/core/commands/ports/utc_timer.py create mode 100644 src/app/core/commands/revoke_admin.py create mode 100644 src/app/core/commands/set_user_password.py rename src/app/{application/common/exceptions => core/common}/__init__.py (100%) rename src/app/{application/common/ports => core/common/authorization}/__init__.py (100%) create mode 100644 src/app/core/common/authorization/authorize.py rename src/app/{application/common/services => core/common}/authorization/base.py (86%) rename src/app/{application/common/services => core/common}/authorization/composite.py (72%) create mode 100644 src/app/core/common/authorization/current_user_service.py create mode 100644 src/app/core/common/authorization/exceptions.py rename src/app/{application/common/services => core/common}/authorization/permissions.py (62%) create mode 100644 src/app/core/common/authorization/ports.py rename src/app/{application/common/services => core/common}/authorization/role_hierarchy.py (62%) rename src/app/{application/common/query_params => core/common/entities}/__init__.py (100%) rename src/app/{domain => core/common}/entities/base.py (100%) create mode 100644 src/app/core/common/entities/types_.py create mode 100644 src/app/core/common/entities/user.py create mode 100644 src/app/core/common/exceptions.py rename src/app/{application/common/services => core/common/factories}/__init__.py (100%) create mode 100644 src/app/core/common/factories/id_factory.py rename src/app/{application/common/services/authorization => core/common/ports}/__init__.py (100%) rename src/app/{application => core}/common/ports/access_revoker.py (64%) create mode 100644 src/app/core/common/ports/identity_provider.py create mode 100644 src/app/core/common/ports/password_hasher.py rename src/app/{application/queries => core/common/services}/__init__.py (100%) create mode 100644 src/app/core/common/services/user.py rename src/app/{domain => core/common/value_objects}/__init__.py (100%) rename src/app/{domain => core/common}/value_objects/base.py (68%) create mode 100644 src/app/core/common/value_objects/raw_password.py create mode 100644 src/app/core/common/value_objects/username.py create mode 100644 src/app/core/common/value_objects/utc_datetime.py rename src/app/{domain/entities => core/queries}/__init__.py (100%) create mode 100644 src/app/core/queries/list_users.py rename src/app/{domain/enums => core/queries/models}/__init__.py (100%) create mode 100644 src/app/core/queries/models/user.py rename src/app/{domain/exceptions => core/queries/ports}/__init__.py (100%) create mode 100644 src/app/core/queries/ports/user_reader.py rename src/app/{domain/ports => core/queries/query_support}/__init__.py (100%) create mode 100644 src/app/core/queries/query_support/exceptions.py create mode 100644 src/app/core/queries/query_support/offset_pagination.py rename src/app/{application/common/query_params => core/queries/query_support}/sorting.py (100%) delete mode 100644 src/app/domain/entities/user.py delete mode 100644 src/app/domain/enums/user_role.py delete mode 100644 src/app/domain/exceptions/base.py delete mode 100644 src/app/domain/exceptions/user.py delete mode 100644 src/app/domain/ports/password_hasher.py delete mode 100644 src/app/domain/ports/user_id_generator.py delete mode 100644 src/app/domain/services/user.py delete mode 100644 src/app/domain/value_objects/raw_password.py delete mode 100644 src/app/domain/value_objects/user_id.py delete mode 100644 src/app/domain/value_objects/user_password_hash.py delete mode 100644 src/app/domain/value_objects/username.py create mode 100644 src/app/infrastructure/adapters/auth_session_access_revoker.py create mode 100644 src/app/infrastructure/adapters/auth_session_identity_provider.py rename src/app/infrastructure/adapters/{password_hasher_bcrypt.py => bcrypt_password_hasher.py} (76%) delete mode 100644 src/app/infrastructure/adapters/constants.py create mode 100644 src/app/infrastructure/adapters/exceptions.py delete mode 100644 src/app/infrastructure/adapters/main_flusher_sqla.py delete mode 100644 src/app/infrastructure/adapters/main_transaction_manager_sqla.py create mode 100644 src/app/infrastructure/adapters/sqla_flusher.py create mode 100644 src/app/infrastructure/adapters/sqla_transaction_manager.py create mode 100644 src/app/infrastructure/adapters/sqla_user_reader.py create mode 100644 src/app/infrastructure/adapters/sqla_user_tx_storage.py create mode 100644 src/app/infrastructure/adapters/system_utc_timer.py delete mode 100644 src/app/infrastructure/adapters/types.py delete mode 100644 src/app/infrastructure/adapters/user_data_mapper_sqla.py delete mode 100644 src/app/infrastructure/adapters/user_id_generator_uuid.py delete mode 100644 src/app/infrastructure/adapters/user_reader_sqla.py delete mode 100644 src/app/infrastructure/auth/adapters/access_revoker.py delete mode 100644 src/app/infrastructure/auth/adapters/data_mapper_sqla.py delete mode 100644 src/app/infrastructure/auth/adapters/identity_provider.py delete mode 100644 src/app/infrastructure/auth/adapters/transaction_manager_sqla.py delete mode 100644 src/app/infrastructure/auth/exceptions.py delete mode 100644 src/app/infrastructure/auth/handlers/change_password.py delete mode 100644 src/app/infrastructure/auth/handlers/constants.py delete mode 100644 src/app/infrastructure/auth/handlers/log_in.py delete mode 100644 src/app/infrastructure/auth/handlers/log_out.py delete mode 100644 src/app/infrastructure/auth/handlers/sign_up.py delete mode 100644 src/app/infrastructure/auth/session/id_generator_str.py delete mode 100644 src/app/infrastructure/auth/session/ports/gateway.py delete mode 100644 src/app/infrastructure/auth/session/ports/transaction_manager.py delete mode 100644 src/app/infrastructure/auth/session/ports/transport.py delete mode 100644 src/app/infrastructure/auth/session/service.py delete mode 100644 src/app/infrastructure/auth/session/timer_utc.py rename src/app/{domain/services => infrastructure/auth_ctx}/__init__.py (100%) create mode 100644 src/app/infrastructure/auth_ctx/cookie_manager.py create mode 100644 src/app/infrastructure/auth_ctx/exceptions.py rename src/app/{domain/value_objects => infrastructure/auth_ctx/handlers}/__init__.py (100%) create mode 100644 src/app/infrastructure/auth_ctx/handlers/change_password.py create mode 100644 src/app/infrastructure/auth_ctx/handlers/log_in.py create mode 100644 src/app/infrastructure/auth_ctx/handlers/log_out.py create mode 100644 src/app/infrastructure/auth_ctx/handlers/sign_up.py create mode 100644 src/app/infrastructure/auth_ctx/id_factory.py create mode 100644 src/app/infrastructure/auth_ctx/jwt_processor.py create mode 100644 src/app/infrastructure/auth_ctx/jwt_types.py rename src/app/infrastructure/{auth/session => auth_ctx}/model.py (56%) create mode 100644 src/app/infrastructure/auth_ctx/service.py create mode 100644 src/app/infrastructure/auth_ctx/sqla_transaction_manager.py create mode 100644 src/app/infrastructure/auth_ctx/sqla_tx_storage.py create mode 100644 src/app/infrastructure/auth_ctx/sqla_user_tx_storage.py rename src/app/infrastructure/{auth/adapters/types.py => auth_ctx/types_.py} (100%) create mode 100644 src/app/infrastructure/auth_ctx/utc_timer.py create mode 100644 src/app/infrastructure/exceptions.py delete mode 100644 src/app/infrastructure/exceptions/base.py delete mode 100644 src/app/infrastructure/exceptions/gateway.py delete mode 100644 src/app/infrastructure/exceptions/password_hasher.py delete mode 100644 src/app/infrastructure/persistence_sqla/alembic/versions/2025_06_11_2058-e325187c1eeb_users_auth.py create mode 100644 src/app/infrastructure/persistence_sqla/alembic/versions/2026-03-02_221518_users.py create mode 100644 src/app/infrastructure/persistence_sqla/alembic/versions/2026-03-02_230628_auth_sessions.py create mode 100644 src/app/infrastructure/persistence_sqla/constraint_names.py rename src/app/{infrastructure/auth => main}/__init__.py (100%) rename src/app/{infrastructure/auth/adapters => main/ioc}/__init__.py (100%) create mode 100644 src/app/main/ioc/core.py create mode 100644 src/app/main/ioc/infrastructure.py create mode 100644 src/app/main/ioc/provider_registry.py create mode 100644 src/app/main/run.py create mode 100644 src/app/main/setup.py rename src/app/{infrastructure/auth/handlers => presentation/http/account}/__init__.py (100%) create mode 100644 src/app/presentation/http/account/change_password.py create mode 100644 src/app/presentation/http/account/log_in.py create mode 100644 src/app/presentation/http/account/log_out.py create mode 100644 src/app/presentation/http/account/router.py create mode 100644 src/app/presentation/http/account/sign_up.py create mode 100644 src/app/presentation/http/api_v1_router.py delete mode 100644 src/app/presentation/http/auth/access_token_processor_jwt.py delete mode 100644 src/app/presentation/http/auth/adapters/session_transport_jwt_cookie.py delete mode 100644 src/app/presentation/http/auth/asgi_middleware.py delete mode 100644 src/app/presentation/http/auth/constants.py delete mode 100644 src/app/presentation/http/auth/cookie_params.py delete mode 100644 src/app/presentation/http/auth/openapi_marker.py create mode 100644 src/app/presentation/http/auth_cookie_middleware.py delete mode 100644 src/app/presentation/http/controllers/account/change_password.py delete mode 100644 src/app/presentation/http/controllers/account/log_in.py delete mode 100644 src/app/presentation/http/controllers/account/log_out.py delete mode 100644 src/app/presentation/http/controllers/account/router.py delete mode 100644 src/app/presentation/http/controllers/account/sign_up.py delete mode 100644 src/app/presentation/http/controllers/api_v1_router.py delete mode 100644 src/app/presentation/http/controllers/general/health.py delete mode 100644 src/app/presentation/http/controllers/general/router.py delete mode 100644 src/app/presentation/http/controllers/root_router.py delete mode 100644 src/app/presentation/http/controllers/users/activate_user.py delete mode 100644 src/app/presentation/http/controllers/users/create_user.py delete mode 100644 src/app/presentation/http/controllers/users/deactivate_user.py delete mode 100644 src/app/presentation/http/controllers/users/grant_admin.py delete mode 100644 src/app/presentation/http/controllers/users/list_users.py delete mode 100644 src/app/presentation/http/controllers/users/revoke_admin.py delete mode 100644 src/app/presentation/http/controllers/users/router.py delete mode 100644 src/app/presentation/http/controllers/users/set_user_password.py create mode 100644 src/app/presentation/http/errors/rules.py rename src/app/{infrastructure/auth/session => presentation/http/health}/__init__.py (100%) create mode 100644 src/app/presentation/http/health/checks.py create mode 100644 src/app/presentation/http/health/router.py create mode 100644 src/app/presentation/http/root_router.py rename src/app/{infrastructure/auth/session/ports => presentation/http/users}/__init__.py (100%) create mode 100644 src/app/presentation/http/users/activate_user.py create mode 100644 src/app/presentation/http/users/create_user.py create mode 100644 src/app/presentation/http/users/deactivate_user.py create mode 100644 src/app/presentation/http/users/grant_admin.py create mode 100644 src/app/presentation/http/users/list_users.py create mode 100644 src/app/presentation/http/users/revoke_admin.py create mode 100644 src/app/presentation/http/users/router.py create mode 100644 src/app/presentation/http/users/set_user_password.py delete mode 100644 src/app/run.py delete mode 100644 src/app/setup/app_factory.py delete mode 100644 src/app/setup/config/database.py delete mode 100644 src/app/setup/config/loader.py delete mode 100644 src/app/setup/config/logs.py delete mode 100644 src/app/setup/config/security.py delete mode 100644 src/app/setup/config/settings.py delete mode 100644 src/app/setup/ioc/application.py delete mode 100644 src/app/setup/ioc/domain.py delete mode 100644 src/app/setup/ioc/infrastructure.py delete mode 100644 src/app/setup/ioc/presentation.py delete mode 100644 src/app/setup/ioc/provider_registry.py delete mode 100644 src/app/setup/ioc/settings.py delete mode 100644 tests/app/integration/setup/test_cfg_loader.py delete mode 100644 tests/app/unit/application/authz_service/test_permissions.py delete mode 100644 tests/app/unit/domain/entities/__init__.py delete mode 100644 tests/app/unit/domain/enums/__init__.py delete mode 100644 tests/app/unit/domain/enums/test_user_role.py delete mode 100644 tests/app/unit/domain/services/__init__.py delete mode 100644 tests/app/unit/domain/services/conftest.py delete mode 100644 tests/app/unit/domain/services/test_user.py delete mode 100644 tests/app/unit/domain/value_objects/__init__.py delete mode 100644 tests/app/unit/factories/__init__.py delete mode 100644 tests/app/unit/factories/named_entity.py delete mode 100644 tests/app/unit/factories/settings_data.py delete mode 100644 tests/app/unit/factories/tagged_entity.py delete mode 100644 tests/app/unit/factories/user_entity.py delete mode 100644 tests/app/unit/factories/value_objects.py delete mode 100644 tests/app/unit/infrastructure/__init__.py delete mode 100644 tests/app/unit/setup/__init__.py delete mode 100644 tests/app/unit/setup/test_cfg_database.py delete mode 100644 tests/app/unit/setup/test_cfg_loader.py delete mode 100644 tests/app/unit/setup/test_cfg_logs.py delete mode 100644 tests/app/unit/setup/test_cfg_security.py rename {src/app/infrastructure/exceptions => tests/integration}/__init__.py (100%) rename {src/app/presentation/http/auth => tests/integration/no_infra}/__init__.py (100%) rename {src/app/presentation/http/auth/adapters => tests/integration/with_infra}/__init__.py (100%) create mode 100644 tests/integration/with_infra/conftest.py create mode 100644 tests/integration/with_infra/test_stairway.py rename {src/app/presentation/http/controllers => tests/performance}/__init__.py (100%) rename tests/{app/performance/profile_password_hasher_bcrypt.py => performance/profile_bcrypt_password_hasher.py} (72%) rename {src/app/presentation/http/controllers/account => tests/sanity}/__init__.py (100%) rename {src/app/presentation/http/controllers/general => tests/sanity/config}/__init__.py (100%) create mode 100644 tests/sanity/config/test_loader.py rename {src/app/presentation/http/controllers/users => tests/smoke}/__init__.py (100%) rename {src/app/setup => tests/unit}/__init__.py (100%) rename {src/app/setup => tests/unit}/config/__init__.py (100%) create mode 100644 tests/unit/config/test_loader.py create mode 100644 tests/unit/config/test_settings.py rename {src/app/setup/ioc => tests/unit/core}/__init__.py (100%) rename tests/{app => unit/core/common}/__init__.py (100%) rename tests/{app/integration => unit/core/common/authorization}/__init__.py (100%) create mode 100644 tests/unit/core/common/authorization/factories.py rename tests/{app/unit/application/authz_service => unit/core/common/authorization}/permission_stubs.py (67%) rename tests/{app/unit/application/authz_service => unit/core/common/authorization}/test_authorize.py (58%) rename tests/{app/unit/application/authz_service => unit/core/common/authorization}/test_composite.py (68%) create mode 100644 tests/unit/core/common/authorization/test_permissions.py rename tests/{app/integration/setup => unit/core/common/entities}/__init__.py (100%) create mode 100644 tests/unit/core/common/entities/factories.py rename tests/{app/unit/domain => unit/core/common}/entities/test_base.py (91%) create mode 100644 tests/unit/core/common/entities/types_.py rename tests/{app/performance => unit/core/common/services}/__init__.py (100%) create mode 100644 tests/unit/core/common/services/conftest.py create mode 100644 tests/unit/core/common/services/factories.py rename tests/{app/unit/domain => unit/core/common}/services/mock_types.py (51%) create mode 100644 tests/unit/core/common/services/stubs.py create mode 100644 tests/unit/core/common/services/test_stubs.py create mode 100644 tests/unit/core/common/services/test_user.py rename tests/{app/unit => unit/core/common/value_objects}/__init__.py (100%) rename tests/{app/unit/domain => unit/core/common}/value_objects/test_base.py (60%) rename tests/{app/unit/domain => unit/core/common}/value_objects/test_raw_password.py (60%) rename tests/{app/unit/domain => unit/core/common}/value_objects/test_username.py (86%) create mode 100644 tests/unit/core/common/value_objects/test_utc_datetime.py create mode 100644 tests/unit/core/common/value_objects/types_.py rename tests/{app/unit/application => unit/core/queries}/__init__.py (100%) rename tests/{app/unit/application/authz_service => unit/core/queries/query_support}/__init__.py (100%) create mode 100644 tests/unit/core/queries/query_support/test_offset_pagination.py rename tests/{app/unit/domain => unit/infrastructure}/__init__.py (100%) rename tests/{app => }/unit/infrastructure/conftest.py (78%) rename tests/{app/unit/infrastructure/test_password_hasher_bcrypt.py => unit/infrastructure/test_bcrypt_password_hasher.py} (92%) diff --git a/.dockerignore b/.dockerignore index 78b7c1de..71d3a94a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,9 +2,10 @@ ** # Except +!docker-entrypoint.sh !pyproject.toml -!uv.lock !README.md -!config/** -!src/** +!uv.lock !alembic.ini +!src/** +!tests/** diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 37f9aa54..be82dffc 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -7,43 +7,24 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v6 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.13" - name: Install uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v7 - name: Install dependencies run: uv sync --locked --group dev - name: Check code - run: uv run make code.check + run: uv run make check - - name: Test Docker Compose setup + - name: Test with Docker env: - APP_ENV: local - run: | - uv run config/toml_config_manager.py - cd config/local - docker compose --env-file .env.local up -d --build - - - name: Verify Application Health - run: | - timeout 10s bash -s <<'BASH' - while ! curl -sf http://127.0.0.1:9999/api/v1/health; do - sleep 1 - done - BASH - - - name: Test Signup Handler - run: | - curl -f --json @- http://127.0.0.1:9999/api/v1/account/signup <<'JSON' - { - "username": "string", - "password": "string" - } - JSON + ALLOW_DESTRUCTIVE_TEST_CLEANUP: 1 + run: make test-docker diff --git a/.gitignore b/.gitignore index c2da5faa..3726ba47 100644 --- a/.gitignore +++ b/.gitignore @@ -159,18 +159,16 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. .idea/ -# Config -config/local/* -!config/local/.gitkeep -config/dev/* -!config/dev/.gitkeep -config/prod/* -!config/prod/.gitkeep -.secrets.* -.env.* - -# IgnoreToDo +# other +.claude/ +.import_linter_cache/ +.ruff_cache/ +.vscode/ +htmlcov-docker/ todo/ -# ImportLinter -.import_linter_cache/ \ No newline at end of file +.constraints.in +.secrets +AGENTS.md +CLAUDE.md +pylock.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 175d857c..d9a97c98 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,14 +1,70 @@ +default_language_version: + python: python3.13 + +default_stages: [pre-commit] + repos: - repo: local hooks: - - id: make-check - name: source-code-check - entry: make code.check + - id: code-check + name: code-check (local) + entry: uv run make check + language: system + pass_filenames: false + - id: pip-audit-local + name: pip-audit (local) + entry: uv run make pip-audit + language: system + pass_filenames: false + verbose: true + - id: test-docker + name: test-docker (local) + entry: make test-docker language: system + stages: [pre-push] pass_filenames: false - always_run: true + verbose: true + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: check-ast + - id: check-case-conflict + - id: trailing-whitespace + - id: end-of-file-fixer + exclude: ^docs/ + - id: check-added-large-files + - id: check-docstring-first + - id: check-json + - id: check-toml + - id: check-yaml + exclude: docker-compose.test.yml + - id: detect-private-key + - id: debug-statements + - id: check-merge-conflict + - id: mixed-line-ending + args: ["--fix=lf"] + - id: no-commit-to-branch + args: [--branch, develop, --branch, dev, --branch, master, --branch, main] + + - repo: https://github.com/crate-ci/typos + rev: v1.40.0 + hooks: + - id: typos + args: [--force-exclude] - repo: https://github.com/google/yamlfmt rev: v0.20.0 hooks: - id: yamlfmt + name: YAML formatter + files: (^|/).*\.ya?ml$ + args: + - "--conf" + - ".yamlfmt" + + - repo: https://github.com/koalaman/shellcheck-precommit + rev: v0.11.0 + hooks: + - id: shellcheck + args: ["--severity=warning"] diff --git a/.yamlfmt b/.yamlfmt index 9a6db598..3620e44b 100644 --- a/.yamlfmt +++ b/.yamlfmt @@ -4,4 +4,4 @@ formatter: type: basic line_ending: lf retain_line_breaks: true - scan_folded_as_literal: true \ No newline at end of file + scan_folded_as_literal: true diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..a47ded36 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,41 @@ +ARG PYTHON_VERSION=3.13 +FROM ghcr.io/astral-sh/uv:python${PYTHON_VERSION}-trixie-slim + +ARG APP_VERSION=develop +ARG ENVIRONMENT="prod" + +ENV APP_VERSION=${APP_VERSION} +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 +ENV UV_HTTP_TIMEOUT=300 +ENV UV_LINK_MODE=copy +ENV UV_PROJECT_ENVIRONMENT=/usr/local + +WORKDIR /code + +COPY pyproject.toml uv.lock README.md ./ + +RUN if [ "${ENVIRONMENT}" = "prod" ]; then \ + uv sync --frozen --no-cache --no-dev --no-install-project; \ + else \ + uv sync --frozen --dev --no-install-project; \ + fi + +COPY . . + +RUN if [ "${ENVIRONMENT}" = "prod" ]; then \ + uv sync --frozen --no-cache --no-dev; \ + else \ + uv sync --frozen --dev; \ + fi + +RUN groupadd -r runner +RUN useradd -r -g runner -m -s /usr/sbin/nologin runner +RUN chown -R runner:root /code +RUN chmod -R g=u /code + +USER runner + +EXPOSE 8000 + +ENTRYPOINT [ "/code/docker-entrypoint.sh" ] diff --git a/Makefile b/Makefile index d099c00a..20fffccb 100644 --- a/Makefile +++ b/Makefile @@ -1,100 +1,169 @@ +# Shell / Make config +SHELL := bash +.SHELLFLAGS := -eu -o pipefail -c + +.SILENT: +MAKEFLAGS += --no-print-directory + +# ----------------------------- +# User-configurable variables (edit this) +# ----------------------------- +PROJECT_NAME ?= $(notdir $(abspath .)) +INFRA_SERVICES ?= db_pg + +# ----------------------------- +# Internal vars / aliases +# ----------------------------- +PYTHON_BIN := python +DOCKER_COMPOSE := docker compose -p $(PROJECT_NAME) +DOCKER_COMPOSE_PRUNE := scripts/makefile/docker_prune.sh + +# Test stack is isolated by project name +TEST_PROJECT ?= $(PROJECT_NAME)-test +DC_TEST_DOCKER := docker compose \ + -p $(TEST_PROJECT) \ + -f docker-compose.yml \ + -f docker-compose.test.yml +TEST_RUNNER := $(TEST_PROJECT)-runner + +# Pytest paths +PYTEST_PATHS_LIGHT := \ + tests/sanity \ + tests/unit \ + tests/integration/no_infra +PYTEST_PATHS_ALL := \ + $(PYTEST_PATHS_LIGHT) \ + tests/smoke \ + tests/integration/with_infra + +# Pytest args +PYTEST_ARGS_VERBOSE := -s -vv +PYTEST_ARGS_COV := \ + --cov=src \ + --cov-report=term-missing \ + --cov-report=html +PYTEST_ARGS_COV_DOCKER := \ + --cov=src \ + --cov-report=term-missing + +# Safety +.PHONY: pip-audit +pip-audit: + tmp=$$(mktemp -d); trap 'rm -rf "$$tmp"' EXIT; \ + uv -qq export --format pylock.toml -o "$$tmp/pylock.toml"; \ + pip-audit --locked "$$tmp" \ + || echo "WARNING: pip-audit found vulnerabilities (non-blocking)" >&2 + # Code quality -.PHONY: code.format code.lint code.test code.cov code.cov.html code.check -code.format: +.PHONY: slotscheck lint test check +slotscheck: + slotscheck $(SLOTSCHECK_TARGET) 2>&1 | tee /dev/stderr \ + | { grep -m1 "Failed to import" || true; } | cut -d"'" -f2 \ + | xargs -r -n1 $(PYTHON_BIN) -c 'import importlib,sys; importlib.import_module(sys.argv[1])' + +lint: + ruff check --fix ruff format - -code.lint: code.format + tombi format + tombi lint deptry - slotscheck src + $(MAKE) slotscheck SLOTSCHECK_TARGET=src lint-imports - ruff check --exit-non-zero-on-fix mypy -code.test: - pytest -v - -code.cov: - coverage run -m pytest - coverage combine - coverage report +test: + pytest -v \ + $(PYTEST_PATHS_LIGHT) \ + $(PYTEST_ARGS_COV) -code.cov.html: - coverage run -m pytest - coverage combine +check: lint test coverage html -code.check: code.lint code.test - -# Environment -PYTHON := python -CONFIGS_DIG := config -TOML_CONFIG_MANAGER := $(CONFIGS_DIG)/toml_config_manager.py - -.PHONY: guard-APP_ENV -guard-APP_ENV: - @if [ -z "$$APP_ENV" ]; then \ - echo "APP_ENV is not set. Set APP_ENV before running this command."; \ - exit 1; \ - fi - -.PHONY: env dotenv -env: - @echo APP_ENV=$(APP_ENV) - -dotenv: guard-APP_ENV - @$(PYTHON) $(TOML_CONFIG_MANAGER) $(APP_ENV) - # Docker compose -DOCKER_COMPOSE := docker compose -DOCKER_COMPOSE_PRUNE := scripts/makefile/docker_prune.sh - -.PHONY: up.db up.db-echo up up.echo down down.total logs.db shell.db prune -up.db: guard-APP_ENV - @echo "APP_ENV=$(APP_ENV)" - @cd $(CONFIGS_DIG)/$(APP_ENV) && $(DOCKER_COMPOSE) --env-file .env.$(APP_ENV) up -d web_app_db_pg - -up.db-echo: guard-APP_ENV - @echo "APP_ENV=$(APP_ENV)" - @cd $(CONFIGS_DIG)/$(APP_ENV) && $(DOCKER_COMPOSE) --env-file .env.$(APP_ENV) up web_app_db_pg - -up: guard-APP_ENV - @echo "APP_ENV=$(APP_ENV)" - @cd $(CONFIGS_DIG)/$(APP_ENV) && $(DOCKER_COMPOSE) --env-file .env.$(APP_ENV) up -d --build - -up.echo: guard-APP_ENV - @echo "APP_ENV=$(APP_ENV)" - @cd $(CONFIGS_DIG)/$(APP_ENV) && $(DOCKER_COMPOSE) --env-file .env.$(APP_ENV) up --build - -down.db: guard-APP_ENV - @cd $(CONFIGS_DIG)/$(APP_ENV) && $(DOCKER_COMPOSE) --env-file .env.$(APP_ENV) down web_app_db_pg - -down: guard-APP_ENV - @cd $(CONFIGS_DIG)/$(APP_ENV) && $(DOCKER_COMPOSE) --env-file .env.$(APP_ENV) down - -down.total: guard-APP_ENV - @cd $(CONFIGS_DIG)/$(APP_ENV) && $(DOCKER_COMPOSE) --env-file .env.$(APP_ENV) down -v - -logs.db: guard-APP_ENV - @cd $(CONFIGS_DIG)/$(APP_ENV) && $(DOCKER_COMPOSE) --env-file .env.$(APP_ENV) logs -f web_app_db_pg - -shell.db: guard-APP_ENV - @cd $(CONFIGS_DIG)/$(APP_ENV) && $(DOCKER_COMPOSE) --env-file .env.$(APP_ENV) exec web_app_db_pg sh - +.PHONY: docker-env local-env upd up upd-local up-local down stop-all +docker-env: + { \ + echo "# This .env file is generated automatically for DOCKER environment by Makefile."; \ + echo "# Do not edit it directly; edit env.example / .secrets and Makefile instead."; \ + echo; \ + cat env.example; \ + if [ -f .secrets ]; then \ + echo; \ + echo "# --- secrets from .secrets (not committed) ---"; \ + cat .secrets; \ + fi; \ + } > .env + +local-env: + { \ + echo "# This .env file is generated automatically for LOCAL environment by Makefile."; \ + echo "# Do not edit it directly; edit env.example / .secrets and Makefile instead."; \ + echo; \ + sed \ + -e 's|^EXAMPLE_SERVICE_URL=.*|EXAMPLE_SERVICE_URL=http://127.0.0.1:51999|' \ + -e 's|^POSTGRES_HOST=.*|POSTGRES_HOST=127.0.0.1|' \ + env.example; \ + if [ -f .secrets ]; then \ + echo; \ + echo "# --- secrets from .secrets (not committed) ---"; \ + cat .secrets; \ + fi; \ + } > .env + +upd: docker-env + $(DOCKER_COMPOSE) up -d --build --force-recreate + +up: docker-env + $(DOCKER_COMPOSE) up --build --force-recreate + +upd-local: local-env + $(DOCKER_COMPOSE) up -d --build --force-recreate $(INFRA_SERVICES) + +up-local: local-env + $(DOCKER_COMPOSE) up --build --force-recreate $(INFRA_SERVICES) + +down: + $(DOCKER_COMPOSE) down + +stop-all: + docker ps -q | xargs -r docker stop + +# Tests (with infra) +.PHONY: test-docker +test-docker: docker-env + rc=0; \ + $(DC_TEST_DOCKER) down -v --remove-orphans >/dev/null 2>&1 || true; \ + if [ -n "$(strip $(INFRA_SERVICES))" ]; then \ + $(DC_TEST_DOCKER) up -d --build --wait --wait-timeout 180 $(INFRA_SERVICES); \ + else \ + echo "INFRA_SERVICES is empty, skipping infra startup"; \ + fi; \ + $(DC_TEST_DOCKER) run --build --name $(TEST_RUNNER) app \ + pytest $(PYTEST_ARGS_VERBOSE) \ + $(PYTEST_PATHS_ALL) \ + $(PYTEST_ARGS_COV_DOCKER) \ + || rc=$$?; \ + docker cp $(TEST_RUNNER):/tmp/.coverage ./.coverage.docker 2>/dev/null || true; \ + docker rm $(TEST_RUNNER) >/dev/null 2>&1 || true; \ + $(DC_TEST_DOCKER) down -v --remove-orphans; \ + coverage html --data-file=.coverage.docker -d htmlcov-docker && \ + echo "Coverage HTML report: htmlcov-docker/index.html" || true; \ + exit $$rc + +.PHONY: prune prune: $(DOCKER_COMPOSE_PRUNE) # Project structure visualization +.PHONY: pycache-del tree plot-data PYCACHE_DEL := scripts/makefile/pycache_del.sh DISHKA_PLOT_DATA := scripts/dishka/plot_dependencies_data.py -.PHONY: pycache-del tree plot-data pycache-del: @$(PYCACHE_DEL) -# Clean tree tree: pycache-del @tree -# Dishka plot-data: - @$(PYTHON) $(DISHKA_PLOT_DATA) + @$(PYTHON_BIN) $(DISHKA_PLOT_DATA) diff --git a/README.md b/README.md index 12a00f64..1c5ffbf3 100644 --- a/README.md +++ b/README.md @@ -1,771 +1,54 @@ -# Overview +[![Mentioned in Awesome FastAPI](https://awesome.re/mentioned-badge.svg)](https://github.com/mjhea0/awesome-fastapi?tab=readme-ov-file#best-practices) -πŸ“˜ This FastAPI-based project and its documentation represent a practical interpretation of Clean Architecture and -Command Query Responsibility Segregation (CQRS) principles with elements of Domain-Driven Design (DDD). -Although it's not meant to serve as a comprehensive reference or a strict application of these methodologies, the -project demonstrates how their core ideas can be effectively put into practice in Python. -If they're new to you, refer to the [Useful Resources](#useful-resources) section. +Stay tuned. Refactor in progress, see [`legacy-2025`](https://github.com/ivan-borovets/fastapi-clean-example/tree/legacy-2025) branch for architecture docs -# Table of contents +TODO: +- [ ] Polish code where possible +- [ ] Write integration tests, finally +- [ ] Explain code and patterns in new README +- [ ] Make template project -1. [Overview](#overview) -2. [Architecture Principles](#architecture-principles) - 1. [Introduction](#introduction) - 2. [Layered Approach](#layered-approach) - 3. [Dependency Rule](#dependency-rule) - 1. [Note on Adapters](#note-on-adapters) - 4. [Layered Approach Continued](#layered-approach-continued) - 5. [Dependency Inversion](#dependency-inversion) - 6. [Dependency Injection](#dependency-injection) - 7. [CQRS](#cqrs) -3. [Project](#project) - 1. [Dependency Graphs](#dependency-graphs) - 2. [Structure](#structure) - 3. [Technology Stack](#technology-stack) - 4. [API](#api) - 1. [General](#general) - 2. [Account](#account-apiv1account) - 3. [Users](#users-apiv1users) - 5. [Configuration](#configuration) - 1. [Files](#files) - 2. [Flow](#flow) - 3. [Local Environment](#local-environment) - 4. [Other Environments](#other-environments-devprod) - 5. [Adding New Environments](#adding-new-environments) -4. [Useful Resources](#useful-resources) -5. [Support the Project](#-support-the-project) -6. [Acknowledgements](#acknowledgements) - -# Architecture Principles - -## Introduction - -This repository may be helpful for those seeking backend implementation in Python that is both framework-agnostic -and storage-agnostic (unlike Django). -Such flexibility can be achieved by using a web framework that doesn't impose strict software design (like FastAPI) and -applying a layered architecture patterned after the one proposed by Robert Martin, which we'll explore further. - -The original explanation of Clean Architecture concepts can be -found [here](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html). -If you're still wondering why Clean Architecture matters, read the article β€” it only takes about 5 minutes. -In essence, it’s about making your application independent of external systems and highly testable. - -

- Clean Architecture Diagram -
Figure 1: Robert Martin's Clean Architecture Diagram -

- -> "A computer program is a detailed description of the **policy** by which inputs are transformed into outputs." -> -> β€” Robert Martin - -The most abstract policies define core business rules, while the least abstract ones handle I/O operations. -Being closer to implementation details, less abstract policies are more likely to change. -**Layer** represents a collection of components expressing policies at the same level of abstraction. - -Concentric circles represent boundaries between different layers. -The meaning of arrows in the diagram will be discussed [later](#dependency-rule). -For now, we will focus on the purpose of the layers. - -## Layered Approach - -![#gold](https://placehold.co/15x15/gold/gold.svg) **Domain Layer** - -- **Domain model** is a set of concepts, rules and behaviors that define what business (context) is and how it operates. - It is expressed in **ubiquitous language** β€” consistent terminology shared by developers and domain experts. - Domain layer implements domain model in code; this implementation is often called domain model. -- The strictest domain rules are **invariants** β€” conditions that must always hold true for the model. - Enforcing invariants means maintaining data consistency in the model. - This can be achieved through **encapsulation**, which hides internal state and couples data with behavior. -- Building blocks of domain model are (not limited to these): - - **value objects** β€” smart business types (no identity, immutable, equal by value). - - **entities** β€” business objects (have identity and lifecycle, equal by identity). - - **domain services** β€” containers for behavior that has no place in the components above. -- Other domain model building blocks, unused in this project but important for deeper DDD: - - **aggregates** β€” clusters of entities (1+) that must change together as a single unit, - managed exclusively through their root, defining boundaries of transactional consistency. - - **repositories** β€” abstractions emulating collections of aggregate roots. -- Domain model lies on a spectrum from anemic to rich. - - **anemic** β€” simple data types, entities are just data holders, rules and behaviors live outside. - - **rich** β€” value objects and entities encapsulate data and rules; - invariants are enforced internally, so the model itself prevents invalid states. - For components: anemic means no behavior within, rich β€” the contrary. -- Domain services originally represent operations that don't naturally belong to a specific entity or value object. - But in projects with anemic entities, they can also contain logic that would otherwise be in those entities. -- In early stages of development when the domain model is not yet clearly defined, - I'd recommend keeping entities flat and anemic, even though the latter weakens encapsulation. - Once domain logic is well established, some entities can, as aggregate roots, become non-flat and rich. - This best enforces invariants but can be tricky to design once and for all. -- Prefer rich value objects early, freeing entities and services from an excessive burden of local rules. -- Consider domain layer the most important, stable, and independent part of a system. - -![#red](https://placehold.co/15x15/red/red.svg) **Application Layer** - -- Business defines **use case** as specification of observable behavior that delivers value by achieving a goal. -- Within use case, the behavior is enacted by **actor** β€” possibly a client of the software system. -- Actor performs use case in steps, some of which require interaction with the system. - These stepwise interactions with the system are handled at the application layer by **interactors**. - In other words, each interactor handles a single business operation matching a step within use case. -- Interactors are stateless and cannot call each other, unlike use cases. - Each is invoked independently - typically by external drivers such as HTTP controllers, message consumers, or - scheduled jobs. -- Interactor orchestrates domain logic and external calls needed to perform the operation. - Its primary responsibilities may include permission verification and transaction management. - To access external systems, interactors rely on **interfaces (ports)** that abstract infrastructure details. -- Interactor uses **DTOs (Data Transfer Objects)** to exchange serializable data with external layers. - These are simple, behavior-free carriers - the cross-layer transport for external contracts. -- If logic is reused across interactors: extract an application service when it falls under typical interactor - responsibilities; otherwise, consider evolving the domain model to include it. - Such evolution is a normal part of model enrichment. -- Together, domain and application layers form the **core** of the system. - -![#green](https://placehold.co/15x15/green/green.svg) **Infrastructure Layer** - -- This layer is responsible for adapting the core to external systems. -- It consists of **adapters**: driving and driven. - Driving adapters call into the core, translating external requests into interactor calls. - Driven adapters (port implementations) are called by the core via ports, allowing the core to interact with external - systems (databases, APIs, file systems, etc.) while keeping the business logic decoupled. -- Related adapter logic can be grouped into **infrastructure service**. - -> [!IMPORTANT] -> - Clean Architecture doesn't prescribe any particular number of layers. - The key is to follow the Dependency Rule, which is explained in the next section. - -## Dependency Rule - -A dependency occurs when one software component relies on another to operate. -If you were to split all blocks of code into separate modules, dependencies would manifest as imports between those -modules. -Typically, dependencies are graphically depicted in UML style in such a way that - -> [!IMPORTANT] -> - `A -> B` (**A points to B**) means **A depends on B**. - -The key principle of Clean Architecture is the **Dependency Rule**. -This rule states that **more abstract software components must not depend on more concrete ones.** -In other words, dependencies must never point outwards. - -> [!IMPORTANT] -> - Domain and application layers may import external tools and libraries to the extent necessary for describing - business logic - those that extend the programming language's capabilities (math/numeric utilities, time zone - conversion, object modeling, etc.). This trades some core stability for clarity and expressiveness. What is not - acceptable are dependencies that bind business logic to implementation details (including frameworks) or to - out-of-process systems (databases, brokers, file systems, cloud SDKs, etc.). -> -> - Components within the same layer **can depend on each other.** For example, components in the Infrastructure layer - can interact with one another without crossing into other layers. -> -> - Components in any outer layer can depend on components in any inner layer, not necessarily the one closest to - them. For example, components in the Presentation layer can directly depend on the Domain layer, bypassing the - Application and Infrastructure layers. -> -> - Avoid letting business logic leak into peripheral details, such as raising business-specific exceptions in the - Infrastructure layer without re-raising them in the business logic or declaring domain rules outside the Domain - layer. -> -> - In specific cases where database constraints enforce business rules, the Infrastructure layer may raise - domain-specific exceptions, such as `UsernameAlreadyExistsError` for a `UNIQUE CONSTRAINT` violation. - Handling these exceptions in the Application layer ensures that any business logic implemented in adapters remains - under control. -> -> - Avoid introducing elements in inner layers that specifically exist to support outer layers. - For example, you might be tempted to place something in the Application layer that exists solely to support a - specific piece of infrastructure. - At first glance, based on imports, it might seem that the Dependency Rule isn't violated. However, in reality, - you've broken the core idea of the rule by embedding infrastructure concerns (more concrete) into the business logic - (more abstract). - -### Note on Adapters - -The **Infrastructure layer** in Clean Architecture acts as the adapter layer β€” connecting the application to -external systems. -In this project, we treat both **Infrastructure** and **Presentation** as adapters, since both adapt the application to -the outside world. -Speaking of dependencies direction, the diagram by R. Martin in Figure 1 can, without significant loss, be replaced by a -more concise and pragmatic one β€” where the adapter layer serves as a "bridge", depending both on the internal layers of -the application and external components. -This adjustment implies **reversing** the arrow from the blue layer to the green layer in R. Martin's diagram. - -The proposed solution is a **trade-off**. -It doesn't strictly follow R. Martin's original concept but avoids introducing excessive abstractions with -implementations outside the application's boundaries. -Pursuing purity on the outermost layer is more likely to result in overengineering than in practical gains. - -My approach retains nearly all advantages of Clean Architecture while simplifying real-world development. -When needed, adapters can be removed along with the external components they're written for, which isn't a -significant issue. - -Let’s agree, for this project, to revise the principle: - -Original: -> "Dependencies must never point outwards." - -Revised: -> "Dependencies must never point outwards **within the core**." - -
- Revised Interpretation of CA-D - Revised Interpretation of CA-D, alternative -
-

- Figure 2: Revised Interpretation of Clean Architecture
- (diagrammed β€” original and alternative representation) -
-

- -## Layered Approach Continued - -![#blue](https://placehold.co/15x15/blue/blue.svg) **Presentation Layer** - -> [!NOTE] -> In the original diagram, the Presentation layer isn't explicitly distinguished and is instead included within the -> Interface Adapters layer. I chose to introduce it as a separate layer, marked in blue, as I see it as even more -> external compared to typical adapters. - -- This layer handles external requests and includes **controllers** that validate inputs and pass them to the - interactors in the Application layer. More abstract layers of the program assume that request data is already - validated, allowing them to focus solely on their core logic. -- Controllers must be as thin as possible, containing no logic beyond basic input validation and routing. Their - role is to act as an intermediary between the application and external systems (e.g., FastAPI). - -> [!IMPORTANT] -> - **_Basic_** validation, like checking whether the structure of the incoming request matches the structure of the - defined request model (e.g., type safety and required fields) should be performed by controllers at this layer, - while **_business rule_** validation (e.g., ensuring the email domain is allowed, verifying the uniqueness of - username, or checking if a user meets the required age) belongs to the Domain or Application layer. -> - Business rule validation often involves relationships between fields, such as ensuring that a discount applies only - within a specific date range or a promotion code is valid for orders above a certain total. -> - **Carefully** consider using Pydantic for business rule validation. While convenient, Pydantic models are slower - than regular dataclasses and reduce application core stability by coupling business logic to an external library. -> - If you choose Pydantic (or a similar tool bundled with web framework) for business model definitions, ensure that - a Pydantic model in business layers is a separate model from the one in the Presentation layer, even if their - structure appears identical. Mixing data presentation logic with business logic is a common mistake made early in - development to save effort on creating separate models and field mapping, often due to not understanding that - structural similarities are temporary. - -![#gray](https://placehold.co/15x15/gray/gray.svg) **External Layer** - -> [!NOTE] -> In the original diagram, external components are included in the blue layer (Frameworks & Drivers). -> I've marked them in gray to clearly distinguish them from layers within the application's boundaries. - -- This layer represents fully external components such as web frameworks (e.g. FastAPI itself), databases, third-party - APIs, and other services. -- These components operate outside the application’s core logic and can be easily replaced or modified without affecting - the business rules, as they interact with the application only through the Presentation and Infrastructure layers. - -

- Basic Dependency Graph -
Figure 3: Basic Dependency Graph -

- -## Dependency Inversion - -The **dependency inversion** technique enables reversing dependencies **by introducing an interface** between -components, allowing an inner layer to communicate with an outer layer while adhering to the Dependency Rule. - -

- Corrupted Dependency -
Figure 4: Corrupted Dependency -

- -In this example, the Application component depends directly on the Infrastructure component, violating the Dependency -Rule. -This creates "corrupted" dependencies, where changes in the Infrastructure layer can propagate to and unintentionally -affect the Application layer. - -

- Correct Dependency -
Figure 5: Correct Dependency -

- -In the correct design, the Application layer component depends on an **abstraction (port)**, and the Infrastructure -layer component **implements** the corresponding interface. -This makes the Infrastructure component an adapter for the port, effectively turning it into a plugin for -Application layer. -Such a design adheres to the **Dependency Inversion Principle (DIP)**, minimizing the impact of infrastructure changes -on core business logic. - -## Dependency Injection - -The idea behind **Dependency Injection** is that a component shouldn't create the dependencies it needs but rather -receive them. -From this definition, it's clear that one common way to implement DI is by passing dependencies as arguments to the -`__init__` method or functions. - -But how exactly should these dependencies be initialized (and finalized)? - -**DI frameworks** offer an elegant solution by automatically creating necessary objects (while managing their -**lifecycle**) and injecting them where needed. -This makes the process of dependency injection much cleaner and easier to manage. - -

- Correct Dependency with DI -
Figure 6: Correct Dependency with DI -

- -FastAPI provides a built-in **DI mechanism** called [Depends](https://fastapi.tiangolo.com/tutorial/dependencies/), -which tends to leak into different layers of the application. This creates tight coupling to FastAPI, violating the -principles of Clean Architecture, where the web framework belongs to the outermost layer and should remain easily -replaceable. - -Refactoring the codebase to remove `Depends` when switching frameworks can be unnecessarily costly. It also has [other -limitations](https://dishka.readthedocs.io/en/stable/alternatives.html#why-not-fastapi) that are beyond the scope of -this README. Personally, I prefer [**Dishka**](https://dishka.readthedocs.io/en/stable/index.html) β€” a solution that -avoids these issues and remains framework-agnostic. - -## CQRS - -The project implements Command Query Responsibility Segregation (**CQRS**) β€” a pattern that separates read and write -operations into distinct paths. - -- **Commands** (via interactors) handle write operations and business-critical reads using command gateways that work - with entities and value objects. -- **Queries** are implemented through query services (similar contract to interactors) that use query gateways to fetch - data optimized for presentation as query models. - -This separation enables: - -- Efficient read operations through specialized query gates, avoiding loading complete entity models. -- Performance optimization by tailoring data retrieval to specific view requirements. -- Flexibility to combine data from multiple models in read operations with minimal field selection. - -# Project - -## Dependency Graphs - -
- Application Controller - Interactor - -

- Application Controller - Interactor -
Figure 7: Application Controller - Interactor -

- -In the presentation layer, a Pydantic model appears when working with FastAPI and detailed information needs to be -displayed in OpenAPI documentation. -You might also find it convenient to validate certain fields using Pydantic; -however, be cautious to avoid leaking business rules into the presentation layer. - -For request data, a plain `dataclass` is often sufficient. -Unlike lighter alternatives, it provides attribute access, which is more convenient for working in the application -layer. -However, such access is unnecessary for data returned to the client, where a `TypedDict` is sufficient (it's -approximately twice as fast to create as a dataclass with slots, with comparable access times). - -
- -
- Application Interactor - -

- Application Interactor -
Figure 8: Application Interactor -

- -
- -
- Application Interactor - Adapter - -

- Application Interactor - Adapter -
Figure 9: Application Interactor - Adapter -

- -
- -
- Domain - Adapter - -

- Domain - Adapter -
Figure 10: Domain - Adapter -

- -
- -
- Infrastructure Controller - Handler -

- Infrastructure Controller - Handler -
Figure 11: Infrastructure Controller - Handler -

- -An infrastructure handler may be required as a temporary solution in cases where a separate context exists but isn't -physically separated into a distinct domain (e.g., not implemented as a standalone module within a monolithic -application). -In such cases, the handler operates as an application-level interactor but resides in the infrastructure layer. - -Initially, I called these handlers interactors, but the community reacted very negatively to the idea of interactors in -the infrastructure layer, refusing to acknowledge that these essentially belong to another context. - -In this application, such handlers include those managing user accounts, such as registration, login, and logout. - -
- -
- Infrastructure Handler -

- Infrastructure Handler -
Figure 12: Infrastructure Handler -

- -Ports in infrastructure are not commonly seen β€” typically, only concrete implementations are present. -However, in this project, since we have a separate layer of adapters (presentation) located outside the infrastructure, -ports are necessary to comply with the dependency rule. - -
- -
- -**Identity Provider (IdP)** abstracts authentication details, linking the main business context with the authentication -context. In this example, the authentication context is not physically separated, making it an infrastructure detail. -However, it can potentially evolve into a separate domain. - - Identity Provider -

- Identity Provider -
Figure 13: Identity Provider -

- -Normally, IdP is expected to provide all information about current user. -However, in this project, since roles are not stored in sessions or tokens, retrieving them in main context was more -natural. - -
- -## Structure - -``` -. -β”œβ”€β”€ config/... # configuration files and scripts, includes Docker -β”œβ”€β”€ Makefile # shortcuts for setup and common tasks -β”œβ”€β”€ scripts/... # helper scripts -β”œβ”€β”€ pyproject.toml # tooling and environment config (uv) -β”œβ”€β”€ ... -└── src/ - └── app/ - β”œβ”€β”€ domain/ # domain layer - β”‚ β”œβ”€β”€ services/... # domain layer services - β”‚ β”œβ”€β”€ entities/... # entities (have identity) - β”‚ β”‚ β”œβ”€β”€ base.py # base declarations - β”‚ β”‚ └── ... # concrete entities - β”‚ β”œβ”€β”€ value_objects/... # value objects (no identity) - β”‚ β”‚ β”œβ”€β”€ base.py # base declarations - β”‚ β”‚ └── ... # concrete value objects - β”‚ └── ... # ports, enums, exceptions, etc. - β”‚ - β”œβ”€β”€ application/... # application layer - β”‚ β”œβ”€β”€ commands/ # write ops, business-critical reads - β”‚ β”‚ β”œβ”€β”€ create_user.py # interactor - β”‚ β”‚ └── ... # other interactors - β”‚ β”œβ”€β”€ queries/ # optimized read operations - β”‚ β”‚ β”œβ”€β”€ list_users.py # query service - β”‚ β”‚ └── ... # other query services - β”‚ └── common/ # common layer objects - β”‚ β”œβ”€β”€ services/... # authorization, etc. - β”‚ └── ... # ports, exceptions, etc. - β”‚ - β”œβ”€β”€ infrastructure/... # infrastructure layer - β”‚ β”œβ”€β”€ adapters/... # port adapters - β”‚ β”œβ”€β”€ auth/... # auth context (session-based) - β”‚ └── ... # persistence, exceptions, etc. - β”‚ - β”œβ”€β”€ presentation/... # presentation layer - β”‚ └── http/ # http interface - β”‚ β”œβ”€β”€ auth/... # web auth logic - β”‚ β”œβ”€β”€ controllers/... # controllers and routers - β”‚ └── errors/... # error handling helpers - β”‚ - β”œβ”€β”€ setup/ - β”‚ β”œβ”€β”€ ioc/... # dependency injection setup - β”‚ β”œβ”€β”€ config/... # app settings - β”‚ └── app_factory.py # app builder - β”‚ - └── run.py # app entry point -``` - -## Technology Stack - -- **Python**: `3.13` -- **Core**: `alembic`, `alembic-postgresql-enum`, `bcrypt`, `dishka`, `fastapi-error-map`, `fastapi`, `orjson`, - `psycopg3[binary]`, `pyjwt[crypto]`, `sqlalchemy[mypy]`, `uuid-utils`, `uvicorn`, `uvloop` -- **Development**: `deptry`, `import-linter`, `mypy`, `pre-commit`, `ruff`, `slotscheck` -- **Testing**: `coverage`, `line-profiler`, `pytest`, `pytest-asyncio` - -## API - -

- Handlers -
Figure 14: Handlers -

- -### General - -- `/` (GET): Open to **everyone**. - - Redirects to Swagger documentation. -- `/api/v1/health` (GET): Open to **everyone**. - - Returns `200 OK` if the API is alive. - -### Account (`/api/v1/account`) - -- `/signup` (POST): Open to **everyone**. - - Registers a new user with validation and uniqueness checks. - - Passwords are peppered, salted, and stored as hashes. - - A logged-in user cannot sign up until the session expires or is terminated. -- `/login` (POST): Open to **everyone**. - - Authenticates registered user, sets a JWT access token with a session ID in cookies, and creates a session. - - A logged-in user cannot log in again until the session expires or is terminated. - - Authentication renews automatically when accessing protected routes before expiration. - - If the JWT is invalid, expired, or the session is terminated, the user loses authentication. [^1] -- `/password` (PUT): Open to **authenticated users**. - - The current user can change their password. - - New password must differ from current password. -- `/logout` (DELETE): Open to **authenticated users**. - - Logs the user out by deleting the JWT access token from cookies and removing the session from the database. - -### Users (`/api/v1/users`) - -- `/` (POST): Open to **admins**. - - Creates a new user, including admins, if the username is unique. - - Only super admins can create new admins. -- `/` (GET): Open to **admins**. - - Retrieves a paginated list of existing users with relevant information. -- `/{user_id}/password` (PUT): Open to **admins**. - - Admins can set passwords of subordinate users. -- `/{user_id}/roles/admin` (PUT): Open to **super admins**. - - Grants admin rights to a specified user. - - Super admin rights cannot be changed. -- `/{user_id}/roles/admin` (DELETE): Open to **super admins**. - - Revokes admin rights from a specified user. - - Super admin rights cannot be changed. -- `/{user_id}/activation` (PUT): Open to **admins**. - - Restores a previously soft-deleted user. - - Only super admins can activate other admins. -- `/{user_id}/activation` (DELETE): Open to **admins**. - - Soft-deletes an existing user, making that user inactive. - - Also deletes the user's sessions. - - Only super admins can deactivate other admins. - - Super admins cannot be soft-deleted. - -> [!NOTE] -> - Super admin privileges must be initially granted manually (e.g., directly in the database), though the user - account itself can be created through the API. - -## Configuration - -> [!WARNING] -> - This part of documentation is **not** related to the architecture approach. -> - Use any configuration method you prefer. - -### Files - -- **config.toml**: Main application settings organized in sections -- **export.toml**: Lists fields to export to .env (`export.fields = ["postgres.USER", "postgres.PASSWORD", ...]`) -- **.secrets.toml**: Optional sensitive data (same format as config.toml, merged with main config) - -> [!IMPORTANT] -> - This project includes secret files for demonstration purposes only. In a real project, you **must** ensure that - `.secrets.toml` and all `.env` files are not tracked by version control system to prevent exposing sensitive - information. See this project's `.gitignore` for an example of how to properly exclude these sensitive files from - Git. - -### Flow - -In this project I use my own configuration system based on TOML files as the single source of truth. -The system generates `.env` files for Docker and infrastructure components while the application reads settings directly -from the structured TOML files. More details are available at https://github.com/ivan-borovets/toml-config-manager - -

- Configuration flow -
Figure 15: Configuration flow -
Here, the arrows represent usage flow, not dependencies. -

- -### Local Environment - -1. Configure local environment - -* In this project, local configuration is already prepared in `config/local/`. - Nothing needs to be created β€” adjust files only if you want to change defaults. -* If you want to adjust settings, edit the existing TOML files in `config/local/` directly. - `.env.local` will be generated automatically β€” **don’t** create or edit it manually. -* Docker Compose in this project is already configured with `APP_ENV`. - Just keep in mind this variable if you change the setup: - -```yaml -services: - app: - # ... - environment: - APP_ENV: ${APP_ENV} +Prerequisites +```shell +uv sync +source .venv/bin/activate +pre-commit install --hook-type pre-commit --hook-type pre-push ``` -2. Set environment variable - +Start in Docker ```shell -export APP_ENV=local -# export APP_ENV=dev -# export APP_ENV=prod +make upd ``` -3. Check it and generate `.env` - +Start locally ```shell -# Probably you'll need Python 3.13 installed on your system to run these commands. -# The next code section provides commands for its fast installation. -make env # should print APP_ENV=local -make dotenv # should tell you where .env.local was generated +make upd-local +alembic upgrade head +uvicorn app.main.run:make_app --host 0.0.0.0 --port 8000 --reload +# or `src/app/main/run.py` in IDE ``` +Full API access: +- create user via sign up +- set its role to `SUPER_ADMIN` manually in DB +- log in as super admin -4. Install `uv` - +Stop ```shell -# sudo apt update -# sudo apt install pipx -# pipx ensurepath -# pipx install uv -# https://docs.astral.sh/uv/getting-started/installation/#shell-autocompletion -# uv python install 3.13 # To install Python +make down ``` -5. Set up virtual environment - +Test (light paths) ```shell -uv sync --group dev -source .venv/bin/activate - -# Alternatively, -# uv v -# source .venv/bin/activate # on Unix -# .venv\Scripts\activate # on Windows -# uv pip install -e . --group dev +make check ``` -Don't forget to tell your IDE where the interpreter is located. - -Install pre-commit hooks: - +Test (all paths) ```shell -# https://pre-commit.com/ -pre-commit install +make test-docker ``` -6. Launch - -- To run only the database in Docker and use the app locally, use the following command: - - ```shell - make up.db - # make up.db-echo - ``` - -- Then, apply the migrations: - ```shell - alembic upgrade head - ``` - -- After applying the migrations, the database is ready, and you can launch the application locally (e.g., through your - IDE). Remember to set the `APP_ENV` environment variable in your IDE's run configuration. - -- To run via Docker Compose: - - ```shell - make up - # make up.echo - ``` - - In this case, migrations will be applied automatically at startup. - -7. Shutdown - -- To stop the containers, use: - ```shell - make down - ``` - -### Other Environments (dev/prod) - -1. Use the instructions about [local environment](#local-environment) above - -* But make sure you've created similar structure in `config/dev` or `config/prod` with [files](#files): - * `config.toml` - * `.secrets.toml` - * `export.toml` - * `docker-compose.yaml` if needed -* `.env.dev` or `.env.prod` to be generated later β€” **don't** create them manually - -### Adding New Environments - -1. Add new value to `ValidEnvs` enum in `config/toml_config_manager.py` (and maybe in your app settings) -2. Update `ENV_TO_DIR_PATHS` mapping in the same file (and maybe in your app settings) -3. Create corresponding directory in `config/` folder -4. Add required configuration [files](#files) - -Environment directories can also contain other env-specific files like `docker-compose.yaml`, which will be used by -Makefile commands. - -# Useful Resources - -## Layered Architecture - -- [Robert C. Martin. Clean Architecture: A Craftsman's Guide to Software Structure and Design. 2017](https://www.amazon.com/Clean-Architecture-Craftsmans-Software-Structure/dp/0134494164) - -- [Alistair Cockburn. Hexagonal Architecture Explained. 2024](https://www.amazon.com/Hexagonal-Architecture-Explained-Alistair-Cockburn-ebook/dp/B0D4JQJ8KD) - (introduced in 2005) - -## Domain-Driven Design - -- [Vlad Khononov. Learning Domain-Driven Design: Aligning Software Architecture and Business Strategy. 2021](https://www.amazon.com/Learning-Domain-Driven-Design-Aligning-Architecture/dp/1098100131) - -- [Vaughn Vernon. Implementing Domain-Driven Design. 2013](https://www.amazon.com/Implementing-Domain-Driven-Design-Vaughn-Vernon/dp/0321834577) - -- [Eric Evans. Domain-Driven Design: Tackling Complexity in the Heart of Software. 2003](https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215) - -- [Martin Fowler. Patterns of Enterprise Application Architecture. 2002](https://www.amazon.com/Patterns-Enterprise-Application-Architecture-Martin/dp/0321127420) - -## Adjacent - -- [Vladimir Khorikov. Unit Testing Principles. 2020](https://www.amazon.com/Unit-Testing-Principles-Practices-Patterns/dp/1617296279) - -# ⭐ Support the Project - -If you find this project useful, please give it a star or share it! -Your support means a lot. - -πŸ‘‰ Check out the amazing [fastapi-error-map](https://github.com/ivan-borovets/fastapi-error-map), used here to enable -contextual, per-route error handling with automatic OpenAPI schema generation. - -πŸ’¬ Feel free to open issues, ask questions, or submit pull requests. - -# Acknowledgements - -I would like to express my sincere gratitude to the following individuals for their valuable ideas and support in -satisfying my curiosity throughout the development of this project: -[igoryuha](https://github.com/igoryuha), -[tishka17](https://github.com/tishka17), -[chessenjoyer17](https://github.com/chessenjoyer17), -[PlzTrustMe](https://github.com/PlzTrustMe), -[Krak3nDev](https://github.com/Krak3nDev), -[Ivankirpichnikov](https://github.com/Ivankirpichnikov), -[SamWarden](https://github.com/SamWarden), -[nkhitrov](https://github.com/nkhitrov), -[ApostolFet](https://github.com/ApostolFet), -Lancetnik, Sehat1137, Maclovi. - -I also greatly appreciate the valuable insights shared by participants of the ASGI Community Telegram chat, despite -frequent and lively communication challenges, as well as the βš—οΈ Reagento (adaptix/dishka) -[Telegram chat](https://t.me/reagento_ru) for their thoughtful discussions and generous knowledge exchange. - -# Todo +See [Makefile](Makefile) for more commands -- [x] set up CI -- [x] simplify settings -- [x] simplify annotations -- [ ] add integration tests -- [ ] explain design choices +Thanks for your patience and support -[^1]: Session and token share the same expiry time, avoiding database reads if the token is expired. -This scheme of using JWT **is not** related to OAuth 2.0 and is a custom micro-optimization. +[Acknowledgements](https://github.com/ivan-borovets/fastapi-clean-example/tree/legacy-2025?tab=readme-ov-file#acknowledgements) diff --git a/alembic.ini b/alembic.ini index 809dc39b..5fa0c360 100644 --- a/alembic.ini +++ b/alembic.ini @@ -2,21 +2,28 @@ [alembic] # path to migration scripts. -# Use forward slashes (/) also on windows to provide an os agnostic path +# this is typically a path given in POSIX (e.g. forward slashes) +# format, relative to the token %(here)s which refers to the location of this +# ini file script_location = src/app/infrastructure/persistence_sqla/alembic # template used to generate migration file names; The default value is %%(rev)s_%%(slug)s # Uncomment the line below if you want the files to be prepended with date and time -file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s +# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file +# for all available tokens +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s +# Or organize into date-based subdirectories (requires recursive_version_locations = true) +file_template = %%(year)d-%%(month).2d-%%(day).2d_%%(hour).2d%%(minute).2d%%(second).2d_%%(slug)s # sys.path path, will be prepended to sys.path if present. -# defaults to the current working directory. +# defaults to the current working directory. for multiple paths, the path separator +# is defined by "path_separator" below. prepend_sys_path = . # timezone to use when rendering the date within the migration file # as well as the filename. -# If specified, requires the python>=3.9 or backports.zoneinfo library. -# Any required deps can installed by adding `alembic[tz]` to the pip requirements +# If specified, requires the tzdata library which can be installed by adding +# `alembic[tz]` to the pip requirements. # string value is passed to ZoneInfo() # leave blank for localtime # timezone = @@ -34,20 +41,38 @@ prepend_sys_path = . # sourceless = false # version location specification; This defaults -# to src/app/infra/sqla_db/alembic/versions. When using multiple version +# to /versions. When using multiple version # directories, initial revisions must be specified with --version-path. -# The path separator used here should be the separator specified by "version_path_separator" below. -# version_locations = %(here)s/bar:%(here)s/bat:src/app/infra/sqla_db/alembic/versions - -# version path separator; As mentioned above, this is the character used to split -# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. -# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. -# Valid values for version_path_separator are: +# The path separator used here should be the separator specified by "path_separator" +# below. +# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions + +# path_separator; This indicates what character is used to split lists of file +# paths, including version_locations and prepend_sys_path within configparser +# files such as alembic.ini. +# The default rendered in new alembic.ini files is "os", which uses os.pathsep +# to provide os-dependent path splitting. +# +# Note that in order to support legacy alembic.ini files, this default does NOT +# take place if path_separator is not present in alembic.ini. If this +# option is omitted entirely, fallback logic is as follows: +# +# 1. Parsing of the version_locations option falls back to using the legacy +# "version_path_separator" key, which if absent then falls back to the legacy +# behavior of splitting on spaces and/or commas. +# 2. Parsing of the prepend_sys_path option falls back to the legacy +# behavior of splitting on spaces, commas, or colons. +# +# Valid values for path_separator are: # -# version_path_separator = : -# version_path_separator = ; -# version_path_separator = space -version_path_separator = os # Use os.pathsep. Default configuration used for new projects. +# path_separator = : +# path_separator = ; +# path_separator = space +# path_separator = newline +# +# Use os.pathsep. Default configuration used for new projects. +path_separator = os + # set to 'true' to search source files recursively # in each "version_locations" directory @@ -58,6 +83,9 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne # are written from script.py.mako # output_encoding = utf-8 +# database URL. This is consumed by the user-maintained env.py script only. +# other means of configuring database URLs may be customized within the env.py +# file. sqlalchemy.url = driver://user:pass@localhost/dbname @@ -72,13 +100,20 @@ sqlalchemy.url = driver://user:pass@localhost/dbname # black.entrypoint = black # black.options = -l 79 REVISION_SCRIPT_FILENAME -# lint with attempts to fix using "ruff" - use the exec runner, execute a binary +# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module +# hooks = ruff +# ruff.type = module +# ruff.module = ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Alternatively, use the exec runner to execute a binary found on your PATH hooks = ruff ruff.type = exec -ruff.executable = %(here)s/.venv/bin/ruff -ruff.options = format REVISION_SCRIPT_FILENAME +ruff.executable = ruff +ruff.options = check --fix REVISION_SCRIPT_FILENAME -# Logging configuration +# Logging configuration. This is also consumed by the user-maintained +# env.py script only. [loggers] keys = root,sqlalchemy,alembic @@ -89,12 +124,12 @@ keys = console keys = generic [logger_root] -level = WARN +level = WARNING handlers = console qualname = [logger_sqlalchemy] -level = WARN +level = WARNING handlers = qualname = sqlalchemy.engine diff --git a/config/dev/.gitkeep b/config/dev/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/config/local/.env.local b/config/local/.env.local deleted file mode 100644 index 27010d55..00000000 --- a/config/local/.env.local +++ /dev/null @@ -1,11 +0,0 @@ -# This .env file was automatically generated by toml_config_manager. -# Do not edit directly. Make changes in config.toml or .secrets.toml instead. -# Ensure values here match those in config files. -# Environment: local -# Generated: 2025-06-05T02:17:23.145056+00:00 -POSTGRES_USER=postgres -POSTGRES_PASSWORD=changethis -POSTGRES_DB=web_app_db_pg -POSTGRES_PORT=5432 -UVICORN_HOST=0.0.0.0 -UVICORN_PORT=9999 diff --git a/config/local/.gitkeep b/config/local/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/config/local/.secrets.toml b/config/local/.secrets.toml deleted file mode 100644 index 33ef77e2..00000000 --- a/config/local/.secrets.toml +++ /dev/null @@ -1,17 +0,0 @@ -# PostgreSQL -[postgres] -USER = "postgres" -PASSWORD = "changethis" - -[security.auth] -# Recommended: Use a cryptographically secure random generator to create a -# string of at least 32 characters including numbers, letters, and symbols -JWT_SECRET = "REPLACE_THIS_WITH_YOUR_OWN_SECRET_JWT_SECRET_VALUE" - -[security.password] -# Critical: This value must be kept secret and should be changed in production -# Losing or changing this value will invalidate all existing password hashes -# IMPORTANT: Replace the placeholder below with your own secure random string -# Recommended: Use a cryptographically secure random generator to create a -# string of at least 32 characters including numbers, letters, and symbols -PEPPER = "REPLACE_THIS_WITH_YOUR_OWN_SECRET_PEPPER_VALUE" diff --git a/config/local/Dockerfile b/config/local/Dockerfile deleted file mode 100644 index d1e9dc23..00000000 --- a/config/local/Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim AS builder -WORKDIR /app -ENV UV_COMPILE_BYTECODE=1 \ - UV_LINK_MODE=copy -COPY pyproject.toml uv.lock ./ -RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-dev --no-install-project -COPY . ./ -RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-dev - -FROM python:3.13-slim-bookworm AS final -ARG APP_UID=10001 -ARG APP_GID=10001 -RUN groupadd -g ${APP_GID} appgroup && \ - useradd -u ${APP_UID} -g ${APP_GID} -s /usr/sbin/nologin -M appuser -WORKDIR /app -ENV VIRTUAL_ENV="/app/.venv" \ - PATH="/app/.venv/bin:$PATH" \ - PYTHONDONTWRITEBYTECODE=1 \ - PYTHONUNBUFFERED=1 -COPY --from=builder --chown=${APP_UID}:${APP_GID} /app/ ./ -USER appuser -EXPOSE 8888 -CMD ["uvicorn", "app.run:make_app", "--factory", "--host", "0.0.0.0", "--port", "8888", "--loop", "uvloop"] diff --git a/config/local/config.toml b/config/local/config.toml deleted file mode 100644 index d51ad188..00000000 --- a/config/local/config.toml +++ /dev/null @@ -1,43 +0,0 @@ -# PostgreSQL -[postgres] -DB = "web_app_db_pg" -HOST = "localhost" -PORT = 5432 -DRIVER = "psycopg" - -# Uvicorn -[uvicorn] -HOST = "0.0.0.0" -PORT = 9999 - -# SQLAlchemy -[sqla] -ECHO = false -ECHO_POOL = false -POOL_SIZE = 30 -MAX_OVERFLOW = 20 - -# Logs -[logs] -# Can be set to "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL" -LEVEL = "DEBUG" - -[security.auth] -# Can be set to "HS256", "HS384", "HS512", "RS256", "RS384", "RS512" -JWT_ALGORITHM = "HS256" -# Must be at least 1 (number of minutes) -SESSION_TTL_MIN = 5 -# Must be a number (fraction, 0 < fraction < 1) -SESSION_REFRESH_THRESHOLD = 0.2 - -[security.cookies] -# Choose `true` for production (secure=True, samesite="Strict") -SECURE = false - -[security.password] -# https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#introduction -HASHER_WORK_FACTOR = 11 -# CPU-bound & GIL released: per-worker β‰ˆ max(1, floor(effective vCPUs / workers)) -HASHER_MAX_THREADS = 8 -# Fail-fast cap: max semaphore wait before timeout (start ~1 second, tune to peak) -HASHER_SEMAPHORE_WAIT_TIMEOUT_S = 1.0 diff --git a/config/local/docker-compose.yaml b/config/local/docker-compose.yaml deleted file mode 100644 index 80137f4b..00000000 --- a/config/local/docker-compose.yaml +++ /dev/null @@ -1,45 +0,0 @@ -services: - web_app_db_pg: - image: postgres:16-alpine - shm_size: 128mb - environment: - - POSTGRES_USER=${POSTGRES_USER} - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - - POSTGRES_DB=${POSTGRES_DB} - ports: - - "127.0.0.1:${POSTGRES_PORT}:5432" - volumes: - - pgdata:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] - interval: 5s - timeout: 5s - retries: 10 - start_period: 10s - - web_app: - build: - context: ../.. - dockerfile: config/${APP_ENV}/Dockerfile - image: web_app:${APP_ENV} - environment: - APP_ENV: ${APP_ENV} - UVICORN_HOST: ${UVICORN_HOST} - UVICORN_PORT: ${UVICORN_PORT} - POSTGRES_HOST: web_app_db_pg - ports: - - "127.0.0.1:${UVICORN_PORT}:${UVICORN_PORT}" - depends_on: - web_app_db_pg: - condition: service_healthy - command: > - sh -c " - echo 'Running alembic migrations...' && - alembic upgrade head && - echo 'Starting Uvicorn...' && - uvicorn app.run:make_app --factory --host ${UVICORN_HOST} --port ${UVICORN_PORT} --loop uvloop - " - -volumes: - pgdata: - name: "web_app_pgdata_${APP_ENV}" diff --git a/config/local/export.toml b/config/local/export.toml deleted file mode 100644 index fa263745..00000000 --- a/config/local/export.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Don't rename `export` or `fields` -[export] -fields = [ - # PostgreSQL from secrets - "postgres.USER", - "postgres.PASSWORD", - # PostgreSQL from config - "postgres.DB", - "postgres.PORT", - # Uvicorn from config - "uvicorn.HOST", - "uvicorn.PORT", -] diff --git a/config/prod/.gitkeep b/config/prod/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/config/toml_config_manager.py b/config/toml_config_manager.py deleted file mode 100644 index d38169a6..00000000 --- a/config/toml_config_manager.py +++ /dev/null @@ -1,298 +0,0 @@ -import logging -import os -import tomllib -from collections.abc import Mapping -from datetime import UTC, datetime -from enum import StrEnum -from pathlib import Path -from types import MappingProxyType -from typing import Any, Final - -ConfigDict = dict[str, Any] -ExportEnv = dict[str, str] - -log = logging.getLogger(__name__) - - -# LOGGING - - -LOG_LEVEL_VAR_NAME: Final[str] = "LOG_LEVEL" - - -class LoggingLevel(StrEnum): - DEBUG = "DEBUG" - INFO = "INFO" - WARNING = "WARNING" - ERROR = "ERROR" - CRITICAL = "CRITICAL" - - -DEFAULT_LOG_LEVEL: Final[LoggingLevel] = LoggingLevel.INFO - - -def validate_logging_level(*, level: str) -> LoggingLevel: - try: - return LoggingLevel(level) - except ValueError as err: - raise ValueError(f"Invalid log level: '{level}'.") from err - - -FMT: Final[str] = ( - "[%(asctime)s.%(msecs)03d] " - "[%(threadName)s] " - "%(funcName)20s " - "%(module)s:%(lineno)d " - "%(levelname)-8s - " - "%(message)s" -) -DATEFMT: Final[str] = "%Y-%m-%d %H:%M:%S" - - -def configure_logging( - *, - level: LoggingLevel = DEFAULT_LOG_LEVEL, -) -> None: - logging.basicConfig( - level=level, - datefmt=DATEFMT, - format=FMT, - force=True, - ) - - -# ENVIRONMENT & PATHS - - -ENV_VAR_NAME: Final[str] = "APP_ENV" - - -class ValidEnvs(StrEnum): - """ - Values should reflect actual directory names. - """ - - LOCAL = "local" - DEV = "dev" - PROD = "prod" - - -class DirContents(StrEnum): - """ - Values should reflect actual file names. - """ - - CONFIG_NAME = "config.toml" - SECRETS_NAME = ".secrets.toml" - EXPORT_NAME = "export.toml" - DOTENV_NAME = ".env" - - -BASE_DIR_PATH: Final[Path] = Path(__file__).resolve().parents[1] -CONFIG_PATH: Final[Path] = BASE_DIR_PATH / "config" - -ENV_TO_DIR_PATHS: Final[Mapping[ValidEnvs, Path]] = MappingProxyType({ - ValidEnvs.LOCAL: CONFIG_PATH / ValidEnvs.LOCAL, - ValidEnvs.DEV: CONFIG_PATH / ValidEnvs.DEV, - ValidEnvs.PROD: CONFIG_PATH / ValidEnvs.PROD, -}) - - -def validate_env(env: str | None) -> ValidEnvs: - if env is None: - raise ValueError(f"{ENV_VAR_NAME} is not set.") - try: - return ValidEnvs(env) - except ValueError as err: - valid_values = ", ".join(f"'{e}'" for e in ValidEnvs) - raise ValueError( - f"Invalid {ENV_VAR_NAME}: '{env}'. Must be one of: {valid_values}.", - ) from err - - -def get_current_env() -> ValidEnvs: - return validate_env(os.getenv(ENV_VAR_NAME)) - - -# CONFIG READING - - -def load_full_config( - env: ValidEnvs, - dir_paths: Mapping[ValidEnvs, Path] = ENV_TO_DIR_PATHS, - main_config: DirContents = DirContents.CONFIG_NAME, - secrets_config: DirContents = DirContents.SECRETS_NAME, -) -> ConfigDict: - log.info("Reading config for environment: '%s'", env) - config = read_config(env=env, config=main_config, dir_paths=dir_paths) - try: - secrets = read_config(env=env, config=secrets_config, dir_paths=dir_paths) - except FileNotFoundError: - log.warning("Secrets file not found. Full config will not contain secrets.") - return config - return merge_dicts(dict1=config, dict2=secrets) - - -def read_config( - env: ValidEnvs, - dir_paths: Mapping[ValidEnvs, Path], - config: DirContents, -) -> ConfigDict: - dir_path = dir_paths.get(env) - if dir_path is None: - raise FileNotFoundError(f"No directory path configured for environment: {env}") - file_path = dir_path / config - if not file_path.is_file(): - raise FileNotFoundError( - f"The file does not exist at the specified path: {file_path}", - ) - with file_path.open(mode="rb") as f: - return tomllib.load(f) - - -def merge_dicts(*, dict1: ConfigDict, dict2: ConfigDict) -> ConfigDict: - result = dict1.copy() - for key, value in dict2.items(): - if key in result and isinstance(result[key], dict) and isinstance(value, dict): - result[key] = merge_dicts(dict1=result[key], dict2=value) - else: - result[key] = value - return result - - -# EXPORT PROCESSING - - -EXPORT_SECTION: Final[str] = "export" -EXPORT_FIELDS_KEY: Final[str] = "fields" - - -def get_exported_env_variables( - env: ValidEnvs, - dir_paths: Mapping[ValidEnvs, Path] = ENV_TO_DIR_PATHS, -) -> ExportEnv: - config = load_full_config(env=env, dir_paths=dir_paths) - export_fields = load_export_fields(env=env, dir_paths=dir_paths) - return extract_export_fields_from_config(config=config, export_fields=export_fields) - - -def load_export_fields( - env: ValidEnvs, - dir_paths: Mapping[ValidEnvs, Path], -) -> list[str]: - export_data = read_config( - env=env, - config=DirContents.EXPORT_NAME, - dir_paths=dir_paths, - ) - - export_section = export_data.get(EXPORT_SECTION) - if not isinstance(export_section, dict): - raise ValueError( - f"Invalid {DirContents.EXPORT_NAME}: missing [{EXPORT_SECTION}] section" - ) - - fields = export_section.get(EXPORT_FIELDS_KEY) - if not isinstance(fields, list) or not all(isinstance(f, str) for f in fields): - raise ValueError( - f"Invalid {DirContents.EXPORT_NAME}: " - f"'{EXPORT_FIELDS_KEY}' must be a list of strings" - ) - if not fields: - raise ValueError( - f"Invalid {DirContents.EXPORT_NAME}: '{EXPORT_FIELDS_KEY}' cannot be empty" - ) - - return fields - - -def extract_export_fields_from_config( - config: ConfigDict, - export_fields: list[str], -) -> ExportEnv: - result: ExportEnv = {} - for field in export_fields: - str_value = get_env_value_by_export_field(config=config, field=field) - env_key = "_".join(part.upper() for part in field.split(".")) - result[env_key] = str_value - return result - - -def get_env_value_by_export_field(*, config: ConfigDict, field: str) -> str: - node: Mapping[str, Any] = config - value: Any = node - - parts = field.split(".") - for idx, part in enumerate(parts): - if part not in node: - raise KeyError(f"Field '{field}' not found in config") - - value = node[part] - - if idx != len(parts) - 1: - if not isinstance(value, Mapping): - raise KeyError(f"Field '{field}' not found in config") - node = value - - if isinstance(value, (dict, list)): - raise ValueError( - f"Field '{field}' cannot be converted to string: " - f"got {type(value).__name__}", - ) - - return str(value) - - -# DOTENV GENERATION - - -def write_dotenv_file( - *, - env: ValidEnvs, - exported_fields: ExportEnv, - generated_at: datetime | None = None, -) -> None: - if generated_at is None: - generated_at = datetime.now(UTC) - - dotenv_filename = f"{DirContents.DOTENV_NAME}.{env.value}" - dotenv_path = ENV_TO_DIR_PATHS[env] / dotenv_filename - - header = [ - "# This .env file was automatically generated by toml_config_manager.", - "# Do not edit directly. Make changes in config.toml or .secrets.toml instead.", - "# Ensure values here match those in config files.", - f"# Environment: {env}", - f"# Generated: {generated_at.isoformat()}", - ] - body = [f"{key}={value}" for key, value in exported_fields.items()] - body.append("") - - dotenv_path.write_text( - data="\n".join(header + body), - encoding="utf-8", - newline="\n", - ) - - log.info( - "Dotenv for environment '%s' was successfully generated at '%s'! ✨", - env.value, - str(dotenv_path.resolve()), - ) - - -# ENTRY POINT - - -def main() -> None: - log_lvl_str = os.getenv(LOG_LEVEL_VAR_NAME, DEFAULT_LOG_LEVEL) - log_lvl = validate_logging_level(level=log_lvl_str) - configure_logging(level=log_lvl) - - env = get_current_env() - exported_fields = get_exported_env_variables(env) - write_dotenv_file(env=env, exported_fields=exported_fields) - - -if __name__ == "__main__": - main() diff --git a/docker-compose.test.yml b/docker-compose.test.yml new file mode 100644 index 00000000..a47a7f18 --- /dev/null +++ b/docker-compose.test.yml @@ -0,0 +1,8 @@ +services: + app: + ports: !reset [] + environment: + ALLOW_DESTRUCTIVE_TEST_CLEANUP: "1" + + db_pg: + ports: !reset [] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..40be82f6 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,36 @@ +services: + app: + build: + context: . + dockerfile: ./Dockerfile + args: + - ENVIRONMENT=dev + restart: on-failure + volumes: + - .:/code + ports: + - "127.0.0.1:${UVICORN_PORT}:8000" + depends_on: + db_pg: + condition: service_healthy + env_file: + - .env + environment: + - COVERAGE_FILE=/tmp/.coverage + command: ["start", "8000"] + + db_pg: + image: postgres:18-alpine + shm_size: 128mb + ports: + - "127.0.0.1:${POSTGRES_PORT}:5432" + environment: + POSTGRES_DB: ${POSTGRES_DB?POSTGRES_DB is required} + POSTGRES_USER: ${POSTGRES_USER?POSTGRES_USER is required} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD?POSTGRES_PASSWORD is required} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] + interval: 2s + timeout: 2s + retries: 5 + start_period: 1s diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100755 index 00000000..0aba861d --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -e + +PORT=${2:-8000} + +case "$1" in + start) + alembic upgrade head + exec uvicorn app.main.run:make_app --factory --host 0.0.0.0 --port "$PORT" --reload + ;; + *) + exec "$@" + ;; +esac diff --git a/docs/Robert_Martin_CA.png b/docs/Robert_Martin_CA.png deleted file mode 100644 index 4ad25a849265bf6935ebce86766a8b6b41e6e783..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 376479 zcmX_nV|XP^)a{9FCllK?CzvD?+nQ)%+qP{?CboHE+cI(-@W(iKm9ZcU0t=- z-fQoQP?VQMg2#sk002nRQsT-000ble0L~2y{&lAufbezl2S|&5S9RAv>wTdTUnzl?tiuZSKQ~1XP88b;S3 z2mrbMw`j)877a$||Lx#cmws;`2uv|N9-n6~cp|1iX9y z(*i`w#n&;nxVpMJJBx-K27;kosOe?S{QrG>#{!dgK4Qb|@A*TZ^{?W@Du@&!2;iUJ z-oqCl(sTJ(QC@zO=ptP#yWvRtzHcX!OsS+ia4JN0m-ZcWwEu8)DV)lnPoMbFd%C!c ziud!AYs=*L-DxA^e7T1{kY>-glj4eDTV*fhw5BwG{@?UPmBKqep11zT+QWatdI}y4 zNWD4*?}3F5@|Ohs01z1aq{g1l|)qNo_&9?HRmJc3h9@x%r*uS}|{GZz#5$7HRxo z9?E(=+6TQDVA|SQ&wQ%Z)YeV5d^-ENMOHVByMG+=Z4cSliu&-kcU<;1T^x6Cy&xNR zhFoq|8K4IthZ7j$=y#+-yDn-B_AZ_>w_?2S;{6`AY4!d3if$RVjm1yGj#pa<>W_Pa zPnU3-_{7~D)JIQvmF2c0?{1bfx zkN&YhH6U{61uNGP$himC;PomGFrQQ0ix`0a`#yhcvvpFgyk-mfavLA<-iAelk&kba z^T^+a+4m4%0Ppg_M!5ACM4U?>(S#Ux^M8}rPsi=Fy}|&Z#_tv6e#vLI*_lMG5Lt+e z92O7;@Q*IY=&b|~cZ|{dD7VEA-F6QmCYUz@{4Ogi`+Ttbe8{=}~}W)_SNg=7M!mBesRo-_(q> z35aRYcZNLz-DDa8lU&|MU&5Sma#j97B*fKh9XI7J@Sl8g%0CH)4{ia8SYu*f?u)L~ z4X6#g927(Db5OF;-eYS1O8mFpH~=CLD9}fJxHRB&o{t-Dw0}dnr~H{`NzB?zA#|-NUR`sfAn`}> zJ9DBw<{#KVNGSbBjLm5$_(27kWtRc^ z+b*bBML;t6_mHr)Um^F)uNBmAss*a#qVcf!Xjo)F#WC-?pH}&G-_6Tw7C*zq1tJtF zGM1-X9c=QUUm}NvRWY7O-mpB>NXzq7wim{eF`)L)**x`Uy)?Jh-G2tnjK28ZRaAfe zJOz~iSGTPWtSl0~P=e-Kz&DGc>{14@RYgnd*s;y`Hfsx)1v1|GYbg3vwn;_MjF%9% zi*(YyGx!~D&&c)1gK621DAN3Ud@X?1X|`r%fyK3Xy4vC8D?{t|Z}hQ8)@->?)Tltl z)ez6psM>{W()7XIDIn0J%TuhtTccswqd-1Fk_^M}oE(<+MUv5< z>aP<%2>0ldA@h?Q!UBCWgMli=X~6Q2QMKi6h%a<}Cqy3vGUxQ7Llb~$ql!LUZV9`d zt$=m^GaXN@V?ZE)1P3$j9D+T;_fPFE%L*ms@n=PcqJV@E@_MLb^@8!d8Kn*g$QPaQ z2SZ1f%V>|m#}(`aOBPN*)8%a4j^gIC$S} z@jd6>@>HVrTF?D&%{K4LE$GeV{!(iQc{a-~Te6F~uB8B1BT!WBNGv6iZt%312V0+X zC8?Z9gK8J zt}xw^EFtXhAasYm#@6-^^Z^yLvC>xO9Lc?$4X(l+$TeFeAcj{dM0B8co-9(y_MQW8 zB>lONr(#i<*|q9sIWrW)$VUyIqb-!3zfx(tP8P<@#y-qv>3NBJrhfcdTSOQvkJ`N!#<`=Nm0H5-IlHZ#TQQYqjvo50xK5h^LDnYwSQl>$%j6e8KVC{&vtzN9gOE_o>Z`3uk$@Q#596x zp0OxM8jcz5M?s|#$Tnj;s#>&{{N{I?gg0Ff?IXerqWnP}vLa#}HUyfgKUd!#a|lBL z4%%QHrP8kWEH#2qx0E3>=6Q$Euyc}tL&e|6M)z~?2%k+}$abw{3F+Qv+V6o$*H}`> z4+Enk+|Y&1UuB~Zqo!1&m zV2qVz_VxN5Y0{OKLnrs6^(be{&D&|d4uw^NVbpVa)}IP|S$JiBQVbmgNL_^OG`ro1Gr8m*VotF>Puf0 zghKzohlVDI&T>>jdJ6ZwjKd)1yYcrae0O{B*+ad86f~FUyAq%#Wrx#`eR&6K9bBd$ z()@?rk4lyWS)5MyE(IA!*-ZfvSq2Vc5=0@6clUF4!+fSAa~RM0HyQ^LLv?&T9tB%H$ulRB-dB6LN8GJ+Cb@e)^>nNP_KhnC zd)tIs*J6{r$PYU>l)}1n45$f#Q^^nlCgPkQ%#jD|rYBQrtZW?H$=Mkh78GQeTsGQT zT6kBeokx$a{X17%ct5e*v~bz_jtl4}Cnq&(^nJc8w`hFpjrRMYu}rpmg?J&c*-NN7 z?%Tf&@}Gu5bffq$-(`jJ70 zdkFd?z85-_p>C`@^6(7=kv{AMBR9u;LPgBN$Zz9-*|bILFJAG+V%gJ34D*=?yu}%d z`k<-0rNb8ZazY9OfH`<(!*YcgL%u&wN%h-BBZ4h>|K+9fQPdNerVyMUVy7CJF&cWy zI>T82P>%pu0bQL(lEW@{%MU8f`e9R8Z!2LuRFLLDV412xh-#*OFe!n-7-9h=xp*X8 zqY98}pRGdqi^2wG|C#(Pq5v@n;9^Wn%+_50+2_8&$NqYolZ%TB!#G_ecq$jqSBEkjX`2}4@0PDY0U!x+^kFHYMh&#Z ztm8IK#ZZNMkT(%krK1B3bV}jrQ;`Wk1uHwKMnJIzBAc)>F$VKy`}6P?_MVq@s9}X0 z^eNF#bdp%qBIRPLer}`c2Y3GGO8ImJZkZT|!{x!j*DHaBy*BN)QsbbP)- zhuI>BegWFH`P%9I((-aYDY3^tw7n|}3kz=b`L9(OXgksBUbPK?``$nFyk-hksikO$ zwW~zw1003T8;WZPJi?I3GbjW*IWI;MEb5G6K26Tz$u<4kQx!^@4Ye%E;4Y0~iJ^L@ z{>x*G4Sm?6{T3Bht55nHlE0Bz83_%Ws)1U>md4_^x`dPqw~EaAeLY=ohd4?ic*iuX z;gy}>#6}Sy6t@RPT1hR1T8M`b+(D&U4tr)tT7^Z+-@n@v{;ppmfK)%lL$>gD52~lp z=`uuIXZVGY?@FHr3^iZhqHDcN{c;SVIL+IAzmdl`s((;-d~+r0Sgo3tE~L85h7=(pfFoE?9P$Lz<_f1T*0ly zEL)hn_#AWpsH=QqdquU?){4R>eGAmc8BN2Y+$yyi?Yev}p+OHecGm-51ctEPJ3Pdp zKu{q*DkYR65~6F^KuAPKjshe}R81 zN#Hl5?M+}|!tQ2CHe$}S6-fou;3}T+TkPKVfRi;u>)Y#Xt`7Z!e|&R;<`Lh5DIot* zppiK+I3__85@HUpfXWwhD%QHst#|O-KD^WZVRYHA8ZuN}6fj?-zi{C>IsM?sn<1ul z7kmG5z3;J1+!64Q7kb35*nh{*=(%k)*=+%D52v}gxWG$$PB&gMAC^ZBm`|w;ODihq zG2wgb1c^}Mm+Op&4<@tBw>2FdZ(>sZTiGP!uCj|V28xYDj)M`|IvDHlDqB;_Elz5J zqBh)qO~r>EGTu@fGKHYImM8{r8f``#Fx!K%&Q0SU1Xl6~V4>M=JdfGsO*M zcD7L&xqIo&ldL6%w`>VE@mP^EO0YI;mmxws;&O3|-AP4Z(bw%7*zxfr9AOl36I^`; zG0mZBg^I-X(dA>`P>G-6k+|jFcKx$hB?Rq5%!!q1|CKgHrk;sx`{q! zgGy;*9cm=heTJg;5sou$kw;15%#r|GzB2CJu3gVhH@MiaW~R)%Hz4Ox#YHM!NTbn( z#l$SF1FnxNQs@gix({a_^_LO9Q5wMFMV^8pK;&Pr(czXS=+#_ZJ#~8D*KvM+9z?$T z8TsX#!jdvFGXDMhS2Fe+>yJ>8-V58M-gxXh&eX$UVJ}SduROn&>y@jsfWk;kqZF&tWNm&a ziTAWNuV$vnkL|ycGQPD#&u1@Bhs!*9T6^wn=FijJQXU;2ZvpW~_ZW5^EjP+91R1F` zX{MUxHgmoIT0noX$6npXO3~$czjN0L{3tzEV^cq7%bZC=u{Rts=^3{a;T||TCb(-e zMxkKye}7bfxaTxcT$1eN!FdU?(hFk`tRFx}_8rH}7|Z5oXbkN%J^i@4S>ra~Q*Kqe zUI_D#hb5Q3!V|=6QOfw9XfW-O%A!!)Ok+?@2LmLhet7>H*Emv{+s*y=U;aD|7iE;4+exE zsYe;O0Wr`^`zviuhoec<^z`&TLU~)4+-74RMDG%~FDiHiCiV-(#lDKq}U8$rg>wSG%a_!j<%CRiDM6U>Q78s9!Jt&S8;G zj%H9oG2+$HihuD-X|#OH+bkm!^Ko1KwX#SfkMW7(MdU5gqONL*_jE6} zOfccEqYGit#jhz)*%B?&=?I6_LS18vSdn^Rv~3Fy37kNDzLfa&o+oA{iC5XSgxJU) z+g~1A$Yuqois`z@i7Qn#mT2UlM>qmSlZZX>)Kan`TL8ZU8%Jshq^69bg(4=netBK_ z&~aJd8@d_~l~lwJUS#vr(uF+lBqcwEHEWhsd%a732d*(cv#?hGL!lEbJ$D@NIr+6ityO^yqeVqxMOLkQ zu-Mg1%M>qa`i*JsJwCsKW>G^0Z3YDsgYdXUM8(L?UfEsONrNNxsb>P*J_oN2+8yr{ zf?D+V!!2i*H?LQ9so}kq8Z`=}du)AnF*HwXuL|=Xx*I0UH-|d35XlymO1LnYnec3! zTh4qdJo(#Wt1FKg8~JU_);ZP_WS?Ue%9g;iVg|aItjrv|WfIJf7CK>_+t|#2xzC*O{2k2TYF?ES4;Nkd3p^ zaUXnupLV@&X6^}g)_o`{PXg}`Z#RV#-(oLsADb9daFqOxe_Jyv}ji3$&Z7 zZ{B6$SIe zXCNZdI3gYrPd13(226?bh}ro@*&5DwH(f(uLLr{|twuEAA zIj2=#+$)((m}9zF@kpIeY9^L7-xPefNgomzo5Nfyn?rnTdN43I)jU#fe`j9kFpi}Q zW3VWpdwV`eTBA|=b_b&Q_3)zyBK&B-PFKNT#*G}$Gs0G+`{i~WNCz`hr4JTu33R5Z zvtP91VotAHebAgQ%Q;6+aCsUUl_U(MDGrP0EAzNfoBHb2rT0K8C23JKm47~BTw~&g$2}vq$a*UmWg&U^N>sI1A}W&BLl%5 z)tiki*u;a0<@fdV?G49isH)@MwX--^db; z+%G*M@iVUa%9MDrwmwp}h_mhT%2+$}GEQlSYt&d;q3sM}EzEwvuqs$ZyZGj(j1#7U zDIYre4YukLRslxOe*yR1m#FwOH&J<{i3S+FbI=12>kvcH`Fb}rbg|t20mBmk;PJr< zCYcg>1o7ZUz2u5DLTvzIMcgF?`v*vxfCLAY9?dr}%ZgHP)f&gca*p8%hVjgHl_w(lIMWx2#u5 zn4PLam6w(xgl#N@6Rd0MzFIk?efalZT?6E$=_{zQ(JDX57Qpd$`)2{N^df+H)$hGp zm~a@sydZzGqrLgQ#}XS)Ic&%5bLeAs0pqDqH<4V^pVUw!O(_AAkPpUKcw?Nnb0vl} z2OCP!n!M3_dlC{PnJWiC-Nd8p>~n9Dej$!gA&Y7ADcCACIQmh18QcLPK?zQ!aE)I) zYS-61Z~j3v=*YIu5-X|wTDAT|^&^(JT2hpul!-#+C1ddPg%GG`s0w*Kq3Lsa^nUR+ z1i1u)6yy)2mnr?|*Kqo7VRRU=0qpcgorQ|LB+u_NXhH^xJ6Lmt2~;svaC9+|E0y5q zbwxS1s)VkTgOifJF0umkqf3L$;PGHgBJ>3saF;>0VcIVReBaC{0&^2Ed7y|wTX*yf zIgex`uAwn!aU~)93~_tv0Vy2BJ(uUS|Mg0w%lujX-;`%76+ndQ~`FHS7Wp2|U=5 zQ7OgH%Xf2jqRAR_ClVfrD7a~`Oe{5unbXcJC_tiG4?|$nA?9b*Lmj*6*wW~! zbx^XfX9LVm=k+Zg;_9J3@GB#-h72APA7cLaE7 z3Tl2c!EU;i!9A5_HCq&^fsZrR|D@SA#$yrqKKANh4_gp3fB2IJ?y&YMbaS~!jYw8Y zfO?u&Xp4<)SZL33)Eo^cAtsE9r9u_Ov+!N3=4X0*kCl)ezr05+Saq}aFpTs=>07G< zEvu47qkm++Ta_Z_gwwF@B+lCd9-Y9uy%Mw zs$dmS8#)lfjZOB*RpzgWd}$s;aDhYhl_kJ6q(`ydU1V-%{CZ)eO>(o*FQTzN4-xxW za2v?X$Y)R`j_?peEdJbA&$Ix@%G$+kW8`-f!$$l5fZ5n^| zm-u*!Q83wmZDgNp0-!vfJEViG%%a(vYfGH3C^%_-#-!LH(eu#iJ^ z;;KRhw3@Dw^{%2x_}7Hb?DFO&pc1AS5oZ1(bogcET+>3wKx6N5XG#R#x06K+TA6xY z)54y?-e@ZYF|%9pXKTpcu!>yZQx^K_vyjrD;GkdM;)0RD-zUzYDtc>r1Q;qiF2O(= zJZ(B1&pxJU5{*RX=Br*Jz%ugGL*nq)N28@JhucFL7CmbvfTuxV_L}#%Ct*whPP7Pk zQ0R@<=)lc!W?^i}A+e;F50OVJ^UP`Ymh3<9h}^-+K-HcU%~T8?ly+S|w87!&pICj` zCmL$Gc}NlU8@~eE?S%OL1%ZKqU!w6lhT%vy6}wB=D6aeVHug6aW^hEf@BXAekXaK0 zlfZtPZAmO2+a>x1CR>$?|=<6A#7>%&;qiyrq%xwRKa~HLW0i!9eIISfk-iOk@7U0&rc5xkKet-sl zX(1R)Ex6=%v2I@(##s0Qb%i5|6h57+Pvaz7iUo1TNxCDcohkP+h0%#r_8E99T!UQK z=2-ZvY^_;dOC!xT??&%(Fd$kJ*(dp4gi(Y{Zr49xZS{qzShtc-oY@Yg<=Z8RDWPx6 zG^l*p09xi-5##sjVh!6fYB0+=KB)$joWy+eNTyv=s!NI2Dc!2awcr zRU8PI);r+$voZvs7Q;yj?evdhk}5pWRqqZf0UV|!0r z3+pX%__7T%p~CAqlBfU}#1)qD5?;~?avB7wM%F5j2s1H}fE`42hD&U!U zGDXqB-mwbQMTlxpWVAnVI_q~EUQZ)n&!yiexOSN-MQ)zcaTO^So|Ue6*CpRZo;9!P zqubUI!?A&Nayj?*@vlI+s)FA;#eUXTz;j|?Wp`EH*`2LkQ98u%BevjQ?)P$srqx1! zj}(De*dXVUXodIN2m2V?v_EaLH9+Es|5Jr{58gQ@1WB0BL9~+m=<0Zn*4)`~j%d&< zR=GBN!FyrX1&C4ea8taxn;QzhT-ZS|m}rR`X+l=0Q&_Zg4U^(b%p2&FhV!g;b}~gP zfMTsMV6cYO6PD1L%0?_2_tnrne`9T>{J-!ahi!-!v_YW8)|A}W`7>p$i+i1tyOEOo z{$VE+k=wM%YHqWh1f#r(;#tnox9igiWC$I3-VQ4IuwGiDB-na-h96h4GVbmWx_BM6 zdoSFV^~JG)B%s>c%@-n{kQ=&N{x*dTcXUTcLiBLHilZjGwd9?^* z8neI1MfOC~du7Xmd4SaO*F*w8agb%l{~Vvr%`f|9tZ`yx7A_U8#7V<-*320)!DMuK zMcT44(|EAr46dfLmi6N}rB0zn_f2#T)Lq4Y7O7~*e;0}$`0^S-fapD$)}d>)rTKWG zurw&#Fe>DyH&J;f{L%SlkKP$_`e#SfLdZS4(@|+HEcI=%4v$znIr4pOQS3R7r)5a6 zX^@qf^kOu4*>AOO_nuYlYG`W-*MMNc+mb>$JwND~OLrepo&Y3zI$h*z z!uZ6&<@T5TI>=qwiB)7JRHh_~8`xz&8E7)Ta(DEq^5>G-_w*VfOMhIyBiqWVhm|fj zG$N~pNB1ehXRi=vc{T52;o}T{_>pZ>PtlKqn}$@hCiroP|# z2%65W-A~G6^tE+4V~3OR`!N0RtoutQmHA*+$ye)oZXI!;U%VqYNG&FWW_u1BN0>02@7=exza zn{b4e+2pP{d)jCFg}b_g@5AA&Dh#&WA9hhWj;_ON@hg>dfjyp{A!~DlRv3GEYu0&rS3_}GzzHKd zWQwjw<;2^B>?d=1YQLHNvc0Zv=|)Z0-kWs8=23v{+_k3@H|_zU)Z_FVo4ic3ymVCI z+nbBUoFe%C4_c_XwTB0p9>KEMW9d|jk3UxMTkn^QOCA|hS{bRO(ILi8ID*Njui>h| ziuubuo*`K0OJY1W$E!Q)?E>)>Z!kNHUN2!e=P3)GM%uYnerDSrJAnltfjOiQF z1iNRs?2=52>3u!5??%4d>d7UoqBljwtL>4D?Td$89Xmf~P~WIUY}gVg4~I_PS-JS{ zuIN&Uj4tsT8>6juvy<~?q4cWEfsMhQ)T;?C$PtUAHHt%zb~XKd?CoE=v_(yq$kacC zw8ukl!;J%PlPKe<>uk4+n@w1sX@xu;f(rvZh?g`LpFv}47T4^+RtF|w@ z4bHf8e~Kt-hciVvdn;JvjHF>}ZE|$qpVzUf(?6PwZBOCd+sc4O*hf} zrL>sxz?;Qj|Hht$Y#qt+H%Hdu_BvViF#4MnCN83%R>TnZ6*xXz^cxH=x~8lzxuw52 zVUfeMm+)u=Q7#VmuPmDZ?ZZl<166;!A=KXi>~CrXtl@5*3>cBX74RK6gwhh0d!#UK ziZv?pHA&m3EN8P}ZDAXdY-*{m48?Pu9H)6gmBbu&OJr(SdyW3ag5&Yd==9^&ZsM#1 zch&aJY35zum2!+z?9Zo4E00o7$zZ{%6r41wL36EQGf-#=?+?4^y!m~>b87vVk{nsF zsaJ$KYZX0K@*C^DhCMSPZJhHjfsE;Kk^$662I5)nGqPBc&a-n3PPfD|-k(Xpdk=<| zOWIjUr}>To8H|0Ik|QEo7DbHYHFal?BW&<~Dsb9b2=y44A)NSO7GI;Th6pBRZbCF& zx#rHb%2zSK$K=)<-jMf(+N==n$1%(2)Ua0<`KOvh1EWAKtc6M1RbHVXMxjC09L4Sg ztJ<=$Ij9T0e4~4k)$MV(`@MWdBRgyui(| z#?wi?|2g2*tYJO(#1&JOG>$^CLOdFcmt&eYQ)@?P=n!mI$)B8nCQQE}#Kr20ZJ-@2 zq+eOoOS$fBY4x^O7HgsCTJ?0PKYh%kjP;-<8C=ecj$;$=yQ8A!_8T$WJG>}lZ>Uv){gFPB#%<18O^K)qUgp?7$8uO3>80t7$T-QP+6D?N)* zV3$;hLGMzcU zThfyHd1O&=X#ePlH+)-I}b9s{)gU?#?U14#Wrs{ zNVB9s4?~)faL<0V`MX6@&z1JTju_aqyc)oKIUN9T`BmRv12?xkT3%!K9q z{AD!YKZ!6?tvM-~$xRnpSHvn91UI8;Djy3pyEH|OtIcV)Z%S=kfXdNxMrw$KDmYnl z9Sill1;QmIttGfuZXF2AimX<#`WoO}lxuN&k{zslOimF~pZ7&C{N z+gt~6EYgYMoC>D(Hu5{;!d{1lHkf(2Vro#04*UD?J2vdZhO{N7eg};A!h@t0m zZKBrhrxXf)*=M=~7nvJfubVo!yL>$q3Xh!JA~$`k{IX05GBBKkQAMtYyJKq83U*vzeB|nMr7xmwp4raJ;|RD=Vvm z6~l9wXYNRqkdlj+)NotbiADLO-r!=TcbzkDhT(j~b9Q>*bZyd;YEYPJK_0D-e)*;1 z>~9l_@r_CYLA@(^Cp+7eAP1Ab;oM!cq>i|{DyO+NC9g)e9o4eXT;4J!9^qBko0Zv2 zGr?5+$HW4x7gW9gNj+f^vQYZ(EbucZaToW~Q#~L)(j`(la>0fBP(1_x!J({!Y^883 zKCTiPp5Zk!yRcMCG=1YnniXNQ)9SaL`8O25I-eZV+nY29QJwIGRPVQW>%-q^Hn2U4 zUbk(il=wSqI?N?n=GdS=Yz!(7H7%io(DEj{3ESl!U$lgc^n?h8NGRy(36dqV} z2SVM{^SlrfvduOoy&&e_lQ{3_DVSP&1oF0Coa0=Bri3}a@;q%{wOP>dLEAOLx!C;l z{GL(~PInJ2xzV+0{>GTqqn-%^=CPGbM>}zi&HC&`(@HduvOREq%aFa2I2dmwA9>p6&&5@#4bEx(!w~ znWmNnrkD%osym)BT=d=zs(xg=$X05pL6`koG%vC-~k0KDS9AqoG85;L2uYZ4rb#qxBOUvJK zWa#f%(r0?6p=i1WCqVVF+^92o4PrXI@*Yl4erhw?TKrC%j&$D}PbBvPY_E@znv2oX z?2k{Zx;{h0J>3@mb+(+>{LF zExFJdnp5GMYeS&_yIZ)Qwcw;D)pZULmk_-xpq*U0F0P$nuYnOG3EQV;S>uAbwN!?= zE&1+bUZksS3GOQgfP8{ic$lVhVU)Awo1C13{Ag{ARCI=>)|6+5~a&iF@8(M5-hyRxeQ6ikDf@I zl~L%1V>LZV{hw_n)k!;~!GDm!sStlq8~eyY8D~ZhbiAn44gqAZmd!2#xLJQZ zD!?0sdKQ~=8KoR$8N&&8B0p;M>?T!y>M8GkY%dC)r?EXUX62zwpb7pmp16apf`EXt zACkf?j-~~O$1%7YPV4EuKez$YuIC`0Qxn>z%A~IqB)$J;_FNSjJk}1$@(#_Nl4SGt zt8#ujKvPuxhPPJ7$8L7=aHQ0hYJmgK?E;&dKeOOWg;S|ra1WN>F6if8R57v7OpjK5 z2CF*-YC5X}f8|MY68-WXSiJNT!OFuKf(ZBftgV>maj$iEO6ehV&oH#PAuQ}7;QyL2ZC zsxXvy^~#VFX+bRi($!!`n(U?8ZOUJ*NkxgazF~R?8@CU>oKfDwTZt5dvMNOf0|Dt~m_Fc&E`WLm?w;PAqxyT~DmUl#Suzl{S zj{g%F4p%+>z3lZl->?1zH}$od1f>S!E)1g+y$`ka2a1ly&4JskeZ8aj3B42l6aLXR z5p&^t4&U75U2h%`c8X<=FT;Kg(;Nx9_$YGSf%8KL@5AUb>z{e+`AihsBNCVNzQ{(R zX|1G66ect6;WM3U6~IgF48t{JPg-Qnpqnp>YUKbIn_usUkDbtx-ZA?4&AWzMSoOaP zZ9bG4x-C{Z+J_xiyC#DL*s#&;7mZsM)03tDqJPEJNkml}E#jvDL@*D4!!`@z9E>f1 zqyR3a1YW~Tv(OngQ}OPDdF`1i+o0b@5Nvkdvl#Ls@%Frbu2Kxy?#-uUMG6*8bFH7F z1+!|z(?Yb`MYqsqt*);LAa?HkzTcIVc&4Pq`l*J?&(P27u(x|C9P>F#{b0^LD46p* zFUc^YZc)f^16z-qE^DGP61LrGW_iu4ZfUE|J13UL8`UUd3Yy1}hT$J= zpNw}Cw<;TiyL_G>9e-`_J^o!KvgV1OnQ8)KypO+0T4hnI)^?uUUBg!>TTX#A`YWzhNY|RV_*6Pzz-}4ZjcG44F3{Vpu z*XOB3eOc$rVH6H#S48J(OFyZPovNFvq}5(ztxqlR=)Q`W!3cn6`xA2z&Iap#?i0Y#{K$L zIQZ3gVxH#3q>fQ!qw3I=N~@#?$t~4*y^b+e-aTzsw3e{5hU>9EI|QT0yO@V6mqC}X z7%9iQzpOLm*da;x!Hb1Bo=<130Xcf%PfCZE)=J zYa}(0@Km#WZ$iOQ*`ec7h=WkY^V`F;G9!jR+)btN&F59;ntPxH(<4_38d(rUGeT zpp(mVW%@@8qgy5Cs^*2Fd~W6?m@D<(x;P!CtKT?N-ub#7Fwwe78H)y$O4=hknT8_8HjcoU z_z##rysTwR}y9#`nv*t-cq>D=J{+SaV5wV991AoWz7JHI08ZWS+t7a0#QBA~g283@> zw$&jt$;G+M#y8HjFaE-B)I7w@o;b5MnwB%^fzaMK!zZ-yVtCp+d0vEuiu#f3SOpjv zuaExeGG)!v$l24pU1*jFsbQVo%8X_0s8xb9jJS#?Z2QWuK*tHQ8X5%*rI(#IEk59> z60YXV*(sj_0FCYa8OkGh8B9*NM*oLM<_))!!EpQVD8ZINbH=SKY7YKvMvnKg7ag!- zOE8X=Vo{r31xX7eIpNXZ54lzj1}hoAyk@Z2>Bfm%yra5;DpDZP(soIz7N3dtwW$K0 zN29ZzOh0~3%u3>aNxCx4lKX2H({*tp9(&i}iWsO`a2JCT$S~wA6uJ8zu}1b|Vhil1 z1g8l`=e3FO>yuv7I5v(`n`Ujo=GP;TItvkv%U(~R`QZ>!A{L6Qh$8a=al4V&ts-R< z6*ILY20Hq0V47^-j_PHFPG1_c?4WL!+{s`rZlcbQppjW1Z3e7;*~|!20m#8r5$!>d zxcNUv=G{4tQ6get=5vg{5;FJYJK8~GU#QGsE%GVHOZ2`taM3W@t98{R;CWo`}M zbsl*Yv@~paQc)~^w?jw1#fC1MS=9zka`8L$zI)6*itT4jW0p5c6amEaW!ooJ0+d*^ z*e;LJ=m4RPjK*O2&1+^a zfTO-;qL_I21dF2bxRh<(QnJB`bZ}FxDTu>sXUlAQSLHez-IqXzwsAk~gmMpNh*p9D zSvi})e@3lTY9yIJGD&Kk97666k{2*UFfSXSyAoD%yzLW$N1sevJea2x)2utwp^h&t zC|9lnD$|?H2abbonGe~p^m+RLbqi5^b80!yxhv0>808TfWX)dwU~7THBY}JsoD$AF z;zbaLV>Te`(&cP0H2#R)bt_A)agcVs7HTQOc3olr*BD3^VJ;D|4U2tx@k;l_3sL{G z6+I}VpBh>R78>^$*q&WfBS2d)Ul)GbT#ip^SSsJ1YOdXD9XC>sOz(av!9s!CM5(NG z_56%on~$6QHTmcHo=2c{_;nzJuUpHZb@cS_l-hmpz3CWz=Q4H`+C!*X?NaID>>A|h z?W3I;y-7BB>DQg*O*`qlLqq85GJo{0frXTo4`IMr6SN~o@$?w;WPtBN!h-f~)*E~s zHtAL73_d<}mv-AgEiZx(wW?AQ9+kU|+?@!1+1j8PakP^(GlXn3eOL~yC9}kFE zi1EO8Im?N^)13eB5=$8q=v~Y8Y+-k$)cG~~VWC96IUS=zX4@=G%h$q3*f2 zXRta{dMI+hERS2f6;gv*!P+}P)zQ5&Ccnk)_}=M54MN2KVd)zj>+FBGYuh+0Puaj z7!BLn3lJH~h*l8>R)8jgvwtOC>@`>qBp8=yTxc^UQUaPF1yKSW{XIoiAa2KP)_}`;RZi3RcU~3^wFInE3@fw+*iYgKerpAivdHoyC{*7 z@kijCW%^%crS#W89Y3rc2t+vN5ywsG=GgIiR7IC*5KjWYNQb!{b4Fw-zfxnWylHtw z{89P3SjuHGB~wv(U$(Wtj!#kz|7WQkIyxYIhz*YAu>ETXgUjgu;RtY{S5f|ea^jG@ z>xNu};~9i&zX=ZeW-14P(K>m?I*7|CTDPmW3#;!AA=Cp@5MGWOb5U4`W*K7*3VdxY zq`hcbD#C}oX1cUmIaZko7kKgec)q4j=GVf}cP@#qQA@c~E%Kv{G2?Y|<@9=lJEv>R zWUH8Ht7`LQa76L2v;%5?KA0ABgW?c;`eC_hvJd*@Q(5$~bI$TD?nfgjH7yNb z)u}^f%9hFYc43f>g*Qaew!bR7Mb?{HLKhv%kGp|ctsW%g9H(xNZv?7eRhDm7#lKEm z7Yaw(0EG+ZiF6SH7p7+cZCd=LGGYaK#^4IcPc2zn*&uHwUNey4d0m1l@l{c(`YKq6 z?-h*XX}^7Y%A7?D>>7&oMZr{C2^0<8>wc?6i+y{Hy>tt{8#m{rKY2JgYhq6hcu{}BDT`fF>dMRB@0Ch#o?kyo=d zQd7N$2#gu76%7edpH7@ni&K|=IEsSRIrD$`fU|7Ia1`!`h-*fVkd1VLx(qNqwU=v0 zq{!>eKS|JxtMqUPX{sd#CNLrpR?U=}1Qq<(sv8@$w-L@JYRmO(>F+Ox)-X80=oMBg znEj{?GaZ7YSg+95@XI=^9L5;6<*y?TFSiTPa!ys+O^qfIV%9`VU2%6&N2FSdqq=jk zzAm??*zTGWRlWeKk?@nFiklxPJ%nTjP=6tQv5G>m_&XrgJTr}e)q2EJ5VICbd=MeJ zlOQEi)QcRz+^?B(DXZ6d|72Jq^0;I9qqJd5ra_UW4rQh#MlhwTYC{tE5Tvf`K1>ap8DZ6_z4A5Qhn!Qm97@^Gua_*6S<6IvT{ z>7m>e$Z0rMCgDr<=3D-K2$?Gm`ueTNxQ1O+pY*ddKlc!T>a*hy@OV=<*Fht5O!B+^o00gkIm?!w*H)jW4D(p~+|Z%Yc2=l6 z>KLK{Mj4`$RN zu2T0ITU6CNIRXQnj*M8U!KtY_q?wjj*(h(8etmx;)%Fg!?mCJ~W|DlqiK6PiBOa^M zZz~+$A3t3e#@>^bPritD=_&S9En496PNA+&(NEcOkbVqJ>-r{y@sF#R>jw!oEggv# z#}69f&y~@*uIjqWkXf)v3qzg^I9@3`X(+mxhk%sNQ>%aN8|aFngOEBJ7Dgv0iNVqA z$!*WLR4xwDHK@KhSiAyMHj5l2^$S8sNr_(^DV3#<5$?M;2L^7+=nl9tnpc?B#|Gq`sB3BiQP zf*`&(Gn+@YXdRghf01MO;$B{LZ&b{JI#*Yxx?9Uxjz@k|R8N7W^mH zlgf4eH&RJJ&#$c$TS<&0s$}<#k(M^$bR=qoNN{LzvNLg0)@f2M5+9P1)PBy9zes)C zR6?bkKKS`?qLahAS$-no3xlThd<1Se8H$)Q8n!Epb-RlG2Qf9)@8y{n6pwsYX*g)e zN+UM)dUmx=iCef2fommZLjp+{Poq0QnzckyT&LP6+W!vUYTsMhcaz2mQ5Ip49`ZBe z;|TuI`t&!6Cuy`>&Ga=RE*KwC{wOgtI#?cYwGQJ~ZK&Cetg4i3Kq>Z3dY``182KQ% zN_=*4EzfOok*OYO*j%&p{A zd$>UKRP7nn$!&j(e!~5zY2IbStFuwbe0rQ=F)rDcj2*R?TWL5q?_3aAe?L`>v2LRJ zQCg%$Wz#&x*W|2RcOW8W70!fxV*3w4U!RKrsr1N+^6D2lWcKWo?$k;3-<6e=aPIm> zj%6ZS^9qV_upkxxKhokU$zbnMpn$jjo}#Ikm2mr54~B1#j#2tT3c;%6(n#d8ddjmv zy(N>~1@!Q|TaqT+WyG8Q{#YWl6qYJtR;VU43A>zh3l{p{wI*Yi2# zt0Y`o>Wy}W57r6T2_N`wWF{;U%jG36`?rii%_9m1Kr)WtozyL_)ewBit5;Zgc-=uL z=B8ZTlzeo00uDb7zss=1Th>%$?zq?6o4KkL{@t$*de! zWXVq~4GVk~mFFHFB{dme?ftCL#nbZ>NW#HSyW0dkqwiLb>tnqS-Dq#Bw?ctT^8po7 z;62}MKL~&A=I9VVEt=PMxiNWW=l(*c-qBJFJwLfl{`_~I4Vwj2Z@%`NpQcR75*J5f z&I>E^5<^dldi4^x=Ep_{BEXg5xzWnkeJvm+0oMwp@PbUss zdL6CnY%H)gQu6_~?s#={7JmNR`FkPokLPIJk^JK9^xth@`;%V#_D=2Z>sfVQ-|LEY z#dVgaa$e`orAPSB&!jBHJ*x|q3Q4&qFi-y-MYA!kmnx}|wZK0o$$tTK{Qe zRQ4K4O%Q@40SW$?bS4;xIqv&cs9xR=#J)+fZ(vZ^yLc1Q@jU@R(*+p;)U~If?#$2I z?Eu)Gp5y!peUJSPLBaP8ziQUZc((lk!U&Sb(j;A$ocM5G z>SVmh!8_q!XZ>5%Ugr{Zm%Va~@8=!vv3gJM+M_v5x{rESVwc+%cH}6)obmnscKGO$8-usW_Jj3Hl@oWU8%+P^)}CzL zNONTl9n$62yDctjQv|#|8enVPFa0z1s&(QpM70=nl@*cP}@B|vOy<|Gq`@{IQLzgoCI8tmyyQQh28v&o#s*H!kY{()EhotN( z%Z*gL-N%vWPyF5m45MKNKW9!G(OOPB$CGu5rTN>FR!l{XSP;eCLIdw}@2ETzXF!NK zW!%usczHf1P1Z#m`%pU61I0;BEriM}cv4LeJEZr~uShw`OX755&=cB6tPbh*F>)2T=jR<{ zEjK!mI!D^up>mm5<+n9@ZKU6*Y2`oD8;lk*Wi1d-j8z3ZdX1sLPYy!jJVNn!wW$ zo~1G)$P|*5rmoU~l2DQImZo0wKE|{yC#{|{SA~T?O(lEcC@F|O2wFU);fFBVE6c7H ztsR>UL1O35d#Jdtpl&V6K5KuP$w(n^QdC+~7>7xTA~M*dfhv2(9I*}=fMs)%hY|R_ z@$sVPk^OIXWdFBcFz>LEVw$yBvch-Nw?h5Ut!{EGNC+iecmX=)quf7|ByPin=U6CD zC7?wW6K1})BUM8)D4}2({jJCH8t10$%oB!jXaMdWdr;sL#t(G&)b#p0ujdTq*L3KX zbqijEP6v+29Od2J54T5|>(6UlE#D>l+pZ{bzN*_NP!d@&DU(z}ViQ+fhdI)fvKvS+2ik;Agi6E#y+nFT!U#vUALGwG z4OpIc_gQ{qaZn@&6Z-<-Xe1<)M?nE+PcCA-1oYK+Mi-S>s(W){OLgbaH7X6kVihC@ zp3s1vF@vx%UQ(oxG^TWtT3FHR0;Iu&apUR^dKB1@()TXFMe=|_sE4>OxUmTWit0>> z-E3kS9<_<*0vEG+Ee7nGB7U)8Dx|{c&-S++8Ma($lVp%B z?L1vaC=k^^$(L+4vww1ri=Jgx28?(tU(HJhVPFTwp&*KNsRU7`C~jX8OTTveRCWlV z$p}+d3PFJprFt<@=FudnDQU7KC`ZkU3mZS--6Uc~ijq!Bh6VNwhk~s#ej6@b7wPVGXf!QnY1jgP!^a$)4 zkm-_)5~agd;R4iZCON7wWS%6?b61td1?VIOp`w@-K)z_{!QYn_c#|FSmewP=fh$1g zp-*#Dz_O&5kpSx&6k2dAoe|O)HW)o`M6kfq!0_2A_N<5;l0bZ!0T=d3S!6)Z#&W&h zV-q(ic+}C&{G?LBy|amDRU@zo3DLVzd}&hmGyAPFjI(Gr`*N`b2Ou(E)_{dZkr>9A z!NIKq0&LlnlmjUd;j|d6sxTMww)Xb-wtVR0Q3nw$BmNaN4jQB)?6xcNlWPN7q@M&~ zVgVdud6D))F-UNm!gHJ?hE#)=DEMHXY+2UA5l`WU$GKdEmOQAbv*c7E#iL<>U`lli z_>8fb93u1)rrnFg-83R9+1~8gR#>25iJ|nmG6hqSn5qb0^o@L#AM}&}4QI@k7gqc~ zFAR!L-qjBZ4f9*U6wAx_W-AdCpU;Teh!Ddi=Wupe>9NVR!xH~^h=)}E@tDy50v(s37k8fdMw_j4>_4{ z7o&%VX;z{JCfa-0ICT-0hXx@*(Ye!eCJyfBJE06n$ei8i3a5o`8|mNIOxQ(0U7%|>(~300)Zi%58Q4XCz9x~)ve%(^@;uX+Oh{N z%ZRAe2B#qE|Lt(Wo(9~jT*?7e%M+bjYRpVZ*|^+KRUNDzZq`VpL})jYCc*l<@Wg<7 zL=in!mb`%Ed2_<)1bCO}4w5RDrQBp`Q4|~r87&LW;dHpcw0+4>=}GDp0G=JU?Cnn= zxPnlSC#|d&m!#Yg3qO~&ERL)&L_)uM5duEb&TNYvUHmUc=&UgXg>64CaX*+SDxeA` z9Gq;BONt3SGPLbzJ7C?$$(x2uS6Ks$MHYg5Tm%o%%`DhT0iD^KGH@yxJWlOarbXYM zShdal$6#<2&;%+=7rF-79KNrtAVI`cJ9a)-`mjFKfH5)d{Z1Tw>PWnzOb0--(C3iLTIl>V=}{zbT+WbjaY2Vr8h#yO(*C$RVFs$fMIte? zkuF&NFd*9aF^p98dK7kL>?kHf>O_d(TIK@$4yD^VQ6{H4#b-nXylx2#bh?d$*%hG` z#1E;uWF!~1NYRe%CVwg*5;nGj+lBqT-a{OZ4A(NDc^pX>**HWVye?u08%sJAn>3}O zndk(hML5?F9irF(rG%j-VgOReJtXhQ54vq=ksu-9EX%PQ*Z_S#q{8^qVDI?hETNd3 z7l8n*7LAl0Y(g)lpEv#wp@{JVyNX1t9I%jrxZ2rk$aueO7@r|8M*JT*zc`BTnglSL zOax+|P?XU#HAvCmeR1>Jw;y**Ov7T^}}qsX6Bg&**% z%;{r@D_wbUA~MH_0myc07!vR|2FF$;RD~o}Mbb^^gs_gICm3sq z&jZ_yFS^88L`np@aJvBkm>c~OBoy`|ZlU#K30#m?1hv%2Ss8lIjGETeQ_lc+MRg$l59M>l;f43riM9m9IX0NX0pg@BCe4hp;x$=fpw zG?_h;ER7F!3?hmto>Ya%V|o=?7~~9EfH4P0wD`n%y#{tMLb>h~!rc@-?Q(zx+%y4X z`@Rj=XK1Zavu4RXQTT0)J=N-;j1ArJivRZeGLGv18OIW~Qy$htmJ%rM;B$Nr1R|6& z<#M8pJK-IboaOL4plD9`<7(gG_HH`j&eRKxOBjccz=yLf=f%%844fQ~L5m|1hcIpv z;S&NOTEJTr;MI|sLxVK3IEvuK1lUULI#F5lIz zCnsv+&vTzO!9qrp4?!ZFJVB$-3GNHCr2)KvE!6jLT&X6f@25Iq_A~bpAl1}gCu~a6 zopXsZ@!YFY-lq+`DqT1o{50Bymnls^jRT3)@CvOz+CgUiW>8sR7-WPb{3dp-+&?a{ zl@T%Y2;6_7H9~ZIaRFdXfespbM7$oL)$@am@JC`k&U0)jPnN$-qh@H(^ZXG3bpcdJ znovi6i{i5K^FbUQfBaD60XXwWB`nPIHvBaB0?UOQ7WO@cR_e{T6r(f;pdkhNRq5yO zn!OSGc@CB}_#0l89-6DW`^`C@%H1k$$ZRks>vyBPbPk^z=k*XP%5BX+C&4bjs&T$4 z+VpD?7PSYlYEc2t2~pEf8k4(ca35MCYHFTj=;%IY429|6Uo1#-Tfe-zwa8O5e2rq^ zEe+;Q;WhB9Xv{Fnhw;o~N6_UcFckuLxpqPj5U>)$)Jq&eaVuKo%2Y1=objP$DQ)2T zMcTmp`GceZH0cIXKlS~5_#zR`5>4e*7#ERPsZEvYY#EGwfQmSV$WUcTnz{&-+N)7| zA6*B|F*bI244y3%$*N6I5X-%4P}Vh)ux#m17*VVfxM*cOGTB$0Zj}H6aXhkU3Bxd5 ztae5zJ|HuI$`uj`Vtqhn>j!oS95jYHQ+G%QbA*YIrEqdawuoT`Jx#O<^f_{%w23V+ z`deBLmW#4MFmqEVa<3MRTf12? zoD`_2uw>dB$raYuwuHcfRzGU);v_#AT|~QeGGYS=DHKJt{319NylaReI)g!|ns1E6 zF)Bth4i!m1#X58Xoc7Q}YdM3bDl_yf$>-)vF}R>%FC(&~?GaG;q>awMHul3pdKE3x zn`lztsl-R-!0?k%0NcGTK z+!hY%?r&qQIAkrJTnunq&pwE&3`cA-21)g=FY0i>*L@kf&X=Ko$=;*^pRQl&q}sR- z?L#$~bTEEWh-`tX#1m8(=tYnljtyYVk}WH+rOj%M57SkMM+}r zrD!mG$O?cY)crYx5aUn=a`-#Q35-bfUEf7Yi@>MkQ7Dd(tDb}P7 z;F3EO!CJ-*;C|}%GO(eEMtO`OsO&}9L^&Mcu*WUN#3bT-qjQtM@{SePO}e7|G;Q;q zQ>$bWL*~;ZPx&E}h?HD_P30Pa0ppBP4iwuUtgtPVigTm9cd6tmyTsQg*+le6upCTW zbfJCfMK@9IX2)_&`~e|qTBX!w+NQYb1f~9GmZ%SFpVdkNK3j3HFpw0uD=&=K4@S_Z z%Eh@W)UPMJ&b>@072AJuw@r@4Uw#)d4=;@Qo~Lq%5)?}UZ#qyehbL+^6qXi{xUSqa zJWLN^6dt4o{JymNox$Ac-dQ=8qsFq(lNy@Oa1aWT3YB5dyD{LkNqGufE?~}rb!2CXj18M zW38qA7_2x=rPK;riCQUhX&MrmYFtp6unLXv0g@MpK?IMR>_#ikU#yCz-77*v-i_`d%O3HlOk%Y7V=+HTtrM$hSn=S!|3b}p-c|@A;AbCcC*d!Hy**G!9nQWA4 zIQ<{U_(w70*i`Nu2QjOhSDqXgVVrv;QgjSaJu=dQq80cwj24n-*>^r7BnsEKEZT1UK0U11^Ej(=;Oh^$eZfQ5^m639lSf4;jnNg+(i{aQOs*0%-L~40t zSBCX1ie0+2!zE0?2^XD1h0$+bG2CCRQ0jxu=^yA3gfi0R3bmFWWfq|(JmnQX6J{+@ zsfaCKOE93)SSa(iW4X>UEXd7tcExSg=%}YMXL; zY(JWXXtL@Fx9SzTwc@ySkVst6wpJVRtXvAv22+86iWDLE)LP|>vd^V)&V4OZ)>(Jk`Crd~D< zh7`Hv(Q&oq-URfN$4hGSeKIeglG<62Qf^bjkZEuqlHqutRK=rspSmERQ4_6io=qzo z=GR%DuZes-)DH|XC1tq)1dIx)pdJbYW?`wpU^Nvb6VeJ`p@RrICXKWltq5PDR7rxg zaME6bT;-mW13(R@{xVB0;w?NOKyt9}S{gzR)DD7zP0mZJ5dRwRi4m0^dr(0^KJ83G z0s*1r)!NyM+a%|}^-I%4o~l?h(#^@l;p?Gv+~C+N;rkTWXJ`%b`wIOE&@4%}6KLC^ z;8>%1wW$l{eyIryPh~QsOB)SEN(nRMn(-h4Zxck*NZx>KanfejEb!wb2TG|h6nLjM zGUa~7EKp>K_5(_{O8RP~($+J(lH-}F5`|I|UG!7mysrD6=OE*J42rHRLB^H4F5TN4S^;YL7=9g4dRhtO?!tNy!Uz-gA?KZBH!a;rMD{W zaF*H^`I$lJan@qJv2(5WZo$ zi_pc}>D!(A+Kc2BTB58|${^UUYL6Nj%^duTBOoLKx#o3e*ih=BCK(RnC;i}Wk%0h9 z9uEF2gm|u7-ERB)FtteC;uaWjVh;kfx^T7ILZx6yn!dP#7@G+b3V@RYY|@+ywK$)} z9+q(8dE>DPSu3UJMtV}3`ZcZIGZY>fJ49U!rhk4w0$8z*kqv%7S^;Ww-<<650?Ch^ zB>k~^8A2w*+ZMz?Qp&4_q;mxHhm|J0K$4b<$u(uRI7}ak6AmrA+OolR^0)^7W83gT zbJ)lp3TXH-NFfT+UJMcng4iJpdnFQVEeRv#XF@gt=-zNiGUi#Pc#`;8tni?~9Rq4S zqybAdys+Q^7Wz{dD zBNu}?Nsn>OI%4&NhFEQj>Pd~;ys;S&m5Z7Tf27~FyxT5s$*Hb)v`+b3ptzlqlU~m(J+`z>p1BhQD5N@f zzg4Dm^1E?l_&r=umRf=)N&XR1LCpBAyD56rS}nge=}~TfivR9Quo_>PXQJzm7-UhJ z=1Y|38xl5Q*yPVJRSJN3cpuvT?M;tC;M#?B9Q?7 zVITxNxVYGU3ZF+v#P!03?wkxKUJJ(Ib31}7=%7Khsc2Qq@sKs4kN1_yf$kJq=mwFm zagYTuDdp0<(Ej_}r?@1+(hBW;qAW1sz9f^ZdSMxrEmR?}lD#kl5EG>shLIeJGF5(h%08#ok)Wo2XW|I z=ak4FGj^Ttl#wf1JvJQ9nQ|K@7A%?75{W|~Ho^}1x*%saXdFsiW*tLCF@u@w(lIOc zwQqTpncq@0sLc1=lqwfY_tsrD_v|{d0eR_LXNdkCxf0Pjxl0#!?)`mex@`2! zmpCzEh03SLGHBpzGQc1c5d{Q1VmT=X5oL^sBsoLM1OkG@v8bZ|S}cbBL2&=Q=9Z#H ziYHVvcmw(#nE;Q5^BVoom6bDp)n2fEx z2WYocShRp607Yrcw?8N#h&l+6$RC526M2nUn?ih#65*}lvs0*GY^AvA>FI$`R97b_ zPlJUE@6+YR*LjGqb?V3@nWu)PYbn*dU#Da`J!j}@&rd+VuFgScr;GhQ9Zla> zr(Lv8{t`B2!&qy&rcT3EEa2b2+64B&!3H;iFE{+~Z@Skg+_YilvKBIs*k(7N0b|c2 zd)~da!JX;c(2HF#u^ySSLLupAbt-dO}EF`Du->l5|JZ3pgbDtz7Dst@< zr8mY@)|CGN(JIp^(a}~4(;6l_?M2XNtLCTc{bV;kj9O3>*uf;`KWfbF*dCv#?bspY z+`8%Jz3P1W)E9WE{QZ716Ny8w+i2-L(enLGZ>{jmk`=$RqjL>8grl&2z-k`9^I@^l zgpFX%eyCk&r^6uJo-=ji;IV@r4|Mk`BH)YG7iQu}4iFcaH5$XKz(WiVuS<#|(J)E} z%jyP9zyjzN!v2GAasq1q6B`M|S*1ZtQ&r4of|zjJaoVGpNFLml%a&3Qs57mzaZg~u z`-X5W-R_eZq~5H_*Pi=!)f+7+L`a}Clf2R|$~m#~Tl({{r>OrKR@;9P;-y`jRVmb= zzZ0d!M-X8Nf5fk1i?NCY4&=L0E7By;l$Mf55^T{{h8h z^569Yu8;%LyK=g1toWa9mXlhf$# zK7F+NVWZf)hwL6G>`RSBLevZw^8;~c6puZ_B56=ZsemkQ%=Q1?L^4AFujUHa~kf>qZyg^=}y%v-xvoFahvY5N#EM^q%x^Urwsl9tAck0lI zh_UTuOF9x|p|E06b+_YD4Zq{jl<)bG;=0FKb?)2x(cxseD1Jc%R9#qUNyIjaVcs~t z_#n5is&|6f>q$o9jNoU$#B}HT-(Fehoi>Dl~&rQKECp1Lg}_nC!4t%zU$f~^q=Lewexj#{>iXB$KkLnq*>d}LRrC2d!_f2MJKcSLxFFzZ`f!2^GRyKWRfAu24k$6=s4#=! zcW#RqkFY1k=;E?oZBq0;Rps1tnozv*+$Ns+-Ddi0Vy6WqTe$Xu&xGCLyIJ7MqIL6X zHOI8cft5n;(2i>dImaey^(k`5x(~4(r6p4sbPQQq7_uu=8Qf=)E9_rcPDC4hm`bdQ zTaWI5|7>?N_CMSG9mo0Wj?!A+0Xn?MiKHqp9C{(rKf?qgWycN;l9PGTzK?*PNNw}L z(SZo|s*z`%k{6Y!sFiz7K7~V$`*N|F*vr&>Vm_F0!*{DY_22K>9FNoT+2BPhB6cV7 zE`hSmxL-*Fi_+@4G!6pZ8}`U%l7+hH6aM3Fm_$T$VS+vm6gsv z?6bT0tA|AE=Kzq?Bj;V9&ia7-x)3?%@*t|RwG%SC!vOO8HRTh!+EQA_u7Jvw$i`so zJocrpv18_mJh&jl(023y=4ii7T(; zSk0}^g_)oBZ}7zJbq|EP*;?=dGnu%wT()+!oh)C}*7T&u-!yQp% zbS?)J?>O1W8tg-g`U=yxKDhs(A)I_LF=NBUm;mqMz~1sJ@IdxKn1zmDh0=u`JGTuY z+^eQF`Z_k3vnh$hxmoK|4i{(Vul-e=oSXs=i;TAIFPXSGzBbm_(tYJ**_wsr6*(bD zP+m&BlB(WWC9i|ysscCRjQYNRd^cTZ{xQ66CG-9z+IHnq;mtm=Zo$f%vDw--lQcx! z`n|#qmA7zCVB-|+$I&hs@7C6LNOoU_u;wO_!7phCxdg|8CMbsv%LFR)3?Rd6A3ohM zMkdnT&3#wavbc|8e;q#MFMQ|~&DrsIGAKlB9)k`Qy|#3YKDR@*Zho?9e7M{Xb-58; zI}g6_`lnG-_iA_i^h^KkhB#HlMnJ*tzTXs8)@Q~%x-RDxSM$wy@)5PJT{yIN=`pTjY1OcBnj6g&vdVE(6#zu z4cq+*hn+pdaC-u3$Y<~X!L)!T_?T&A&Kq(&7Q!q^4wagKyB(O;4w#lskEb?=%+>Xo znoXZA+fSc6-!0#r)Y=^PaqdA2F)}~w##v$wc2QOSZ>qoP*wopRb32dwiQliM#UpXL zUQhM1TM!pPOx#uqkwXyro*>UQTtBg1Q7pysLTZs9GWvD-w(vwg2@2YRRv)8SgZ?1+ z#HUauJfmA*tWzXvaHVj689cEQ*mLUU@rfuv zRSwR@k4H^KJ;xzas>vLGFg5=Oe(Wz|;E=9&gV|v^mG^5zb;$6&?AQAp&%#LiH{RcM^Mjf6Bl){Qtrtg*gf`DRi~=4v4;lF83HOC7 z88VYNwPiFh9^~!Isd^qKfv5sq9)Dh22DH0-Ix8zD44AaSW-}xd$a*F0es6C=hsC9t zr8!u;x3G&BB*epa*^j}=n!RX$(U!#5YYy%(zct0`-#%Qe-wbYjwml}p{4Dwt zzj8V{r-U0)vo1*p$7!`6GmDmdS|Qwk55qZ=-r^=D=)6^VOEH6U#~VfdjVe=TDlW?9R7q}O!88AJ zq3Hj)&@3g40uDun!#^r8v`>)2oCP~fb%nh`d^5&003&^57yvX3-q)pvzL1a*PhW=_ z_amPI0>qbDVO4dLUd`$fk1uDhaDb1i>#??>I9=)c!oFnI*p+<+`)aWZ1FAu#j}K}S zCU~?YnGKwy10&h(SB?+v6@xT_LNqyC_=H>t0}^vjY6@r|r~eF9zCI;vQxxv?RL1-T+u1#Kh{y?Mo|iGyTWGi$A& z^4G`fgW&rPs^DXNY&p9RZArfMFdHt*?#FYZpyx%a&wZvZ{l><|xM}MRzfM~p%n@3i z5P7W#*pzFHBNE$npbnoTb00olKQ|ns%_CVJNo|`tqjGz!q$xS$JY;_a?0_PhtE6~b zIFs5m9R(Pzc_9s9Wkd|Xfy5(BB|+(Bv*faU{UXR*wzo1IY8CC_*>KlRL<+(hxLG^T zK9B9uCXst81i`a@F3_;Ty*!TG#M7`=eLLYPAN%S3l4{dYxVsqp%Q9v`ZJ|SwodA#= z@{l5M8z1X+wARX~d#$pz6KKMbpupl8K4w+_b%w2(PyYKY^n1~wa~UAK1VuD+Jza$* z9W9z6{l78-{}-Q8z$rnRj*CKl1(Lz0+l&mm1pG3DC%COuy&J{A0yTXzx%HGi@rHviYL#n&tQ? zDfNml1}=g`#x2A;&9is6u4|Nbri(`@k|Q3)A0YUb+DwKm=9mO3LzDOngpw!Q5}PbQ2jJuFU|KT1|LfWcNQ zl{b6CVnGwCS{L>Mx>TaD+g=3L^uG|`s*8u@eQZLDRD6t7u4)k06BD6YQV@-aA)Un~ zmr{<}8{25?CK}IFjfN~+6$Wa&&Kj`#%4p84DFcPqdQAGdY<1Q z&?;;{EMnYz-V*dR^ezE8;G!8n{A}XTi&`T21fyQ7pa11`pp0cNhIU_b{CgGqddR zc?<(GA?Ar@5)3DXpk2M3{4~9|T=RWhtZHNgKGc?~YiequNuqlh3VQEAEAo9VKmN7u zsF|LQ;Q;uVIr{N%Hj`nh!}_J&4eYOY zGF%)aY?6c*gDML&5)x4%L%832a_?<{mM#{H+2bQZ{YJF4QN485^mDbPSQcm9V?v(Q ze475Rc-5c9oU(GB{MUQL+V(qh-yfZ=E5J*;J?c`PUxt-Wqm)M^Q-~$-{fpY1?js=6 z>~cJ@#FEks_Yzw%PM**cI}WY(mJDWs{GHiBsRx^F?sbwf=_TKuL9oWQ?^ z9C1#}p&NZGZneB(*Q*}3J4fy!l^IV;~DBQ67vm}|9JP$gVZz4_rz_)lNcRoc~Ueve%YcuqB&k`JAF&zHbh*(?$Qh=y(j=fXN;h)5+pO%|MjQ zF;qVTlWv5ijTjCBsT4?wxre#@9?MSnANZv)WV+AC6qinH0(B^m!i~I*CJ2pyQ-B9< zGYeq!`)P5BnQ@^mXQSjy5}0u~J1TglwuqK4RBLxX4(t3@SC`clR_CH6>=yn*HO==V z`|o9xl@P_Dv;Ccew>YFQ++!ZB86*!V534V`h4PwbZ zqGEgI49;lw6p$%73Zjdx^A0sDk1!$1>QU_O&+A=l5yo%$Yrp4K8>e_Xruf^|{vS_Y z85CF7bPGX4@ZjzeG`JHO+}+*X-Q8V-Gq}42CpZLmcNpB=zIonTb*tw0RGrgj_gdX+ zb#LCz!@AGXDSH0*TYcptaR3wA=KJwnslMky>g{1S#0H>t&dsffd-Tn`&gEL81iqXG zhsGVFTteiF`AI%2_+T@84u|$lhbw?^>KpQL`bZQd6cD5O=6EF&A71<1p3gd?Z=Zg0 zubM5K-VfQm?*QOFZjQ_u+d*W)f)oU5tZ?CB69Vrqyce(98y{HWZg5*`QGhmY$1(RQ z8k`L!G(s1_D{)RP@xa(cUNIGQXk4x^e2i0EIrbx&t*Eru5DuPj)gMmr99(b^!I@8_ z8i9hxB9usP%sl5&c`(AOXQd%ghRsySN8)S8GE8g0j{6I0E4j}iv(-p00D;`QVqtj| z5BLh_yq5(guRyCIV;F8SPG|(4G<*)3;;T^pk9PF9iE5OWpK1B^?#A} z14p_$os;EQ=$WG-35$|4d9>G7>>GvTJbp?(G}{{tp*D?rQti~cm7ef zx7cWY+{xqjT0J&bQ!|o?`G=`rSf2S_FJ1G#vZYtEG_0-jZlE?yU6QZdmmImZAUIwD znTj4f1~-*1ph|>6XUk3NFmzXK?OB<&Rr}Sw+JoEx<~Z$gxY)a`Z)Ry1*NS;mp27Cd z)#{~XNuHP)Kv_I*g4=Ep{4wjDrcrf0)Ya_O%~>^MAo3kS{BY`c4nWaZi{6_Q23oNF zNSLN~Me9n$Tl{>}{a;{}!71C9=`$%r$&@*yhU5#tz&^>Ojg=lCE08HOakbWF-Eau7AwI^&vCzD1^5?Y3t_6N} z;o2AD*vStyb34W8hcIHkf(&O4D$ON>_o1jUtKNds+77qp21>9LpkUm(X$N3R9G>v5 zYlI012fYzZbCV~IoW^p|x$fN{e~R(^PoR2}21k$-ot;L^03NsONd%c9#5FkRs_5d- zGk1@xa>5YA;d+cnn4{}p&DitC(rmy6&-WGOl-^)zyz*%|F42SQ`*X z0g27?D^g1V(@0?)zI=T*iD8&ua+t0EJEV1ksQHW9U0$n|=eTll;jBdl**5V0K`1i< zsnxH;-ULp+>=N;9pL0rHFgk^{BBQ#zVnJo;3&8({H7|?T-Vc0hv0Q0zZO^EM1CNaz zHrzM_+OL9Hj5_5+3nzOjaI+2kIeub+pG64kMMbulO^KdX0_cDdgVgy2<$!B6W$FCB z&12KDdABOtprlFnMXt-RFX=$PYu73!REVj(ifS`|m6h@7uWyexaDl9;&EXAAV^~ZO zSxD=$*K-g5XAH}H3>$#lXuzjEN>hjuXE$C3X#gYD=RqSE=x^7O@sXG6bvS>}Gt2W~ z(MYV^5fCL+uG=ey^ya|N8Q=`!LFm}v?;sdc{&phxv4B5MVf~LL6Y~A&Z#3h_@a^sD zDr6K-z1W|Hf836+`mJk7d>CZHi54d^=C$D>|EpoK!082iX`o>OPpy^Gz{)^`K@=M) zDRxo=<1P#uc3mWSGniU2Cs-iuY!3F14(X|I2qerkPoRd*pkJgx*~*>do6?^flf?=6pa&i=hKp{#wQ!)c^&}E z6)}&>NSO>_X)YrKw8g{|IEdA=K`XIo07Pj(b_=*M;bwX+aKw4(rNnw`Bg?)6L_o~W zao_Zu5_qM*LRCOptZUIacjG-yBgzyNNsJJim`5}J?)m@3oeu_Z(g>!aw4bIx{QDAV zvX9Kxy#$`fbV!d*NGQjbkf^=XYIE43O!xcRZ-}{&+T55B^24gO?O<}yn?Sn_XuC;% z|Ip2FkyyQ14@>lImd$$}mng!OpvpbA33~XXjdc6B6E9C+3fA|s(#*Bl=mWhdW4t{l zz~p~PaZOG(CwKJ_iKr@8Q(j673YPuVA*k_XHpTo+(ZfB5(9%-c!GYo_d5a&F6%_(6Gw}kq|5$l)YO1^DZ0a*6%(5n6-{;PUoMMOX z&}F5n%>JuST4@l&HXjS*-mBvpMrow9D1k=DB&gYPd>Geo|A}FUh=k1cEE!E2yowz@ z&$G2U4-2~#M$Nx>6j{N|yi1MNM%#Emg*)P`4slHApE#pI5^61}Ut}_wYDXV@?U6+> zz0t}-rR!B#0E6UK+V@cd5TV*^#qUy8IpJiF2)pu45_A&lyX|TqH{dt3NR-5k6?pSX zh5}4N*-Uu(Vpf;9q5Z_<=0w6p#RX!;8# zZmbwy(}8E}aBy<(v_F5l-B={t9r!nJrZH*KO4W2~P za_6Nv(?-7CnVV43jp7QWz>tzs76|jDL_m}8kZ63jXExeh?_}EF&cY8}qo6MG~+76-5Tbek2=1f5NmfVHU17w`< zN#~_j%`PyF(rWwr!m$-n^Yv9xoXHOjuySrWFP6E(o< zxb-29%hn4h3Z7^xbk&8)s_FT4zt`4QTkG?aS@$N_RU3}dIm<>(2P?k3;XOxDhdNC* zy!fw4IPz2!LIaZeltOArw4xHF&_!gL5IAFGh^D`A|5r=qeG3LYcA?rUM8#lB&wf?w zQwaET_55YVV*Y&hiy!Av6JAG0wwrkCrUPjELtpTIoOOx|DKkfgF3hn$y8EG8lwlRi zyM2MTvrnn)D2y5TFB1D7@IU)X^Lz~(vgUIFtgQz##GScSYP3$hHD7*+u%4TU6l5M} zj*acbk_b{F1Ge>^jN>;=i^if48zDF>4ePIiN$%++i*j%Dl}AGQNXut(vq;}*Q*I2^bD zM=G~NDaZIr=!i4_US`{Th1Z~Z0o6W!*y$^_*w}2ue2A;JSaB{2Jl*>HXn)|<>ipbwadw$DpXbnry)e6gnfhPJ`|^?Jq+gzXasO_*_dLz)N|H(jwy;8ayr16=#)k5|n79)YE; z8R*qM@?BlnNU7E~!PZrM@z>wzvq6JDSX|!}326BTJAazK4Bg`xvM43`@cX+YV_Rk+ z%M_AN;@LdsT>Ql{-Us&3*9e^ZOf+#94FK5RBsQGJ8P;0uFHcTpPw(BpysXjNb8_$S zy}OPH14$fx$Ac_M;tM(@MaC6s9QAK_z;BHSkN-ec8O$4A#Z;C)2Y8|)MaYPvMaY2$ z%%J;M9lP58(cog{tce#!-SieHqH=ludleRL6$UDk`CPAqocJ2P?Tw2Q+d7lXXCW;{ z_-VM9?^g;raNi`2)9B=_BpaD==lt{1!bcY!#aqON7n4?Feu-L3;>u@V#t^TZ5ZJh4 zakxVF_-(#>$dj#xz%#qN()gK-w)YuLfC3+@_z3UQl-vz9_7@tIO(Mw{)0x3FMCrc1nISn$@8x?PEZP9eDccz zdr>{xYT-A_dyl2%FI;BMD^!DN*xl5s+qmkz;S&iv*JSIeM10$waCh$8(}xuS|B2QS zjYhm};E&kD@pPT}AC3Z6_sv>X=&$nEf#-ZKW#`AG z{{HKZ!|G}aMkzq6w8JN7znj<4>NuOi#-5a>Kyoj=(%hY&`fqiX!T6=AG{^Huo4USJ zxy#@~juY!0qhoyfTn~#lLx#@2^6K9H(vp?j<89er#X3a|Lt@2_nR#NV>b`ZmxAf3c z!RmsG$kQ=l)R@U{R~qdu_V=n8Ud(9bH;EDjdYu6~7S%*Ch9LG9Il512Y~;-MFlZtcszw6eCgwpo39X7Wii(K1MDGFu(h zZ-4GtB6ezZ2oRWdW{%4SqNK@3lI0?PF>bceVevDSKa0IhWnPXB6qdZDq^--v)RKxXMx94nAkO& zV>5BV-BGAi86zE^kE-fAGeW@Y`)o?~skjK@$V z*i5z!xN3h{&Vb35G~R|8&XzvcrcLJ*tPZPfxU3DXtYIwghrz>1WdY~dk`oF%uHd}5 zHTxdlqsj9jJ74$o#iG+Y2e&GglUrzEpyXceEzg(t)4{OZMjF;e&n zj!{|4t`SCX(kO)iYL4Z?<)e3(Ui$dpEgxHS*DuWb500 zq?oP&@6y~Zk*yQq;EW+r_F=&&`|>}OxLbvl0qleLv*M9KILt6ATFNZ{FPz0lWxFcp zBGg?UOs*%nf_z|@l;UICJY5&(XcOan9zN+TJk5dXl?1CX_|IqrE{BwjK<*bU!WClv z`vS8=&CC;S6QcN&J$ZqH;&i=PHy1ot-Ifcxj+FuK^DDyZ7vD{i8`NG@D?U;x>M8H3 z{~(4I7H}y`o2cMf!mbSC*gCMP_54 zfUjhc8#b<7xwYt(R&kypCrZ{HA$POA@RV@c>5jWZSlsqXSpU4%9VO7t3CYeW{+GQC zd>Mb^3~S3Ef5SxDrb|E%=;U)+B{!h4OAf#^7kpiWSjexCP&-YxeI&Fb`%No0ZXG#1 zAzIq*KG#XLRzIxhN~Zc|yPByt?z8;Q>S)&p_}6s$@#(w3T>{s2g<$U#6=&QvkrP-& zA~ME;ucVEV%qmUt$+D~7%3JVoDF89<2pbn<<&ABeA2a{Ny#4>!)!h>+FKK(nmmjR9 zT$*?JqQ)uJ6c>Je=gahBP`>wt1`JLw?^8a=JUZkQqNHj2Jnf44Sp++~5W0sTbFOXk z&F=HGuW(PF(4RS(`1AMZfgMtg5fB+C_nq=SRvVekFLKo_jT<%`Jf3hzyE%60f~W}L zg)|sQG!Y6*Zf)?MLscA}tPGd;}pSf1KwqOVKlfzhC687#}%* z;A^iWXyXR73Lkbi0n#0Z8^v2sCbej3K3FL!;F$@_C7Hqn&KHW#;K zVP#c1(SCH1*F?y(zUjAYaWQ0Q=9V>lG&lqM@rwc@>=J`80zyy({KWqq)@g7;b(fv3 zz5RwLI3uY@33T8>6x;#(eNP>^2|2PR3Kpz7T*4O5d22t1kWy0@xORLNz|OL4`_H>2 z-s)W}ZNEnAwn&Tz(WN+d#DCOsKM#3Udr^`RqBP_qc>F*~R)02rwCY_-)y0&|r6pOF z@0uWsl-kwWF{1@Y;nZ((7g?eCa%jLUYQHRHyq49l-v`yO+=JiEL)8WN6_JjshP_o> z&@ZGT;a1SyTTUOCNZy-n)T{cACukNAqttMebqKTXtK&O6M5K>8VNNHNyEp%VIBFc4 z4K6i54ZaOI@bdoT5f`MV-cjU6S@@&!ek6Mrmb!W8KUJNF0NCRa5OstazQUTCp(5h{ z^-I`w9gRR)%>~a;fc<#LqYIo8N08kOOTk;t?e(YsaY1#%_ksYOu(TK z&*I8@C9mu_v~g{YI~)3w#0V5vM22tw0VI}`pw=J%`wz6h39(go_L#n`U5-kkAXByO zL6R!;z!8&@VVqV{?rQ;oyaad(B1r$tgB}Cnsk3v<@P=yw-0!!UCAQ*yi~n;g&;3q* zg{l*Z4AfL5{);fL+9fK?4PH!Np#}YIhPwDvCoD5~b4l&zhoxS1$iO#tJmLR!oCIIr?})6J}$rJ^~yOTC%~ z^lFCAa-zm+u8x?CXE=n^QhEzzT63iIny3cjX+l~nU0og~VfjJM4MKH;FkPRMX}U~+ zBv6_!_JS>Y%F5VG*1e`l$8@y10ef-P8zN~@%lkh`kN^rNxACqrj?vB|*xMt=+JZr_ zZIizp3|a(CV^k`NS(!B5N*5ZLW0ZR?PV`cKuPG0Kk~BK=kqV+CC}kFn?oFX|^mgYw zrv-HEsf~_^LZYtcRop#q6LaUfVE{s1tmp~DRvw0-fMG(!zfiC*;pp{Xj)xYip@mNQ zQ_>UOyhVHhQymBMr{N6{6`cG7rdP-msrpcBAihdAP|)T}3Q18MXy_n{8`XQvo{NSj zWHqkJ>LNGRXfJj9Z*3B9-jmCu)bJyRW2sO5d21oIogUJL;))a^=j>e3zJqWy`FM65 z3;`C%HUGTKCQ1$oa8;rBfhQb7x-?TDI^g&*0;n;ZmgTb|92MEz#{vJU8{=)7kZRfQ zm0`8433ZH*waxJZ4iJpppg7xRQ?$(_3Lgh`e%*jn7I7S&ozfA`w9zAz(!l3E64 z`@bwuh9^a%P+<{AF>s^mb_#Z0HE$QZ2M)poMWZ}npFd(QG@A7I&sc8`)_P!d?&>uy zbzP}WC8>8^jU~McO%J|)DXR@lJNw48L9FS;-2Uc3;DUShP*)+nw(Fs@U5X6J75NJt z6@6O~!Ehv7(<&hiKjnV$ua`B`H2SYus@GmG*7mr0nk-|7*hc^6M*kdZ6MmGa2!&ar zJ$VvfIZtZHCMJ%Ta(g+~gl0~)dugeEDf_cNiePm)4r>`jRo&nB@uEwsdNKdf zNsB=)d}3$Y=c8^4J3H~PRJY`!zFwgJ?aK!I+jv%V)Pa&v)9|Y!P@+W|1gAVr!2ah zp@&VlUpTp6PYB8eN0v|_rp1mxc;mfe7kpwJGSBHq<*8nt%|E-I9I}I88YXwR@=<7j z30(Anu2_UI^n9p#b%JdE`d7tEKF<2C3?4|XA9QgrwLIyaLkyk^e=J%}pNZBQHV#dB zW5-+EgBk4?n5<_QERVH13%&blE(SYdq91;zUHt5Knwh+K=0&=7$H6{h>w_z2TsPHg z8*g`rx1FJT3sUzmD1oW#@)oo#adl~Sc$cTt6pqH`~8ihb1MWj966?yy*xVFJa-KwjH5{4nF zG;(X}k0RX8-41b0Gg-2 zftIFxAAwt&K1)-zFAp2~UDuOLI=9tRdTx8>-8U28n7TgRqH8`z%)7nDO2G@-gQU%J zUY|4ap5C%=dpvH;d%PA0)qVG8YkhB`vKZTL4(0Wo*T6faH^zJ33$re~_9w;tKKDv< zJ3i&5{RQmzh?mZ?JN`hSh?82TyN>D|?RLHW?ILhxV%WsiaAWS=b-NQ?=22 z((C286s6U&qkV#>P}IHw>|{B13G>nWNd!fKo^)-ZB!eKOBu12!+9X`UTG*`(E^~dh z!7T?~k_drv8N8Grw2!e848^ox$pxtLRJ+#}YWQ`n-E@(qKV;Q6-tiWWh%Eo_;9zigi{y{FW9QOgX_Q-X-ff}XRhjUb`RCS-bKHHs_F1l73yX3ZPo4L7=hs|K$ z0~JW%d%P%$wJ3rwuF)uqC2%#3z^$3la1n4)hY}7lvcwES4OWWEANeI5L_rloA=f~N z6b}FOS!WW$+#T;;OgCL`tLvy4N?qR_ij=gjp|Mhq|M5{et5stFYf7Q;XvFIEit19M z5#`$#!@7Wh5s1jR1Kfg_%@E*>nO(>sU!bQQNr_joF-`(<2ol;EKG%;4?!t(c0 z9^JwWMV->TANuO<8sLGV@(i`&^72KQtdb0|Nyrsz4lK1?#}h~54v@3@me)C<;QNd+ z(}$PhcUN+TWlOD_3g_#}^>y8c(AV{7WEd<$#6U*d=r3;!ml8F{NlPwL*3CCsgomQl z$CK4R57e`~14)*aUmznBqRxV+Tc%a~ipwsm>^nX%LkkeP z(uJU~`Wmya;>F2}(4iCR85?fqd-pAKj5|ciy8CI6$#$io7^&z#DTh0|bI@v7^WO*t z=QYmI_`b=P?#wF~7@|zJN;PZCkUn7|!~A4B zJRt1cFyeI4>~OUWN3PZE_&AHti@ylLV@?>(dH!7CMY(FJXuQ^6yOB6M6M{Dk*K}Jk zO;P%LqE6FP)qr4{*5w`uAuBO*wb}5{&>3!GJL+wXvgX*S-f4S&EqKY_P_R5*+jJ28 zQ2XdfESgwbQ^Y&@oa3%{7wzNNp}?uTeqfI03nNC{8g64_86ts)_{*n}n8+3~%uf^I z{xc^`9!xV$3=A^f^39l_MY7QPxtC!_l7nn$t8-TVjDR#C`e8AiGA3=a#_YJ$7WfiZ z*mCEXbCm_tRC$wXh@_N}N@!k^O}l85$isgi;e!C}`U4x0Nfnb#w8aF6e&x9Iw~$-tvt@$NNV;E1CeMWn>LH z^AAx-{6T|8Wt%HrLu=*Tm*&zu?~CT1_H&@Q{%TG`9;FvJp&GGuh2dkSR|BjaQr7rnWG_uy1!V?o1GFO}HtYP( zyK5iW*xA|HI5?YK-SGawN@Eukkn`)li?@fpdWpe#4Tr-GiG>GK6-06s1eHZNbghw4S!5^K^Y*`?V)uxY0TyBghDq9Dfd1vyjHZ?aAJ`jkVCp zcfEC6Uw9mEQa|v0LcWgM7iDyQoa{<^9tZ;leQd9*DlFtwJIiRD%3z|cx&edk<(_uz zYv8S#nDm@{EN4z}%8`2e-fTMQ2y`}wDvpFwak_ntbTD%4O5K29Hzl6h^51hR6N{>H zmLb0EmPk#H+#Pw`S7Y{iukY+Ao1O0`bH**hkQ9{kxXD4;Aw6zvLXniRnx~6-j>l7W z?MEL+f_{%AM|JrEjZ)%4iD+?=Mfgce81ETleKPkJX=e157aDSVE^2cwZnr9Sr!sc0 zE_%Z<-ELIsywY>LmOPxgC`j7OD52VsSATdR!#-J)7Lo(D znep+p{RG7+$k3kcQ{WNb16U#taOS^fQjnK8CdcmV%mnuB0D+0*;&E>7?uCVhEF`E_ zoGxzjOO_Z23jh>B*oP<(7reWM@6EXWK^ksJD3!N|yDH1>)38oSEn>tF9=)rql}9i< z+l-S01;QW{X)(SI43Bv3qTB+n^V(58Cw1X5&Hz-iTnT5485U**paH4FU3fpS)JPjE_w9%aoOOZfF4`s6uRqkC3HO^YJTXUOXZxwhuyVa+-L@QGgY42BE z1uTS_MBXP9(aEmll?0u=vrS0Y-Kwbdi^$EH=#8@hc;lC8dioPLx__@7=i>rR(0B+D z`CTMTg&-}MF&I)g;Dc;HXW<2FBu4PXT#hF z10^}z5ivpikCoc?l~w5F*6iS6XF@_-*e}uTP;$fOrvlv<8`)-Lga7lxp zZAdN0Nk~i4Hp37W3A7&(UrN?#f+D9#R~k;NI5Urf4A`mNvqXnfD8q!c3kpoT^gMr9 zZy7vGd;WK5Sp*-(k~PG75~i#wo_wG%POWMrS+hu31ClAXk!P^}pszi1XYzAeWOq;S zEl+}XsX~3E7=R1*R!eRV({Nsi(|7mkHrmM-dEX%HBs$W9Lwu%#%bcL8; z{^C^8L$yU+@+^86@cZKX&rWs;NH0`Sl|oXW)%4JO06c$~%R!olpoV5{K1(NASZ(G` zB?WOhMi&V4=Y~8!|8)?XVU(zIlmgWJ%`-<*7{c4daijc3Hp4T;jQ_+Yufz^F zWP&A~GZ2PJ>m(VGs5zV=>U+LaqAU`AcCh>U<$fBBXiAK{p?xrooqL;wW3a zTsPo!o5tq(L@M6Y+cU%T2x0?^@f7c6XT$8SR%}I$TC5la6nfzwI0i*8uXqA(77iZ1 zX@3(J4-=Pi$Ke}qUNW@E5&vir15h}6r%4E;3jxLty11A)L?jd>#0O$gx~a|0TynF* z@`sH>??6X))uk=k{KrCz zwj7c-(e>&aYp1>0QQ$|(^h2dBX{{$|tEE7xBV(x}N3$DWmm6E7ADiYP7=$I$HJq&e zX=1M46%6&$KT8vv>-fGBt6y@ZN5oR^s(-Ki{uLoKUj!}?qOZ@=(fsqF!P`yn!ERx* z6L>?W&%ac>%^(p)fDouVq*u1Xdk`uhBFxgRj7UY07juz*@cOG95@yyXFGK_vk+YX( zJf*qliQ7vgE0lDm)q0soxC%Yjpchbye5+_;P1DIyOO=q$m~LWtt8N=Q~{ zHQ%LR!HvVX;dcroXhVzgDa5Ta@mlA5H{hVM#r3aro#1ryx^LRB7e04Pz0($>Xqc`y zX|eO!G6R5ES%zvY7{aZ+!(6l;_jM1HQn5KW9tp)Dh)PvWN3{aOa4B8WH^VGCcjqu zg>909f<&0F;1igeiVTRgxB+h)+wBPkwnJO>55&1vEYej=6P0OGN?;6ndC`5NgPA@UV|K<%HrO5)HHeG+)w}+L)7c>;z_! z`{dkO`@r7^%oxIChyXfBP=_Hrfi1jC-7rJZmFzmZ#Ef%^ejJ7}8ZSxVAJONUiZ8V| zK>P`6hAXLnWIn0MpiUDZRrcybEh&K=zw7Z*d)ZC{S>|OLTD)Z%D!g7a!lni7sZLNy zc;2Unyq&0=skxJ=dDMtU+fWklVoK?e{6gd|LzOoh<*SAAm4w>q5YjIT6G?d&*cne~ z-5ruw#Z@d+<-fwn!QzA51)ORUbTb{5rpUO&j*-}9RKygUcCJzjZjsN3sFzA6mVkuz?HVzKR zF7?sOsCVD4F7fvGx95#0}!N!7j)& z+TLbZR1}yENW;7Dhf#mIZw0m9rdb9lpeIDqnc=I12%Vc+GJ+N@i!9nFEODl)2uG{% zIo}2Or`$eswQ3z1@3Ytx*DmW)mIi6eeb+?PV@8XTjwYcB9xMrPeo%X>U!}hD*9|*6M0&0!)^Vma7<&6axBD5DT_&rpLnRf)S zUqk~WvtH1fZ0bmB%F6rd812Q`f2=M#%SLGF^b(FTVdRT@T5jFb(aaTs0+%tq+#F8V zLpjg9B)jXquTgKl{pg_YDif$~qHWa5BX8WN?Ql-v*BsllnfN95PQ%0NS(F;F$|G+L z$4XS=glwK{6CoC+^(o>3TEiSJ+fjuchkjpmXn|}u4WaT*wS0L8xNHNTD1H?zhb(RS z>C>yzooM50kq#pEDYOoHG{Ff+yu7}KhlPoW_3C3u{*0Co`2de900dL$6Q^Zzo9 z%*%ApOzA0ZSC+t5;6h4v_xzaBjbo0$Px#5d;9|}m^i>93MIH&3+yh1^3|j&|M$FHE zif45hYKNC@25wINX^+K2>nr~_1kIb{*Dwhrf_VM{ugd8q16Zp%mNz3M%4fTu6V#=z zrdJ0W0aA6u@i^~dPc8dPR9iHV_mUcJU;P!;cB<%z(uovI1v+0N&wIF#-Yl+{Be7&D z5)2P#tlf1SHth2}T$!48Nx+*qW-`ea%@==zGZLuCWC6L*Ic39xzSWYd))NaqiWzL~ zVPa{DV0(RERx7xwvm=m+-7A0AMFkL!Y42-oQ|+#Oap@|pUstHsCD@t^!Vne#MjQ&NdFr5ba=%$7xq5XN6++1A zd-*6kKO9L^@iT<_+X=R1`o;|}nIGN0uaGYv!oT&e(Di8=&KoV>-Clf}(o8xk2xm@# z(tXY&Pj)bCUlTdk1HO2`{FKe7+ER`z`@&CqrJ|Ul8bi5+!_k^=`x_FMBm%Al2uEUp z*N|n&BzOdy0>Uq#?iB-- zK37=k-X8qN#Lp0%%N){fvKrv%1;~R2Z}GjouZh#K|=@L zO1w;BITGu*qyf|l^1fg)(5`=3MiyZ~g+MhgS7ZPEqk3Bkpl+#{-YOvoNIh+RJL74n zr-KRHMOxCOO;=f1D#ydabD?USG-21avFH@;Clf(KLMEg5%NIiV7w{i{3J3Ei@9}|p z+J?e=cZ8O&vBC)N%y>MnaAI)}NUp@k^;+Ns2x^tq)|R5=wz{sa_u$g2}JKed}%#0(QE@P7Ae*b*k>s5#d(cr;;h%iJPv)3Q7 z4nF>Fr~s}H-1fLue)}@(&U}k)6)9=5dglxZW4al0=fAU=eQ1>ARrmreGA!C}lk+Rv ztzXlB^;IV~PL~A=BBTv&;vFW!pwlRZ3-lADNG236LO`qZ#59V98gqs=w@#KujK~m? zfGXKb^hS0UVMt>^OctEE(iZ~8S0J-4(S2ujpj-&ho|PQNI9@njXcNJC4F(e=ivR+w z`hj*5S0Q`zgCKC)NkD70`CdgTtudGi*lm65qQT@O0Qk^26wZ8MgAlF;7rlNSrDx%x zEc5gB1Pys5G-#Ziid^eLjy%`w=`}m zI+5-<-m_(qkl-puB0uo#=IcTm*{9ykK)T%q%PzSzqi;AEuN~s)9YE!b!oSn1TYZ%Kn?Z#RY_D zvV`1}#O-xwp|8Ky3lUdJL_~}YsF|cMdPkZq<+C715N@?G_U!K}ln$DTOp7$&B@*FqWQ6Vuz z%|a9<1SesQHWsJ_pB9v3E@o$8RDpm=XQ5CcgE$ANCv2C{9ICP%vHVXcq*jjiIj^_Y zs(`v_SZQb~({{=)wNaNTw@1a8j7Y|9Z}r*Ot?Qmx1DJ*=S~~Qazj+;BC&w?mPh#Ki zPGZJd0I4}});7l{i5Slo?I#*??>1)VB7V0nLZ^JqrZxG#aV5G%$0JgULoi`8lPn#h z6}x^d*U)amQ@|mr7wac>8-yMspcZtpkFJ4JQfPB86+&3{-eB!$ECXW^kQb&4y;~Mk zqv=)(=9-M`m3{ugd8%f=yZ7qV9injCNm_HfVC6Lml*;k&PgYlXUA+niR~I(sq7&fX zOoQLa``^El_qSP#qejNb0u2rCme~%04iZrk;7mQ24v*+72qw#4eXHY_ehY-z3X_wQ z{}P7s_3aY(ZjZ~9)rAk!(#|@?pGT5y9xN6;(!XN;?Qeu0*%sR>cq1(N>QHYQXni{) zeEq9$pneZ+ZbwlcQam@D;?zF1KBB8Rwr z7LY^!kVEdU^&cs^lqyO|#S80vFxngFyzP=6nt*F%LcNNKIZq8PkM@ zTz`Gg;!1XjWSCp-Dz|oiRw+If8p=;uuI!O;4~t0GU)Pia6w#qZR$qfjeP+HOLe7DU z7$JkZD;es)s5bEiBav(bVK~xj`A1wUxs6}K>UPzyat4Q1%&`Ul4+#AObHqNf1GZ$= z-PF}Xi_1&#@P^Vgv*zFbr~um9;?B<2U`kA??=W;+ zIy9h^zk^|V9&GQUS;enrWoIKdPzUcou6n0Zqw~S&mvAZG;}f*y3uGZ1~b#>8cEUG``OxW zVz$2LYBa<>e6ZlhJYnLLRGT zqS0q8-l8px+?Wg8F{R*&@uMB47L^!qP-8tW5Kb={Skd4r{}6Ej?TXh@%aKZ})%>-5 zwvJeirMvlg>m=Z!M9M4L8|%wQgszf@3)ibH*RK#iRO`2_6eM{I+_AN?D?7 zf`dhhqJMdK;>#CIjN#oO(MVSQgb+@LRgY{x$^~7U6)e-w$qRD`(~wE_6=0H&63Nb$ z+&fBsiSefIad}_$3q714xIQfdc7n{Pgh5UwW5KeBVxi%5EX<#81NIx z`f4Pu!#N?j59JF$BY4~z9+7PP0{6_KrSKRVN6g&s$j6S?!fa1PYePe;-$QIqqoU%o zu8CLFikF^+O1+sFwSw6D=yU>GPkNw=GRsOaVnH#1sE74BkSTBU^2c}jdFLEgneeCs zwfc9fcJ>!xzH$NpH2oZrrnUXMKYoX2DAYDWWCq-Oewg^dqd;GHEKZJ3{_Z=DF zP}-Z9hA2u6O7iy{-N)792w`G?rlMBL#r3iEm@4KE6n-agIUo5!hTd9eor?awnUR!- z^W30NEYPE@u&_|M@^U&$rX@F*9958wYnqc|KE9(J){|xz@4p&_6%2cyF$C_hvT{UF zS=0MZ1N$SO`o5XSZs5#jprMGc?uIycE@RUI1hTQTwza8RaIlDJl;{6m(M>`Xq5Zn6 zUcSKpP39aCFOw8*8MFr?&H3Q+ud2IZ!e8k(0Bw~qZ#ndB)D=;Q2>Z=m?~w6UFdm*B z-MTn)GtAAmXnLIzG+Xj?fETM9?kc>4^qv!gXKP$C&J8S2A#>k=JwA0-bH~v2Kd2bd zgq23l>Rfxc_AnpJ2OBeYMMZ(?Fb8n&2rRf#I@_t!?<_3rh@781hC%UdqaSFxo+&|T zq)a@W36)a_BP0*&NJzgtAi3AT3*Red{72#KzK&Dw@v0O*grU&&aKCobk-XA7j=E#X zM(Q;lFQEuHCooAfT1#Q#D;0N*HWWsLJF)LQXG&VVv+a^vof`S^YW8YziZ4w$-^ooE z%bB;WXhx`~5j6Kz`hE@TMH^~_&bqE=AUe~5`W{Ey%SoEjr&<~xP7qozhw3f?HTxB( zsR^<(4x4CyG9CWkw%98M&fys1oW25G1Uq#hrZhdJm#>k}*hlTWlOzBDaF)*Mn>B56 z1E*$-I^tV}5rHP3x`_V(E{_C!);7@(@pvx?@o@>oDdq+F+_FQeu(~#vH#awzHEI)< z_RY<0*E%VqBB*F8Ba4#txAN4jvc;_ROZrBcJ%D^QGt_5+Xx!N$YB$rZ$QPe#Nc|*; zVnYza+va$kh+WYot>oxq3zJ8#zpiZT_Dt-~En2*|bT|Dqy!HdCPJhK-|I$2#1is{& zZ%Hrv39PWPW3@*q_1-#7^mCr4o2O1JVsW$6r-vK|(qDhkcg?Mh!bAND#WDhO8-bv+ zpkVLdiMMpaEl{vkL5G47Mk>Kbo_sk?N8f{Si#p%pmeTkIl#1uV7^IrchaM+B>Pqt` z`38~U@-Y2^swL6Wd>R@l{QG&^^W&6{C^qT7YJzhfMLT37bj1MxpX_!?JNcSz3+O6 z8&o^vuqZVoBIRV1JxxId+)1o=xWsvJ1)H#Ims(m| z*cUm=QY)%r{#9#ppnvgq210GUTM zQd3V+`1ob;eP(ohMA^lMCBPLz_f`#ocX2kIo^N5j%2)}r@91T6`c6tK_gz81Tc0!0(_Z(qUx6u=PtA&_EgNsWd5;bh z`svLCX)DN5-pJ$Ltf^2dC!}SD8*GUy961{T^5+`#pr*ppa65;eF{obg_zOE_ISPvR z`5g#w$J#nFmERyyONt$E$RtOwZT_`F18i78=9Gw`G727_q_4`L7xVCYH_^xZx^L^| zKJPa5mbptjnQAL?HUiiu$obOThWGk{uPQ%MIXRj^NF`~DZiO=zjVq`q_mB6;$akT; z;biyW;437ZvjAt%Ejfpk~7JYB03nHM8(@o8F&L zknx1&51?6d_{%Xc&Q8Ji#h{4!)eOdq?FeV(SzpiI6Ui+(!6MKSb;qMzPQ-i=+z znj|w)0~jdSU@W6gO88hjvJMtT?RpmXG8*z$wh8>~N8(>wQDA7t+YN*W{y(PPfvpm@ z4HwRnt*Iv4ZgQ8i~j;Za#|l`ND_)|kcO*F62K zY@ewtKB+ux%Ule8cjaxG`z69x_(V)@gts0RI`YW+%u<=Hz|1=_&=_DvKM@P_NskOA zAd=x3B{6&zV?YyuVL<)9sNum?rP4hqf0c=`$|uR)9!y&C&0o>6h|~8C+Suh8sxjeZ zI}k-hihK648M{i&iuJFxE`L~PZBG;;A)bz=AN7AEiM&23^Zkdh5lx`=C@+iyAT0=Z zTH7)1J!s15+JbEdCbi@yNzr1&f^<|Y(lsB`Sj3XaNLp1H>FoFDPN16OO0xCy4^gz(0`9spWN6gIV%$0|PigNJl{#S`U#Lk!E4JQ1-fZNvWh!vidYFH>p9hU{u za3Q%BzbU|O8w29NkO+U^bLZupDQOr&MS(tXP9q5Dd4mffxi|JUas*AN!*CA|B3pH~ zkt^26%+8JG+W2dD{y~oQY>5d8Bl!Up;j(6g^s`BB9SAm?%=j{>Qp~AA00EMTfOES) zA&%Kd;x_Dq4fn|GNV$I@KawS49$M?b#?OP38~dX5Fdusb`Y`Ge{QC{42Stv1NF4jg zgWfE*Q}Cd(TCSFCP{!Yd8Q7Y3##Wj=RtSjUDTCGu;k{>@AqMP#L! zeFx*)&1CH*lK}`2Q^Blw5XPg9GnpjT^-6yXrTp%b*9^1~N^ys!`&2&e`C7kv?PkXO z$B!$-LKsX8S3zJt3OV*v5p3%++W7i%-m;`uj}-^Y6%ZXBz2$idS>xkfTlsmmd+59p zPjBHDQYom*pRfV~VOF^2-J|lnAWRPtWHXApDA@4l7<6@Nn3M3w53NQn&?_+taJosFH%W z!EtPfqrv~24RE7q#1_t+|B;&kmgfF^VJaW*8&cLxk>vrs(jNr~4}U#vh&@Z1edR$4 zAfZ;YIRUJCHGWCTFAe^j$HhlFvlIL^cIyCAMt}-}45%Q)N|}oez}Sld2FQMg1;rr8 z#A)7hRwynl+03-CpRT53qAS|UEruZB69;G4)1DSK)K_Rlx;RBOi!o~cP2I;OdWNX% zOP}JK{fun{?(T(F$;Rg!Y8gCWt#z8OGt~p4sL}xu?S?X;J8Qc~iF%xk&f?n;c_`%Q zJ{b`HJ{22bjkLmHR;8nST~6SDLT$5I{9#t@e+?Ppvq=(jSY4y4#f|Vj@I5C5HJYG^ zKwiw~7)>l|NxPcE zs_EjXRmO>7EBCqc6V0(a5jJysqYW_QOFMxj*i*msi+fsLBC3paNg>jxX?^GZ;&eTl zm}>nR&&!`eyor}l)vw<*SKBZL$sk0SwMrstfvZBjng6%t$oTfjAldxxq(sO|RzcBvUPJDqwiX1Q<+ z|BA{R91_KN`+N*mB2g}9hYjIDGVNMfT<`&@-+C>ZkdcuSK6w6w_D6%zU`bKmF%TQ) zC)xkYJo8*ER?^(wywJ@6w+ePL>dZ$&~kWR&#F43*%T2$JB z65(QD11ZrFDvg=4L2wy zCME?@(V5P`+DuA}aWI1P|T@qyF$st3yxPW-S0p^1Y=#&+}4^-=@|n zO2H=5>K<^|Nnt)EO!qAtF1;MPQ8vrzXbxxnX1S@yuWW*SprNfz_mazHerdDRSP(?7$Iq>XLo`M!~}@o{u_UD1V%FiOU_IunoI;d_tTm7*@z+COzj-6v5%?DhGJu!(?Z`nT{v^sivd%u5v zyd6l1`G_bhJ6U;!s@XB;<>kg=uJ>3Q8F>Tizco5TBehscF3mk<>dtyViYvyfgTEo{i`KGW4N&K!x zq9G+YuE&fbrwH}UvH7Cyg5`q1bRo6TTx|qS=8;dbvE+QRn6-&i__k z+I2D1 zAgsqh66pSWzn@NAjodqOzIS>QK379-)Ph>!YR|3~W0-A{wkBIcNv#b#CQkZT*n`zX z;!4A^<{|qrxXK2kgTvVjxx^Ed3=1+i$jzW(X9U!z4QbC`m{}b~-YeH!7NFHpk$qU{ znZ&R7mqyM(+p67!yT6DT73LUugf87Zt$&=!N(l;l^i#gt$pd;0f=lHa`2MeREIF7zxw!ebzjlx! z7DGrH5rNcTPNHa;&NM$QoEAT$Y2=cpuJ@vi9B+@_U$(IlY!Y%(&p10{>VT8}1GWc< zpQlECVorAnhY1=FzD6mclM+|K`^O(EJJt=5K~X#rq*f&dv_2c@MN1SS{khTh-3nfC4J}_hhmrfbmm#E4HuzYyUSf?V}{SihBaaq z(lEDZ=~3WyncKTU|7Dg>!Bn?%Zj-u2X?-5QdYFa;sZ;Rd!U--AgbW4R-TlsdRl^O4 z+3ky%6#{@t&)X3Es@bh}$!&U!2}t%ik6c9k_TNSyLH`z+j$*Hx`gICUYRSJM4>?9f zsoQ;1aNAXM$rB_T=9uQoZ6Dr{a8|KHrgS4jjS{m59vF)MZe;8Zydfr;oluayQ)gm( z51O7F#sCJ$vAc_s2Vq>b`F?Lk$y5IfGzvK`bfDf+9U_htE|4f?J3$N9%VVO5H#A!r zYx(j)5=_$=cyTP+MY1P>47-7#@X8K4x{s$ZO;baIi67SSm*YulM3&4;A@j5oiWgK@ zJyyV+q)t35Gh6onuZuZ%XvUK%Jks?We*5+pw5NrRJ<#lC%S_s*n^J?6ZxtS-B`&Nr zHk>^^;7T}6gw$6S&TO*C%`0#o#^MHKc@{eZ11h9E@nL#STXS|~t!S=<9iB@>u%x~p zuhGX36`$7;KxTczH1G514f|wqs`I>*l+%(32D!Y^sZ!q8?^TD%A_4RsI7zCFFYfK! zqMS9CqtLzil`9#-I4<`jz?VXygp$juMsD{IXaiQ(Q&*jQA#AB?udoLOQD z=mo!xxT>Ug*=|phl%lo5vrJ4IxCcc70|WEiPY-_ozYohA8v-5gyT9JOPZvKwzgoqD zIlHTCa|`?YQrUhoh#|}I6Np1CRI~9sBgT-F6dVo&I0-P4;wOem<9R)sn(KbAtU^EE zlYJyZP$PuL4jzCZWVd%(rRp)L2y0D*8%ybG1N`!`(=0f-X%UEBaRoAE|*{2dt*fQ0|`k4Erf)SVpbLSLeEx72D&R zrk<))EQ_@Qj4n${ZjstD(a+*YduR1&L3kB(Klj zKn;4`ncc_7Ucw^TJnLjxs$M8|vFJIEV^wDB!+VAF>Tu*FVMyQjCH!@JFRQ5U@eQIk zwS@6KV77iyB)yoA+lGz3DsZtoZH^fls|bHsS>>ZFeRBZ*%>VXrKjAP|8LC9jnGG!q zGb%w(R_D5-0%2mP2pI_g#U2>)ZH;lVxUY=IyC3;9oXyj|`ClE4^vk18OV|atHrn$H zA9|PqoC?Y8?$6|+xd#-VXZE^~bA-AG89i+Yg{ie`=UaS}M;KNo*}=!YwZDaXlsK}} z;8=s^iIgecJ$$j5^A?OW47n3ezlgaemHTd!*}RjF=MXwkKl>lU^Iqe7LN1f83DTVd z4divFvKQ$Oow2s)O1MSoa}qM&td2jWEOXnzJWEHv3qZen`ysuS8;m=s-9f_NXQIV1 zqcXQu;8x=`tBboud?&*KULbr#RB&Q4&EI6guNu%8m()-i)vWYpB|{iO?R!+y>IuQ2 zo+RJo{);6bFawuNCyQkhLWh%4=u5y5@>|&NND5O-1DYdtT37yo0yIIOdkP7=ji=c% z|D5SmmAOysS~zBawY|;akP*avygq?NTXW0mV2=B64HnFkm!O(q5DSfLpx%)Qvvog+ z$qlFGj0n?eI%t&E|DdNUYCMROY|%FoC_I(6!rMhpRY>k!}?ZoBeyHu$twM3jp;B7@ak?}u9| z--~Hw8hU&Vbpz_N!?aPlj^%lB!UX?@ZOE)RBCA%{uoHUijXSq?js~ongNqE>k{LCl zvf$4=J_$V8mKlZ+Rr-roYmYcC?6>z*u``?(tL5|lNFayZZM>kmX+2+Ur&QG?yKh-? z$?pTdQBZ%1?<1)N;a^DclRBX#|Au2^qjs7@BuRBI$v{&?7-)*%g;rJ^;pODxi;_Gx z-x8AF%AHxQ;?M$?E-k8TEoxMtCSSB4h)c;}u0;CI1=?NG%umDG7<>abqX&>Agn|X8 z==`QL2=g}9gm6yGk-)pl1IwDx+&JJqO8EviwyHeUw%-!VCr_Rokd0Aiwab~^d@+GQ z635NMQqYLihq{{$(LlsHpIZe=l3FE!+)eZbVS@E8l46pgHd!TSnVPb59yWK}vWX|4 z`R&1=y#7cb`ZHs;c%O7qSH4qx&<1)rzgI<;+v=gD{{yQ5Ni3TuL^}Ow2)x-~wU0cA z>coUI%1>0}5v%bVW8+@wKZnKb;2R-W&T+bYBvqT&gB7UPyo!-e!W(HxOq8vQ^rI_k zO=2rN4_adw1I9Pk1Pi%s=hD zGzTy$Aie~O2eBsXL=|2)dhN0sl{BCQeXG&rhJdwM-Tx78N`UH>m^vrx3S*$e-B`%r z65f9KrH~*tfCt5=`<6fXc$}=)=-IgPPryMcHe%8?rM^R|9ZinSKCjAow-lHxeBEh+ zOEQ9s`Oyk$4cVlV3Rw`KWA~>CL2T5y9x2T0jdWUfTtpkVVs=;5WqDECspfWNC7_0E zanvoTpog@7+({~pk$bo#$nK0`4+^Ml37nd76Ys&hSsY`#oj$2Ay*Bv%E3|bYZ?#5B z+i$^iTiuy~KIIC{z(s$Y(_&%UTi7vHOJv#mrDYf<^R+3rLJVsI?8h+RFPPf#6 zOtGYN<%+n1+EwR0ZMBB-hVMOSC6`S}lzShr>YzjVU2`D^V+Fc5Hj!SXe*n74^26il zjDXYu-#W3@e`%SFeWHNl*T@FJ>4raSS%2rVLowKHV*boaWcTxKBtr0zrL6An-(;A% zSI4z0){ub%<_ql1%wXB|GdsTvM3nc)hAHC$mJs8C&!XgK0w9;vFOYq%Te&tF)@v9Q zF0V<~&rgtt$GoY8O_LYtaIrb2U8||EefKXFQ7|5zA|`w7=jXz7MaK__NCd}?JX6MA z1^4A!_%{Wh)F6p8QXa$Ke_#WMAq3r%(~Dkhon+jF;|w3k(2eq0bgzuhh}F_T9b=1K-m1xr zOQvWkmTC#P$O0yDs5uGV$Me@*Xeh4sbPqbcy!k>$I-rum*5|^Ff^01RWYx&-_hkSq z-gvONVu`WGu{1`sIaLa0Njsmt7UpEYZ~EO+iJBMb z%ZlmZ9@mvXE>x@hKBrXC_t%FlvRYzUsqMDkFYE**30FzjFNJ(9lDKh-S)|(xJwK;) zv(KtTbNZ3NWcEA8PO>dyL};(6!OQ8#sEF%jj##b`^pA zwB6c@$0>6cq|6*`eYc_yNhG!+QIdDG=5k2WO8|Wpd%iJ15BiKoa|t|&95_U3AK)45 zh8%H{qt}hFbTFncXnawP!5sBtzg4VK=m%SFh@-3=ES7`i1Y)lI)6h|tS)bEdEcop3ujPNXvE8EICjBr)_v~#41feYfGZw> z|K-qx^IvF7chK4cNh6K_bB7#xXWQlp9p#FsG-cFi%ZOEqG=KWsSIHCl+_8~*UPQYI zP;*6Pc@Hs?I=toReH;(PJ|%+B;NMCoFZ>&AL9X*ZEHK)7J{~U@53Jq%r+%WINpj0%2qXm95E)A461A;wF|4TUA#wjm%nv=SGj+^= zWJG9${I_`iuVql|{}>>LIspOY?d{H<1)BzzmbNw)HWH!dflph}F{Hn!DQ^z91eMEk zc2AF1C|hd3rNrt$>Z$pn;O?&EN-|ELW2T*p!K5l&(X;bD|M<=8>x+-p^Bw8ZsyXMw z14i8luVNPY%JbGGpx$?5s$wka&fwcA{QmdebMo4DXiA)ZvSv8LxNq^O8`}mJ7f~_6 z?~C8roA$Y;_BmuqsA1U7B)YLPP<~B@j;|>PHK7Q88Q!A^OagF2>`!tJz}del2&qVz&QV#^x{2C_YB!XsbyaqguPWpv zeg?=2GyU)_FcMcgO-fx&&R_ZWw&j6-E@G7rgn21|H7b0OLmQ^SktusxIuOWsOYIPR z%TaH&yRw_2Zdh`_?p+OH4#S8mLB4%#H2rZ*zdbbybyvd?ANoOo}8mTi3)QmlbrTM3Kei9>6=yaDEVXqrQ2a+QWVf;$1 zsYzEQq5KW4!rl?kkSlXW?;iZN8R)_E-gLw4r#0~x!wK>fDtIQhL>?+T3CR zpW6C|xaG9G<7qXNqXju)+%cQ1(aSV3tz2#6?>_Wdp#?6RS)I zq=tLH?R*ps)7~HEu1Jd89=Lkm4!-#QV8h@~b?fAxF)hV>d>%~aPSUn>70d(}VhlnK zWkH;yx5R7s83y~lMt^V{7|Uurt?YQu(j21VmE^oz!Jpviy}jeCi2Z9rMGPiG`_^9Q z0UsTC@cLWcw-$veb?Dzef$Pw>16DY8CNgLwOf~H%kjG;N2)e}D&+8&;0OFiqPX5qC@2W1Vs>@IF_A54F6&}px$SrAAJTz*?QATmoko=PGE7h4Mr}kW18Sw)bj-vJ%mM)*SS}wUnx4STdIL_3aM-neJd6U ztEo24d`Qw%iOCt9K|sMN9)VXHFrd}rWv6+Z+0U|2(8ed9NZ%~?L zw6v6XS%J!~pYH{bGGV0U^FL6vQN~~A*vVEVl|3%z+HAI23*44T#$||5j}PtIuUJS^ z>R;x6WK8izYBaqdh;!^F_YfGM&)k@0`}})sn5NoY{JQNsvXmJfObPb-x~0MO4O^D3 z-g>yxMbUG4N=#jU1va_yx!=@$+FxEwBk-E(Z6->PTZ*WjJSWXW7NCuDkP7B5MDDJ z7}3zb`#vYJ6?dQ{7eJ3Y_OGF)hpwxYH?<g;b3Pt^uahUyas+Kp1En45a9m1Y$jHft92gVkou++Fg*zn&j?Z9?>Xor@Y7A z>AOFzkp#P@J{7zHEF#hVo)+oJA)C}uI`mn*Vp$U}U0k z49{hL6fc?Ji8^ncJnAN4XhWTW;IvtfE~mD(p&j3me?@qZIcZ4!U5NCyS2ZQ4PFtIi z){W@t-E|7CL9TK&AI4`8S%n;lV+Z_SIg9L$0BqE`&bXW5`)!!^arrU9s{cp=QP4H! zz2w|G>4$mawu%$*AW=tu-E6DvOowsJ>mguWv=s4f^ZL`#6KlmPii%6|NGze+Jq6tx zNHcw>9dp1zl9kuYThy*(8o0kNvj# zsozEahT!McgL^*7dKJ!7V$0C0!Ph6Bk#(m;jMvn&8G6~45wt@YJ01k=tK$O}B|!-v zmQC$lT|bb@#1o4;N?MLaK+-`_az|LGxcIOLU!(Uuh>#||{SE3QDF}}-!5P8KaGF(W zU3#(NEg}C*iMlWI>ob;NM`(WvpO+z>h-=&Q2q9&vv7>%|%mnv~*O zG57EnlAz#i{;_jS!8dc|zu~NvJEtVI$zRC7xJ>e>kRqHF2ynwXQ2Mke+) zR!Cafr2LfH!-`0sbUq2BB|WbXwid_$atR74$}74IH~Q#xVYB%=LpfC)uhT7p?~@1|4YJ{aTYpBw=QK&R%u5~?GhcvB2D@4k zoxL8h988DbjG@Cq*HsDbgDUMBZwue&@Q_9NsPxQNz;~o<(TD&`Vw#tZoCtBh7PnN;@6^ga|iHayb@*sNQL40_k2iUW9$(lzEZG!L*7RS zAFL96PZ_`T%Nu0=T->xKbwyg$o?Wy#>9j2K-L9ZVt~(?rwY6D)bc%2uhOe@f5gd1i z-KYiONhS!GX}vYN3wsBPt8xNY@T`29pwDu0azd-Ly1EK1jaXFV%s`4zenNpj3C1eX z695zYFYJVc972Q&wizxM0Qw~^Zf<)CfUuzS^z`|r^xIoU$-+=E8~QreVF6$M>Er!4 zPy_>l77b4jHuE*6`G!vYvNN@n^>@UbF97<=EDS^ktn}nPqJ)}F27$LEs2KYG1Q@vnl|?z@W^j5A(SRS1s@)-E zr9W||F=C@VVVgB+o+erILr(RfMAGIPtxH&tz|=lPmIL^T2$CsqnQhTq;l9aUao}1C z0fMx=Bj7tK+XrcSOT>-@h8fnlzVdeY;j0N^>VK-L#y=HUe)_niN7D8cJL~M{ZUg+| zx#BaOrAFn*hqoHr;TsR!unD;%(HK4~-#w+PdlCqR8Y(emt0#mQ_R<2DMezXW;UD#U z&AJ2jR%6(fU)L*pU>IWg?!uFw(H^z8bd^%bMbW;*Z5^~L3nE$IEMD$A@c z(P_lGikn!dOwT!9m0&y z@@~{#l8)^1ldf!2Drmk9Sff6HK>c!e8(6-MB)Em#*-Go5TN$(Iy(sN<+rwx*l7Oou z*3RzX7GnCd!IiYdg0W;s%*qXxH`b=@n3B|$L0Hpkl7Bk+_=p53!S&LB?~2A^fX#DO z{FYZ2GNWs74cp=U^nSp$owuAArb;zz%ZpNvN}Un5u$EY%B^{k;cQH;@p;bewRuj6N zx1dc-&jjQ8xi6ugs)fxhF5#Z35Ecr=7-?*Qfv@!U?vginNX9cwW3|~GKns157lDU(f5X@_7&#CbDtH$0TMjNkkE$@8c! z5`0HI0T9}MsvoXL$^kNop(Y`YEf9D87+2^1;GQ2As#Tm0URMuKe7_Vp^_$MX|9&24 z$@e%+Ug?!HGuk8G8HcgoA5((JMWD4mJA3iWH~#E(-CTAHvkWKN&N-U>cbDP1DOlr=~gNI^KeAy#OV zx_Eo}o%to$rQx;2Fzj%6gBvWwTn~DIY}ehmhz)Po8D8f4W>7-)+cGlsO4Ol>nvmAE z|2lO_i_$jMT^0vli>E#r)&!>mR2^Bl5n!NRdIg+gxN5U%Y>-*hnvz|a_9W`|RqzX) zcjz=V%koZVw@Xe@k|R9}G6QFSD^PeRzdDd%*R0rz0r#kxVnGf%!{ zz1+6SoUUbF35~2QLI`!146Io)K~CmHGVFOhrm7xukU5f~aZO2#6f@$GetosI+~{3d z8JSU(Kt+v)K3i3JwiLBIM5&2f844`10lX{V!Mlw6zw$;M!&2A_-MrwdP}GrAc`k-CDIQ!pp3Z1o62 z3jvzOUpFc+R&t-r@Gn05s{W7{4#v3MD(1(C?L~YY?e0=e<5o`zJF+uS6e(eAo>$6O zMexQJShAq@QSM1B#*(Hh_;>54L{-jVqs-Pvf2sjvQRe-=z7v9rE3EP2^v2!0gb?>l z$@}HxhnRvEgw8+22K~|u-{+0M!D58owDj8OFkXv`mzMLL`4kOZK;lP{k4}IiUfpS9 zs(|!{=L|z!3Q($+K$3n%tw*&fB@Nr^e0M*xZQVT*Mt#OMv!&okS4C zBl-s!Q1AZ`ArI=N>L+f@2H6%t+P{p98B+N?1-_d>41>7X7>az19QU(vsXfdtA8akN zxB&_{5ynE?kZmz$*kC|F%mAwL72PEgDE4ho1eiDr-*yi%Fkut>9vrrX+wp+t(6Ez- zvn6u2dZE6dI_r49VdrQN-SHTJ9++iyQ(x(;k&Txr1dtwz0On3GyKC~4E{1huRsAOH zQ?NbLvAl4vcVyOOdv;0w6jB`~^xrKG&GttP#Z6#C4+ljkDb?jNiaN0w#s8`Ar3-}j zwPi6PuEwXafFea;*)sz4(R(KR28lo5L^RZ=!u<48>uJ?3@d=Bg|b=GrrlwIky#W!R_5hgz0`(X58|owzeu_f+d3ZZ=PlPmN>E z`D0G77hz5GhG0!-g zPMSwTV0h#ry1gHFJ6Z@t-}_(2?+8cdW5JK*`3wKt$a56Tf_6B5YYkyAcAZ>`ocFip zHy6@FrTI1 z%)L2;MVuL1Xs!41UV&jkWO2xrHp4oz2@U}bG2=ot)3srAcCX-A?Z4l+msrZ*A`v5* z{4hnt&I{z)wScyLMrM>ujYHfTAyF|lEI(g(LVFf9s7jM(ntQEt(jrH_Mx9E+7HA5~$Q&FK`P`t@}X zlVb1&#_k$l4a0yFezOg;C)kifBR`h|jj~A^nO97dye=rduHK-Kr@9UZ4!m7rbV;sh z{#LVgH)ZA}DpAe(j(L^sUk0Rk3J62}37P%N6J8M!J;(lFM~5B{*FR$f)u`A!?>%5= z@s*u5e-=yxaUJeMJ1X_EclDX@Q~7JZM&69Ke30${XTuHpvNc-Dc2|m^+`5CbDct{G ziR?U0OQz5gy98RiZ;!|ypLAqehXrj(1CW+iZUyo7*x|o zgbzi^DSDTKC-5{}vM zWQhVVpbp)B|M0_1?_Cd3P;J$@wUJJ|OkM@A0gn=wA+n64p|e=icP+Em;OINQInn~q z|5D4V+Eul@K?=-ShB3iQH`ncUA(FfOz8s!k|I%%SH87ETUfbT=QBnrE{63+unkUD5!uZkDtuw`Ah92KHW%f&> zh=Nl6o@e|bB~5K*Xy*BO4HQ0h_T{auB{q;NmyHFUIcRhKjB-4H8T23C)yZK+d{=M< zU{C;6YWUIOyR<60x|7DNEw*dI<*`|Qa~~%EJQSZu^?#4E7>cR(q#neN;Y++DE(Q#Z z;0bc}>7^wrnuRvw@h)aAc{!xW+U;4X)jfNcBrn*SG&7hrO_gv>#?8;dMQpb2RtM!5 zs|);s-|(XKxwf~I9uafXCt=?|s0RLh^~P#!%7q9y2TB+@xNEl!s-H1cwf_?eQepnp zh~iRJ*yeXEZ+^I?Jj;>)7T>c({tKsppvc>#?R`h zvd}dYOr}%woh!Q_>4;(6O-1l=ssYO9((8yOzJO;*EpW~_UJ`wx_g!{tlM7lG%o9-n!;6 zUR>PY*3!rp)fH_gHP!WaQk{mstBymA3pxzPVko-e6WpQ6d}VFier44LO_y_~mpS`4 zOgKK7EScJ!3cec+Tygk*J=+bLCPl#N-IZ~{IWxVt%Pg*LpD*Rnnh~{hQHbTGtYSw!ZHeLK+I+rcxR$z)@@r>>O>HDG2oV% z6#EUCptQOi%*MuIwzQ)${nGz)Gk&Kp-v4tH&hM6Fx2knh^#AsSv?Y^tLVSEOUx1H~ z512?+R+h1AWozs6y3=$~;IvzCmNQyQJC{TQIg7{fcgCE!Y@C9;Bgu~MumHI2nRI-P z<`Qo4R1K@ixf<-#l&x|4jz_bej`K}3eg2Wj3%z$MQ@AD%oDfvyHOAIM3Hgtm0Su;& zzl_PAyCfED$iH0F;y1IoB;W8)_h4%zw`ED6KT?>EOR3E%c@8)mJxDqp7<9^H zC@iDpXy1+4ZS@m4GoKgJS@YdrDvPDyl~swKSMGZxX##6HB3Xe$sxI zBr)oP3pDTDQ(rzytut6C37@#_K$83@z`JjU(N zH(zBZ&TzJzDEfyVL8+||?fZY+o8;P6K9+NGh@C8{bQ}+l#J+pGjA~n4k#%pU%;P8c z^_qg_2Rgm(R2h=4tf#u=kRpa4x&wLNXVSmAp67{Mre5h{iy=;zZSZdEG9Mr?>OH>x zeW5t0*t!u>Op$8CS2aX!@9OLlt6<@yX#QU_ zx(Apm+G{mm$dIgQwT423`r3MbWIgaszkceE;r`1ws~dA`h44I`D# z>}$@ZNMwRF`5)GY$qXF&@6Q~qOUS2P>phT)hJ8r-HnV?lWrf#f@RF2L6a^P%*=KI# z-oLpcXqVn)ca{%ork*J{sz(m$VNI7|w$)vq`aLFothFNtj<9a2_uY=v*M`6y_M2+X zSW?ufV^wm#^IXV(wgCv-&s$GsjtYv(pJ&@OJglr=473KkJvXey_vnWWq|bDWEi*MY zki}avVuj%d5kr<%9Az~w1xiq;a@+7SmbZKRAZ<2CC{@cAvrC+Et+J;4SEM+m2*|pV znXBWFU&zICByFbIe(oUYn(gvi#)+KQr_BaTam3o?qisq2Y*J~6+hBM8=i>I;H7br8 zSNO)gvwih6_2(LSkM&I`gKr-u)%4;VHLoUk&)hB zA$j>&@q&}fOIu4zOLKDtYE00%T@c5mNq(UY57v`Hrm@HZ{4x*n>3;yW#|o)g24J5t&RW+>&{p z@q|&hamS212}8)*uBoeri&TJ9vKT|oPT{`}2KEQ+m#DDHJjbv_j#(4EF(+g3(lnJ? z^uLPN#N>ZL`OO-N3$jmwvNo$?V!^^oxW3|pyt_TAs~Mur@bN9_2XS-1ow*%kOn;gn zmSi5N8My_oM4Z%@ITt-OP5(}IcK>2QbTFSL@q8j2meK3}potm?kg*lYK%RFlpd_1^u&Po0nEfCpt8VMw=;r4oVb4_VVXZiWhQZ z@Do;&QE{=XKE(76#i11WrzO;%Ah!i>Nj91r`|Zagng|S^UsNsy>I(XNPV!B% zdXkqOJwNzt`Q5wmPIa){4&M1)1DOQSO36<>?x#3CUSOPa|EVlle!E7MurYDd)VN@q zNLiWq_ji7^t?;yL;CAz8@Tc;x58=Gi{0zce&YV9M7Wt)FEt#2>7?L&|-d5b>;XhQ{ zo5HS?BfO} zlWyaGd`EKlI?=yRt3xFHYf_U`*Yo?*m`g zz-rZc-CtryI_jd(LN?5Iy~9M#R!*uTkZEDivL21qRnf~%P zfO{`u+}D@PRwl#j8q=%vZObb`P@U#>95K zy?VJ=HePl|M32f%!4u)o{bJDja*8J8Uo57wtCnV}l!^E#V;;iLK8(6r`e{1m!E1APo5<$cU@Z@bF-Nf3L}?*J`yuLM<&VDph4dVsdzR zI295~g+$%_;M2v1Wq}$UC!rlg9MK^XVPHHBI)1YdE*}*^MtfD$xoeH+OfAN$^2?c3 zJ=D%~sqhnSeCSz`sv}#~QQA3PpkEg2mhO4=0H=p*A9VHvX-IZN4|VurVr4L#**b>T{fY_2_{N&F}Fhi~vdjB8>^E#(ES zD!vUcs5Hw{#6ol{7r3gV)&|LV`DdkZuQT3_t#~>X zWDE83l{%rll{R zrKZHuAap3G5QY^K{;i))@sqX@=!(Uo7@lH%|Cpo ziI;1sldRSu386`*oJD(2Gkh5D#`_AQ$!HiNuuE)*YZzSy{L1mkL3ut*bk6grXbYth zzp^QFoejm7`rBXmDO$E7BdO5HE&I;;Q!GEapfyI;olxGBRymqgJC#*G1!DkS*D|~X z?Cw>}Fr@PU#9-bpwNqmB<^y+S=&hS1Hf7h~`F0V8g&KhQyHasv|a^bTGR z);#8r$uECk#8EIr#yrphpfL#(RpYFgn2xju8WZWAg(iDJ=j2nbeL!v6jXXq!zW3gH z3WcHpq_MfZqsw6IG4=GVw-0TcFAGXgg7VeKcL;_8VH)cv=5cmxYG!-_%|gRN0|Wi0 zzFvb~-PzgE*4S8ISzVQvU%Yq!DVX&oV#~gZZW1?-7onU+1$>)hL(R6Mb1vV7cTE6}OQ*dS zIVwTdJI#>2#Ex>n!N6v)5{`fefpcMCT}5ECfRmG-6zL>Pd5zo**=g+J541 zK8QJ#R~sW&A1pH@*NmsuOl39BG1?Ze(0#`eTVu`YwxT&l>lJJ1)TRI~CIA;=-SJOb z2ORs3b_NQ(zy4emfeqG`&G~|T179IF8QlWCThi;}Yi#g37wZ=cySk&by{!ovELoZ2nrpw0by!0-Q(O0^$vQET%io@^k`y^%F(Q()K6wL&n8umB~%P2$ou0; zHSzgP&%JXnY~N{^Nhk{EN}l@s`MlLyp{w#gFx~_;s9%`^32|I#9n5l*(h=E57(TyHH~V0u#}5=)gm{t)p8`zZ7E^sL->{JiQ<{ zyRxFZzP=uKNUhPCOeSI+0v;lfWgB6LXi$O@lrK-dLohVEA7!>NdUZ^w%ZvYW$s^IZB!;Dg;zl|@+tyv@UdyS_=5^2JtLKz@^Yiauo|5nrFD}&E zp*~S^f1U0C~S%HL{r5vA}@2dbF;(An4U!!!TCa#H1 zox2qs$-`lAvg1cTdnsO87hBSGNN$X+7)r07N~@d9Y?%dxv4==)7j#+CqfY1H8qm`j z-&UhbXmW2PnB%BoI!ZL#=;rG%(Ge&NjdHtY<8rF0Z6{;+1x5Y%_8%%`#lS-?t?exx z-5RZ)lr0+?8X{#n$Yk02mMSPg3Cfo(-z^vl2$_|iqGRbtMn+&**3;LkGw9V_nzpvK z`o;!TbyZAETv%8Hss4EPb8qFT7xGOGx^Uya2SYGG6slK${>GYDLqYvIE7vOr#mPsX9DUlHGPb9JV=EV}Rx^x+yRJG7`5x=T@3`%! zzl_PQjFW3(l*U9=e@5*@YVA0ueSy_9%Wq$V(E?9x<)Qwi=#$|`E=0fss{yY+hHq5l4UXp4Y{ zNN`zTHb0nyF(^U#3go*8LxhB2>@`Ujr5_y~hU*y^8iXO5R;#V6Z&X&+M99wX<|KK@T?K(57>B=q}Ce zvhmxNxviGW=Bc!%iKN=G7}db1rRu|FU59e2?|r=5AxdPS*@0g#u;|!BDWiOXa!5wXk7w5(g|^w-uRv#N6qQM zO3Ass7}ziA!&!28XC->OqG$QWhn~eU9aLykM5ItCEGw_9tZis%Z6}fT&IK6`3}L*dI#!2EIkax`uls0Mk8QQTU%RmYfE`mwMR}7c`VGb`0G^Z94Ut58|_RQ=*<_J~~Z%`Fc#Si($iEHg4w%r)`PR zY))$!O{yD;R+!=|dJhz~9+uYr=<#1uq1*7XBdJqvI!Dx3l5MLN`s$cIWv07CKu1Ug zVJN6yX9Wi0_~eL0Ub(=hgauHhtBmcd;Cd%gtsfsOZ zO;+ktl>Hgi<4IMcysqV}j(KJm@({m!MW}J|bsl($KxupJvG3_|RCLoey5Un~^fEee zBqO48KZ?8N^V>#topl*q);{dQi!V}^S1Bqg8ycEAIy#{JfmSFGSw<$y0`{RG4+SMC zU$%VbU?@OBB$giTwtof?E(z)@{25I+vfo|^C zAE%%H!$-HCef{+hJpY&UyyCu9k#Szsvnnzn014F|LARUN>0oy*Gu!90TIbUmrxU9O zVk?ZX<%Uml8sa21H$U(TDr9R&R1_9}xZ?uhSeej{bu%)2a)wvN^T>s6)F2E6_3Nx$ zUpd#SWI1FUmmKh&>ntUC99XVWo=?SctGL#R{KbYV{~ukd7L$K%KL6@FJ{KvXi(2Ak z+GNE*YUM;~-E>NuC9T84>R8})E{QtrLbaQ(#X-f!_xJcJa1W?Zhe7y~K5Q{K^z>X# zk2hC6e}F5)u@IKE5B=pYpMy5As;a7~sj0KG6L^R?4~+~BPmGOEO-xQtPMN1?=FCAD z3QADEboma!5Rvs|H-?6W`uqEJ27Nb3ds|~eol;dU$`8qFA|3Z;wMo?uq?TXvzd9lsZ?sNXe zC@s*<=V&d3hLwEHV!nE=(6A&i+Jr_sPq!@8S;acHpwq?gaB@0UfrQdq&8bbZNwt%4 z<$bYb>cfRC(c+40fBXd6gaF_osS6*xTryE6a#S*WiVUA3(^m$AZ0J#hZY9qdgrT5* zt(D=F0Sxh73ix*wxDq%{aRlatK-)-f0I+)GNKt(A{ zQ_0O!@pY32<^4(W-dJf%yr}ZBTOOcIP2n(BzVxvha_2E=4IBCuUmyz(DgO1JBt`tL zDfGmk8`lT*tI_rRt)KV{&xLJ1YpH#mI+~Yo^m0&7iCfEZE2EvI*3{Lm20Up8Ir9=c_8JYU>(W+d7QK9%zn+1_nXLN5=q$W~QcrhoJci%<=~%C_(u$ zWW)`f)=L8a2O?D^Ym?n^R=v%GB%c!FJt0Md{}>;EN(iYSW2SSR}MI z4?p~HNoiSiOYCWk<6llJ)X^sBc#^?Pd5UMh6QpAYehb=KS-mqfep+}qJqBtX)x ztM31KX|I*nwJgwM(Ezd9C+zk?3(i(Au{#%;?Tg6`vl(r3F*W^hwLP(NT}*ywoS^>u zcRzzT1wFwQ>gE^klg`zM?Uj6=itSU7w8#QXRZOa5R(Aq3HhLT|&4;bh4V#d_$9$x{x`0pGf zk!4g!=z+sAz(e&7O>J!*9UWa-wXWCH2RuY3%cdv&p47qAi$Mv>mnYwH7z&V3z%oP< z3k}e9>4AhgJKCD-YwGLk3rZE|UVJ58c4O17(=IG*9nR4#L5r5tk5m3!16Fes>HXBy5Wq$2k0Jo~zWPBZOd=_=GN zym^>^&JP~UY3`M3EK-vjYbR-a!Y&`b)5q@l_`31KHO52A&X~N0 z!-9&N?)w$S}Ux2VscqmLId`>jpz` zg&QyOJF9g04pK0L7&7rc7XHMDNKYx-Efe~b97hS?3wWeVvlsK6s+eH>MO)U+zWwMgzUwjp7ZbWXP!nYsdl^1ANvW-p=sd`Ok zD1l`dRsy~R0_kZI3RK?kkdYs&+mX}f6KQP{?GmeYkUHyzTc7$}T2Wn@eyLQq^kHTp z6}g`Z+xv^xKg~B-B?h+`bF#Svy5)i)uRyaR(mK$mLF*NC`FI^(X6G`ab3UuZoZc{< z+%R)M*?XX@GrG7wmS1}J6E9*)>rGMAR_f9xuahp+012_3(4CZJ`zo2%LfYBFKQ*=n zL-d$?L-!NZ^-Ai0{jbV#$=6i*g-S|>ZPblt4C*n;rpvoBe10CH|9i_bT{4cZ9A|#L za-K)dv=<9}6+&O7&{HmUR1~b%-T&Utu?Am67!|(l!kh0sAW%lBTTt)*eaNP9E#(6OysP=XSa<00RE7+P-=A`xVLeSJow zQKP|R(~S*v)hY#(k-25-PAY5*6}Ih>SKlu+ImB2F4C|qC)Xr?W{SxS%ViSgn1nSZY z*BUzlLp&XZn*kDuG3E3Mw`DT&;ybCR)2Q%W)RukM-up~~q_(7c;^v<{M@8)l+k2Lv zemGla&!%@_8N59DEYN^rnrWSr*X3b%*kK@>)jFG1Kb2lH9$P*bQ{Ed>)^Sjxc=VUA zP@z#+%6lty(UaE~E!B!vRRUinhTFJHn2sVCH)Ds5qv}df(w*R#8~v$|?R$c{UXKQy zBg%G`tyh*qwx;qgQ~=YB3XnwXKmpx&0Y|RnYi7avYLvK3GrfUw_kQ~j(;??#P5bgp zTcNYmZ%bn+LGml6E@!}x?o!8Ot`L}K+9W=M!$I&`4Gqu((F*tLLPKkQ)4>3F{oKA-k z68ewNK9?)X8=G1h8=HZL3_5*Z&yh5YK^O{3P`*_8mctNEw3BMA>mtZZCX-I5>+I}o zY;3Hqu9laTe*cE+@EL?|+Hw93Ih|&1_o}cLU7<;-#hgB$Q0KrovR(|Z|DRxpgqvZt zYPCbEcVt!%-1@6`sS_{4y7Un{=(zWtSN-TG|A^1px$6XIt?zvH&7vNgNb5i+A^ONb zT^!8{OSi(*LVIn`YG2K0ozG~R&Z-I&DZ|; zu|uMYc$qp*(UVj;o>(=RQah8?vY6G5b^QYKWyjHfXd?`fmAGE(gcbuZNNRG4w6>fc zFGgDER<8QdgIN7AJZk@`XA~BdD9bBqYwJ5ZyEWZvlfj>caddoaVrr5Ua6Pt-3rbLe zay;Z)2t#wTGqb4ZY&c_{gW(jMadOfhLDtvTr!(lg)ZK6awKY}cO8H;^@(w%z8iqE1 z%2nnVoPZ`AnryJ(JO<)Aa&WQ?1MK}IKkBVu_9Sfu{xJ$`}y3s2_js{t6| z`(a2y!w{0az*#QwR0b9~qcCHR#h!ii|2si((9s@&xl{n8%Cm#X~%GTLn z1ceQY$tN~ASjrA+`-y1i4Uc-_$)7{3SY27wP=__`H5!e-hU@U?=-32Z(|+>U_U^wg z7Fz#_kOmpmJa=SyP5+7fuSX6_P>!2?+h7Rz$1*cHH#;?JnVX)oOqwkdGiD&6iSgkv ztU+cnntD4m+NQSlrlzJ!Wm$n#eBz$n_!J^G{pjIe6sawFXr%FRu{pyQjWlHbu^rva zVX!^4aaw;;HNL@(Y-IHD4Nie}wW!CDChPt%Mf{uh53%H(Wf}|SQPi%aDs&J0?r)K2 zUrU9bK!xw5!gp*v|5|okPrk{PZFF-rE+nmPJAfCE5N6J8UrDJpGwRIg6(cbP`h;Rl zw50yU_hP6myD`gL6m{|s&fty|i5wMt7g~MRIzLP-L@GMrpL8V7I*)0bfY{gr{de^! zf10DNmVA4x`a4U^7l1|ro<~U>Z2h-P$I$USaxR@<2_6N@jrBf>-GzAbupDxP9-E9| zFUz!+rLUA`ESIvZa^`AT#$qvzF9nD`3wf!Qyp%a9C@iE~^5HXiHZRF6h0m0^{A5c( z%6uU#9HXhTs43<=&~!^-+FU`>bS|vPm@i75m1ZvHr&%PK%Xyir1*yx@)YZHUdm(6+ zy(oP(A6Bw#MI2i(*IvSNl!D?k5RidO#&VS41Mw=!9np@)3XWGv7RVadfR7=Be&Y(! zjZc!S@gL!9rt2?~Tp_v0W3E4NlN$V1c_+H*Pp!g8;R$iEAI6_ z6z35h9c)h@+$GVjJpFDAa2pj89vKnF%t%$06*tyawY0Q!cByqb9l(&WZ*X*SW_)bK zJTn1D-!t~563_AgC=H|@v^Yhcw)8uQQAM@OlUoDf<*d)^$ zSc!OqGBYz{u~-Np&CHr%H#jUDJjg>q`D*0b2t($Hc{99Y07UZ>Gjrq9Gvni6vtCE*Kc;AmBFajkqZ1-t-O?^Nu0<}$KM4J$476S|vFbH9E zy~dtn^5xQYBS{|?JmTp*5~D3wXZiV`4^fdPQBixTsNGc9-m|{{)70|5T%#*jZ*d-n=7Fo;Dc(9>6`DW?A3q$Lbz>t4O(sBB&K3LF-?JQ-v zN(lxrooMnoW&$gHr8sT5ByF)67$|MN5Wt0I7XY1{Mdy*+dh@!~sp7%pnV~BP;_LCC^AP$8q7S56| zmz!XbBrfEFCN1QqF6Lz|7cy6ivz7`#nJY!OY%OHkiy5{ehOLCvjsdT_UXucqse-3`75}WgsBMA%nrtt=0~WP4)~70}M@0j9?nZwG^`0;{CT_ zXc7jV^UIUdQ%ft0Gc!|WGiF5s9wHD!rvpHP5YcJN)U*XYkE~gXkF&robZuJLeQI%e z0S<_VMeqy4P*A=K`8L83u?)@5!eC``YG!<5YIJmXcxa%%uSc)fYW2GI&W@V8TA56i zoD>%x7J_9>!*=}cGfqDGP1_{~caG7+@3v;^{Zn89+E7GH-`RRhAOs5E^1AI^S^_HE3p7=@XpsfeAJ0VsSoweQlDirzeUc zs5o|cfxAvLD%*bDDKK)NLZc#gp7r(t&S%;6pO>g(%6k*4M>3ntnJt#|mIY?#Doc&Y zhX96X9y*dfDcoHB`9BxeA{4XmgXUJ7BZWsW~QOt7#|-W9v&VX z=UEmt=El0Zy88P1l9G}$&paJTC}PJ2xBN`3UIL~O8NGasO=@z}*+Gza*1JGr z(nUQ26DhKhZNT|5bh0-1a&`9Pf*RTywBw$~pUYe#LK5nN~Z1VAqzKkv>z!p>E%xt&tx|gyV=a|j&2~~qJ<)&zb;U78m z2l! zspLE5JO}!YpaYOgk?B%q*_Bx~1#3mlTrOj-mS!#$Wy}|(Tk^?g+FTxO{*fk3XU9$m zfqN1_M+KmXBcj9sabjO~La#W^Bup>~6OF+{C_| zxZdp8K1oc!IJ#dHJ1mYH$xfP(CQL}B2%GaJoW%{U*;=voCB5^9`c z4Z@Jn;Nlr!h;l5od>JaHgD^zhizSBT`<{Obhfh@K&Ru(;IhL1|Ha9odH@9_Z4H}IG zny;bZQLI-+huPD>@wITKo;J@|7Urg>Cl?khX7e;X-!(HgM_Y#0NNCo)j5@PwHm|~` z|IhU>G;4uf=tSG7^Ye34QxkC5pu>1jzB2h1!VnSj^bEXCljBFi=?yx4S7&=?M_aj4 zURqr8^2;w{ip8+a)aKJtDux`J|$I^r7Frf920!Eil>y>eZY+ z^u^|Bo!NB28bjk@YQ6Z`WoLCPF`8$y>dYw>pPrdgfs@JGn!W-jzXsJJA3bi z`?sC3WAiCnqE6ZrvXjE{SfD$pkiB71r$lWzZQGVJx9z_0#M7@o`=UFqy8dTBxZ~OT zA9&@lXWswq9}?gFtT-XICNsH(o7BNiQj6lXf;hb}aX=E=Ba9!E#0`lL4+>*OMX}@A zu~RvTv-ydWdCAiS$>ze;x#F}%oYYHQK#y$33fj7WhB4&LQOayG_JzG3QB3VfCDPr~<>XS)@=)gr9z>Q?gHm4f9;7&%ZU zMdD;x$mV_DyYf?B@nKo}fzs}HxiPJ3IHO@Yv&E9py2$Rf@w@E;wL_?L@C{C`$;C5a z*}*`@`7cSubbJ}MHX19xaU|-Li!d#9Sma%I-BnsrR9;?Q*VNkHt?lmWFd6j#L;b^J zW8?lDi~-}&e^&s2ES6bNXm4Q@y0~D$#&>!WK2c}Pw2~p}ym@AR_K5n&t*C3eEX>cr zCz23-Oq#BO5|pn(zLhWp1C%*c<8Z^mBdL%G zDr(=OuOG=Uq$1)!#MZg<27SWLB}w;cLB9`Y)-*Pm zX%!2NZQc{H|NNpl?azMw2DRy=ldiruuX*UL&$FoT{nW0DbGjDznpJTxrW*ka;^;jr ztt+$J0XW6zv_p%`Y+ZoXIlXE!NjY>_-up>W%fbAr??3W$I;Bu3kkI3Qe@<$xW3I?0 zzB=@ffHASN_}ISTo2LIR3^BbGOiv}-rD8kExefGW^S=K_vY5{Ahkguu08pGf0B^k~#P@E!kl>;dLL601TBD=G}3}9aLxp6}olDxz~!@rt*5+ zz(eQ>)azqu-6Xvbfgzzk=mT3WzXP?-#%Ub;`P=b7d-ql?*X&FWlX zwk@;T7Bd@XGHNH2%ZKA+y`SfIeHA1fWP7 zcWCPmlFUAeI)S22JX*;b_}oKpvzy)+bO+5$Tj?WUdG&};q@*ws`@MF?tyIX#VW-}3 z#;woY@tgS94k+T|o0*B7?BpKtCtB8_L2=ATcJ#0yW|SW{!H=KhB~0;B&B8RZIAczd zJ}=E&D9&0eWiBZIl+mNeRt8FYTl+)wdGrk0Iv6_goHza=-z*jRYM2f=k2aAah`vl z&dyF~Hv9SqhlhuW=MdqcW1GLPTr3s~@nJ%zY0T+193G06xUeuRjzTni-ST=pvOWxV zfgPu(M$taBurNzkll(6=^@?((RA=NFt_(>ME<#a0Y zR4RNQ+O)&B-15+K;%ZY)>kzf$G|aCEBhaYu?4q(V%~Vn6YLrGWnsuCJW$D+dhXdVqvvB3~u&P`0C{V7dO*SMJ9L4Gc={c=7em z4&_!IC~k>S8Ddp^S+!Hl`dLQHVrIv3hQ`X!JJ@vcp+IoivG{UamCVGE#E;Ozq$%}u zPO>N}B>d%!K$99Bz(ZdL7$UJ_a3MfM7L2PO2dr^8du@(x zo3qvKY_>a_P+bjnSDnpW2cNVy_}AkGt+27((E~f8eP|Al3Kb6fbufJegt!Ku*x)KLEdBhCf2SgLW26|S3)+k3rI2tcWXGnHFFL?4`#U3# z+I%8y<)$vV;>yQ=^XHQaR$bY6b9o!@O0sBM`|W_HJX za`kXZ)j+&lA5)|`nA`lf)V!@He-C@f2x{k5CuWb!#14PVIn%4i^p!EN6f4G39YYdd zTm4sxOOS0yC(Urmpl`z@gl;+Xb_{<;#8T+sfq^m>^FirUK)K1YlEfKtyjc=6C5j%! z(5?7EVSpi?f>o7H^e>q zi1c?absqd|oXwaM@-0ICf`GdykHKeLHH^k;Dr0{3&r5hn1LS3H^s0r7(D4I+S8(O~FF#g%4heKRp!{zWKB_ zqB9TVRY%J@<12boRKpoH(^<_6S?#M?8V5t`#EEPghDh?EjRbRil^z{A$wH21>Cu

1?AX@uD-k%a*$n4iL?>v> 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

@3G(d^y~JmR#=7ZT{}tX|);v2u}xR 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

xp3~{m-RX4tzTdsGJF*oq6Io7< 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 diff --git a/docs/application_controller_interactor.svg b/docs/application_controller_interactor.svg deleted file mode 100644 index b2dcd9e8..00000000 --- a/docs/application_controller_interactor.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -
FastAPI
Request Data
(Pydantic)
Business Logic Controllers
Request / Response
Data
(Dataclass / TypedDict)
Application
Interactors /
Services
\ No newline at end of file diff --git a/docs/application_interactor.svg b/docs/application_interactor.svg deleted file mode 100644 index c691c44d..00000000 --- a/docs/application_interactor.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -
Application
Interactors /
Services
Request / Response
Data
(Dataclass / TypedDict)
Abstract IdP
Application
Objects
Application
Other Ports
Domain
with Services
Application
Data Gateways
\ No newline at end of file diff --git a/docs/application_interactor_adapter.svg b/docs/application_interactor_adapter.svg deleted file mode 100644 index e4602d6c..00000000 --- a/docs/application_interactor_adapter.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -
Application
Interactors /
Services
Business
Data Gateways
Application
Other Ports
Domain
with Services
Application
OtherΒ Adapters
Business
Data Mappers
Business
Data Source /
External System
\ No newline at end of file diff --git a/docs/dep_graph_basic.svg b/docs/dep_graph_basic.svg deleted file mode 100644 index 6ea56bc6..00000000 --- a/docs/dep_graph_basic.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -
External
Presentation
Infrastructure
Application
Domain
\ No newline at end of file diff --git a/docs/dep_graph_inv_correct.svg b/docs/dep_graph_inv_correct.svg deleted file mode 100644 index 865bf52d..00000000 --- a/docs/dep_graph_inv_correct.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -
depends on
Application
implements
Infrastructure
Adapter
Port
\ No newline at end of file diff --git a/docs/dep_graph_inv_correct_di.svg b/docs/dep_graph_inv_correct_di.svg deleted file mode 100644 index cc9b7146..00000000 --- a/docs/dep_graph_inv_correct_di.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -
depends on
Application
implements
Infrastructure
Adapter
Port
injects to
bases on
creates
IoC container of
DI framework
\ No newline at end of file diff --git a/docs/dep_graph_inv_corrupted.svg b/docs/dep_graph_inv_corrupted.svg deleted file mode 100644 index 3cc8eea3..00000000 --- a/docs/dep_graph_inv_corrupted.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -
Application
Infrastructure
\ No newline at end of file diff --git a/docs/domain_adapter.svg b/docs/domain_adapter.svg deleted file mode 100644 index 4f810f65..00000000 --- a/docs/domain_adapter.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -
Domain
with Services
Domain Adapters
Domain Ports
Business
Data Source /
External System
\ No newline at end of file diff --git a/docs/draw.io/application_controller_interactor.drawio b/docs/draw.io/application_controller_interactor.drawio deleted file mode 100644 index 0a6ab2d9..00000000 --- a/docs/draw.io/application_controller_interactor.drawio +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/draw.io/application_interactor.drawio b/docs/draw.io/application_interactor.drawio deleted file mode 100644 index 3382519e..00000000 --- a/docs/draw.io/application_interactor.drawio +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/draw.io/application_interactor_adapter.drawio b/docs/draw.io/application_interactor_adapter.drawio deleted file mode 100644 index 9c093fc0..00000000 --- a/docs/draw.io/application_interactor_adapter.drawio +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/draw.io/dep_graph_basic.drawio b/docs/draw.io/dep_graph_basic.drawio deleted file mode 100644 index 7311c943..00000000 --- a/docs/draw.io/dep_graph_basic.drawio +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/draw.io/dep_graph_inv_correct.drawio b/docs/draw.io/dep_graph_inv_correct.drawio deleted file mode 100644 index 66218ccc..00000000 --- a/docs/draw.io/dep_graph_inv_correct.drawio +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/draw.io/dep_graph_inv_correct_di.drawio b/docs/draw.io/dep_graph_inv_correct_di.drawio deleted file mode 100644 index 462edb31..00000000 --- a/docs/draw.io/dep_graph_inv_correct_di.drawio +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/draw.io/dep_graph_inv_corrupted.drawio b/docs/draw.io/dep_graph_inv_corrupted.drawio deleted file mode 100644 index 0eab550e..00000000 --- a/docs/draw.io/dep_graph_inv_corrupted.drawio +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/draw.io/domain_adapter.drawio b/docs/draw.io/domain_adapter.drawio deleted file mode 100644 index 42b6eb1e..00000000 --- a/docs/draw.io/domain_adapter.drawio +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/draw.io/identity_provider.drawio b/docs/draw.io/identity_provider.drawio deleted file mode 100644 index fed319c4..00000000 --- a/docs/draw.io/identity_provider.drawio +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/draw.io/infrastructure_controller_handler.drawio b/docs/draw.io/infrastructure_controller_handler.drawio deleted file mode 100644 index 301f4875..00000000 --- a/docs/draw.io/infrastructure_controller_handler.drawio +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/draw.io/infrastructure_handler.drawio b/docs/draw.io/infrastructure_handler.drawio deleted file mode 100644 index 5e84d560..00000000 --- a/docs/draw.io/infrastructure_handler.drawio +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/draw.io/toml_config_manager.drawio b/docs/draw.io/toml_config_manager.drawio deleted file mode 100644 index df95f817..00000000 --- a/docs/draw.io/toml_config_manager.drawio +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/handlers.png b/docs/handlers.png deleted file mode 100644 index 8653d5575ebb8b4e736c71ee2331b791eaa71cf7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 401394 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

+XN2I-+mGCC{v>HahU`j14!!M+d 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%0D1c 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੄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 J^9}4n{s{pW?S=pV diff --git a/docs/identity_provider.svg b/docs/identity_provider.svg deleted file mode 100644 index caf1f882..00000000 --- a/docs/identity_provider.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -
Concrete IdP
Infrastructure
Data & Services
Abstract IdP
Application
Interactors /
Services
Domain
with Services
\ No newline at end of file diff --git a/docs/infrastructure_controller_handler.svg b/docs/infrastructure_controller_handler.svg deleted file mode 100644 index 9180f590..00000000 --- a/docs/infrastructure_controller_handler.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -
FastAPI
Request Data
(Pydantic)
Infrastructure Logic Controllers
Infrastructure
Handlers
Request / Response
DataΒ (Dataclass)
\ No newline at end of file diff --git a/docs/infrastructure_handler.svg b/docs/infrastructure_handler.svg deleted file mode 100644 index 9039a618..00000000 --- a/docs/infrastructure_handler.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -
Infrastructure
Handlers / Services
Infrastructure Ports
Infrastructure Adapters
FastAPI
Infrastructure
Data
Infrastructure
Data Mappers
Infrastructure
Data Source
\ No newline at end of file diff --git a/docs/onion_1.svg b/docs/onion_1.svg deleted file mode 100644 index de0a4f81..00000000 --- a/docs/onion_1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -
INFRASTRUCTURE
APPLICATION
DOMAIN
PRESENTATION
EXTERNAL
\ No newline at end of file diff --git a/docs/onion_2.svg b/docs/onion_2.svg deleted file mode 100644 index 690ac5b2..00000000 --- a/docs/onion_2.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -
INFRASTRUCTURE
DOMAIN
APPLICATION
PRESENTATION
EXTERNAL
\ No newline at end of file diff --git a/docs/toml_config_manager.svg b/docs/toml_config_manager.svg deleted file mode 100644 index ef5eaf5a..00000000 --- a/docs/toml_config_manager.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -
config/dev/export.toml
export APP_ENV=dev
make dotenv
config/dev/.env.dev
config/dev/config.toml
config/dev/.secrets.toml
config/dev/docker-compose.yaml
can be used by
app
can be used by ...
fill in
call
call
get
... if this variable is set
\ No newline at end of file diff --git a/env.example b/env.example new file mode 100644 index 00000000..51a11b27 --- /dev/null +++ b/env.example @@ -0,0 +1,21 @@ +# Example +EXAMPLE_SERVICE_URL=http://example_service:51888 + +# Uvicorn +UVICORN_PORT=8000 + +# App +APP_LOGGING_LEVEL=DEBUG + +# Jwt +JWT_SECRET=REPLACE_THIS_WITH_YOUR_OWN_SECRET_JWT_SECRET_VALUE + +# Password +PASSWORD_PEPPER=REPLACE_THIS_WITH_YOUR_OWN_SECRET_PEPPER_VALUE + +# Postgres +POSTGRES_DB=clean-example +POSTGRES_HOST=db_pg +POSTGRES_PORT=5432 +POSTGRES_USER=postgres +POSTGRES_PASSWORD=password diff --git a/pyproject.toml b/pyproject.toml index e92e4faf..87f96f58 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,92 +1,92 @@ -[build-system] -requires = ["hatchling>=1.13"] -build-backend = "hatchling.build" - -[tool.hatch.build] -sources = ["src"] - -[tool.hatch.build.targets.wheel] -packages = ["src/app"] - [project] name = "fastapi-clean-example" -version = "0.1" +version = "0.2" description = "Practical Clean Architecture backend example built with FastAPI" readme = "README.md" +requires-python = "==3.13.*" license = "MIT" authors = [ - { name = "Ivan Borovets", email = "ivan.r.borovets@gmail.com" }, + { name = "Ivan Borovets", email = "ivan.r.borovets@gmail.com" }, ] -requires-python = "==3.13.*" dependencies = [ - "alembic==1.17.1", - "alembic-postgresql-enum==1.8.0", - "bcrypt==5.0.0", - "dishka==1.7.2", - "fastapi-error-map==0.9.8", - "fastapi==0.121.0", - "orjson==3.11.4", - "psycopg[binary]==3.2.12", - "pyjwt[crypto]==2.10.1", - "sqlalchemy[mypy]==2.0.44", - "uvicorn==0.38.0", - "uvloop==0.22.1", - "uuid-utils==0.11.1", + "alembic==1.18.4", + "alembic-postgresql-enum==1.10.0", + "bcrypt==5.0.0", + "dishka==1.9.1", + "fastapi==0.133.1", + "fastapi-error-map==0.9.10", + "psycopg[binary]==3.3.3", + "pydantic-settings==2.13.1", + "pyjwt[crypto]==2.11.0", + "sqlalchemy[mypy]==2.0.47", + "uuid-utils==0.14.1", + "uvicorn==0.41.0", ] [dependency-groups] dev = [ - "deptry==0.24.0", - "import-linter==2.9", - "mypy==1.17.0", - "pre-commit==4.2.0", - "ruff==0.12.5", - "slotscheck==0.19.1", - { include-group = "test" }, -] -test = [ - "coverage==7.10.0", - "line-profiler==5.0.0", - "pytest==8.4.1", - "pytest-asyncio==1.1.0" + "asgi-lifespan==2.1.0", + "coverage==7.13.4", + "deptry==0.24.0", + "httpx==0.28.1", + "import-linter==2.10", + "line-profiler==5.0.2", + "mypy==1.19.1", + "pip-audit==2.10.0", + "pre-commit==4.5.1", + "pytest==9.0.2", + "pytest-asyncio==1.3.0", + "pytest-cov==7.0.0", + "ruff==0.15.4", + "slotscheck==0.19.1", + "tombi==0.7.33", ] +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + [tool.coverage.report] show_missing = true skip_empty = true exclude_lines = [ - "if __name__ == .__main__.:", - "pass", - "\\.\\.\\.", - "from .* import .*", - "import .*", - "logging\\..*", - "log\\..*", - "@(abc\\.)?abstractmethod", - ".* = NewType\\(.*\\)", - ".* = Literal\\[.*\\]", + ".* = Literal\\[.*\\]", + ".* = NewType\\(.*\\)", + "@abc\\.abstractmethod", + "\\.\\.\\.", + "^\\s*raise\\s*$", + "from .* import .*", + "if TYPE_CHECKING:", + "if __name__ == .__main__.:", + "import .*", + "logger\\..*", + "logging\\..*", + "pass", ] [tool.coverage.run] +relative_files = true source = ["src"] omit = [ - "**/__init__.py", - "**/alembic/**", -] -concurrency = [ - "multiprocessing", - "thread", + "**/__init__.py", + "src/app/config/logging_.py", + "src/app/main/**", + "**/health/**", + "**/alembic/**", ] -parallel = true +concurrency = ["thread"] branch = true [tool.deptry] root = ["src"] ignore = [ - "DEP002", # Unused dependencies - "DEP003", # Transitive dependencies + "DEP002", # Unused dependencies + "DEP003", # Transitive dependencies ] +[tool.hatch.build.targets.wheel] +packages = ["src/app"] + [tool.importlinter] root_package = "app" exclude_type_checking_imports = true @@ -97,24 +97,58 @@ name = "inner must not import outer" type = "layers" containers = ["app"] layers = [ - "(run)", - "(setup)", - "presentation", - "infrastructure", - "application", - "domain", + "(main)", + "(config)", + "presentation", + "infrastructure", + "core", ] ignore_imports = [ - "app.infrastructure.persistence_sqla.alembic.env -> app.setup.config.settings", + "app.infrastructure.persistence_sqla.alembic.env -> app.config.loader", + "app.infrastructure.persistence_sqla.alembic.env -> app.config.settings", ] +[[tool.importlinter.contracts]] +id = "cqrs-common-must-not-import-commands" +name = "cqrs: common must not import commands" +type = "forbidden" +source_modules = ["app.core.common"] +forbidden_modules = ["app.core.commands"] + +[[tool.importlinter.contracts]] +id = "cqrs-common-must-not-import-queries" +name = "cqrs: common must not import queries" +type = "forbidden" +source_modules = ["app.core.common"] +forbidden_modules = ["app.core.queries"] + +[[tool.importlinter.contracts]] +id = "cqrs-commands-must-not-import-queries" +name = "cqrs: commands must not import queries" +type = "forbidden" +source_modules = ["app.core.commands"] +forbidden_modules = ["app.core.queries"] + +[[tool.importlinter.contracts]] +id = "cqrs-queries-must-not-import-commands" +name = "cqrs: queries must not import commands" +type = "forbidden" +source_modules = ["app.core.queries"] +forbidden_modules = ["app.core.commands"] + +[[tool.importlinter.contracts]] +id = "auth-ctx" +name = "auth-ctx must use its own adapters" +type = "forbidden" +source_modules = ["app.infrastructure.auth_ctx"] +forbidden_modules = ["app.infrastructure.adapters"] + [tool.mypy] mypy_path = ["src"] files = [ - "config", - "scripts", - "src", - "tests", + "scripts", + "src", + "tests", ] exclude = "^.*alembic.*$" python_version = "3.13" @@ -127,8 +161,8 @@ no_implicit_optional = true warn_no_return = true warn_unreachable = true plugins = [ - "pydantic.mypy", - "sqlalchemy.ext.mypy.plugin", + "pydantic.mypy", + "sqlalchemy.ext.mypy.plugin", ] [tool.pydantic-mypy] @@ -136,70 +170,77 @@ warn_required_dynamic_aliases = true warn_untyped_fields = true init_typed = true -[tool.pytest.ini_options] -testpaths = ["tests"] +[tool.pytest] +addopts = ["--color=yes", "-mnot(slow)", "-pno:cacheprovider"] +filterwarnings = [] markers = ["slow"] -addopts = "-m 'not slow'" +testpaths = ["tests"] asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "function" -filterwarnings = [] [tool.ruff] -line-length = 88 +line-length = 120 target-version = "py313" -preview = true # experimental +fix = true +unsafe-fixes = true [tool.ruff.format] +quote-style = "double" +indent-style = "space" skip-magic-trailing-comma = false [tool.ruff.lint] select = [ - "A", # flake8-builtins https://docs.astral.sh/ruff/rules/#flake8-builtins-a - "ARG", # flake8-unused-arguments https://docs.astral.sh/ruff/rules/#flake8-unused-arguments-arg - "ASYNC", # flake8-async https://docs.astral.sh/ruff/rules/#flake8-async-async - "B", # flake8-bugbear https://docs.astral.sh/ruff/rules/#flake8-bugbear-b - "C4", # flake8-comprehensions https://docs.astral.sh/ruff/rules/#flake8-comprehensions-c4 - "C90", # mccabe https://docs.astral.sh/ruff/rules/#mccabe-c90 - # "COM", # flake8-commas https://docs.astral.sh/ruff/rules/#flake8-commas-com - # Incompatible with ruff formatter, but can be useful (uncomment once, then review changes) - # See: https://github.com/astral-sh/ruff/issues/9216 - "DTZ", # flake8-datetimez https://docs.astral.sh/ruff/rules/#flake8-datetimez-dtz - "E", # pycodestyle-error https://docs.astral.sh/ruff/rules/#error-e - "ERA001", # commented-out-code https://docs.astral.sh/ruff/rules/#eradicate-era - "F", # pyflakes https://docs.astral.sh/ruff/rules/#pyflakes-f - "FLY", # flynt https://docs.astral.sh/ruff/rules/#flynt-fly - "I", # isort https://docs.astral.sh/ruff/rules/#isort-i - "LOG", # flake8-logging https://docs.astral.sh/ruff/rules/#flake8-logging-log - "N", # pep8-naming https://docs.astral.sh/ruff/rules/#pep8-naming-n - "PERF", # Perflint https://docs.astral.sh/ruff/rules/#perflint-perf - "PL", # pylint https://docs.astral.sh/ruff/rules/#pylint-pl - "PT", # flake8-pytest-style https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt - "PTH", # flake8-use-pathlib https://docs.astral.sh/ruff/rules/#flake8-use-pathlib-pth - "Q", # flake8-quotes https://docs.astral.sh/ruff/rules/#flake8-quotes-q - "RET", # flake8-return (RET) https://docs.astral.sh/ruff/rules/#flake8-return-ret - "RSE", # flake8-raise (RSE) https://docs.astral.sh/ruff/rules/#flake8-raise-rse - "RUF", # Ruff-specific rules https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf - "S", # flake8-bandit https://docs.astral.sh/ruff/rules/#flake8-bandit-s - "SIM", # flake8-simplify https://docs.astral.sh/ruff/rules/#flake8-simplify-sim - "SLF", # flake8-self (SLF) https://docs.astral.sh/ruff/rules/#flake8-self-slf - "SLOT", # flake8-slots https://docs.astral.sh/ruff/rules/#flake8-slots-slot - "T20", # flake8-print https://docs.astral.sh/ruff/rules/#flake8-print-t20 - "TCH", # flake8-type-checking https://docs.astral.sh/ruff/rules/#flake8-type-checking-tch - "TID", # flake8-tidy-imports https://docs.astral.sh/ruff/rules/#flake8-tidy-imports-tid - "UP", # pyupgrade https://docs.astral.sh/ruff/rules/#pyupgrade-up - "W", # pycodestyle-warning https://docs.astral.sh/ruff/rules/#warning-w + "A", # flake8-builtins + "ASYNC", # flake8-async + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "C90", # mccabe + "DTZ", # flake8-datetimez + "E", # pycodestyle (Error) + "EXE", # flake8-executable + "F", # Pyflakes + "FAST", # FastAPI + "FLY", # flynt + "FURB", # refurb + "G", # flake8-logging-format + "I", # isort + "INT", # flake8-gettext + "ISC", # flake8-implicit-str-concat + "LOG", # flake8-logging + "N", # pep8-naming + "PERF", # Perflint + "PL", # pylint + "PLE", # Pylint (error) + "PLR5501", # Pylint Refactor: collapsible-else-if + "PT", # flake8-pytest-style + "PTH", # flake8-use-pathlib + "Q", # flake8-quotes + "RSE", # flake8-raise (RSE) + "RUF", # Ruff-specific rules + "S", # flake8-bandit + "SIM", # flake8-simplify + "SLF", # flake8-self (SLF) + "SLOT", # flake8-slots + "T10", # flake8-debugger + "T20", # flake8-print + "TC", # flake8-type-checking + "TID", # flake8-tidy-imports + "UP", # pyupgrade + "W", # pycodestyle (Warning) ] ignore = [ - "N818", # error-suffix-on-exception-name - "PLR0913", # too-many-arguments - "PLR0917", # too-many-positional-arguments - "PLR6301", # no-self-use - "PTH123", # builtin-open - "TC001", # typing-only-first-party-import - "TC002", # typing-only-third-party-import - "TC003", # typing-only-standard-library-import - "TC006", # runtime-cast-value - "UP015", # redundant-open-modes + "PLR0913", # too-many-arguments + "PLR0917", # too-many-positional-arguments + "PLR6301", # no-self-use + "RUF001", # ambiguous-unicode-character-string (cyrillic) + "RUF002", # ambiguous-unicode-character-docstring (cyrillic) + "RUF003", # ambiguous-unicode-character-comment (cyrillic) + "S311", # suspicious-non-cryptographic-random-usage + "TC001", # typing-only-first-party-import + "TC002", # typing-only-third-party-import + "TC003", # typing-only-standard-library-import + "TC006", # runtime-cast-value ] [tool.ruff.lint.isort] @@ -208,25 +249,25 @@ force-wrap-aliases = true split-on-trailing-comma = true [tool.ruff.lint.per-file-ignores] +"scripts/dishka/plot_dependencies_data.py" = ["T201"] # print "src/app/infrastructure/persistence_sqla/alembic/**" = ["ALL"] "tests/**" = [ - "ARG002", # unused-method-argument - "PLC2801", # unnecessary-dunder-call - "PLR2004", # magic-value-comparison - "PT011", # pytest-raises-too-broad - "S101", # assert - "S106", # hardcoded-password-func-arg - "S107", # hardcoded-password-default + "ARG002", # unused-method-argument + "PLC2801", # unnecessary-dunder-call + "PLR2004", # magic-value-comparison + "PT011", # pytest-raises-too-broad + "S101", # assert + "S105", # hardcoded-password-string + "S106", # hardcoded-password-func-arg + "S107", # hardcoded-password-default + "SIM300", # yoda-conditions ] -# -"src/app/infrastructure/adapters/password_hasher_bcrypt.py" = ["E501"] # line-too-long -"src/app/infrastructure/auth/handlers/constants.py" = ["S105"] # hardcoded-password-string -"src/app/presentation/http/auth/constants.py" = ["S105"] # hardcoded-password-string -"src/app/presentation/http/errors/translators.py" = ["ARG002"] # unused-method-argument -"scripts/dishka/plot_dependencies_data.py" = ["T201"] # print [tool.slotscheck] strict-imports = true exclude-modules = ''' ^app\.infrastructure\.persistence_sqla\.alembic ''' + +[tool.typos.files] +extend-exclude = [] diff --git a/scripts/dishka/plot_dependencies_data.py b/scripts/dishka/plot_dependencies_data.py index 83d6d3fa..b8f28c17 100644 --- a/scripts/dishka/plot_dependencies_data.py +++ b/scripts/dishka/plot_dependencies_data.py @@ -1,13 +1,18 @@ -import dishka.plotter -import uvloop -from dishka import AsyncContainer, make_async_container - -from app.setup.config.settings import AppSettings, load_settings -from app.setup.ioc.provider_registry import get_providers +import asyncio +import dishka.plotter +from dishka import AsyncContainer -def make_plot_data_container(settings: AppSettings) -> AsyncContainer: - return make_async_container(*get_providers(), context={AppSettings: settings}) +from app.config.loader import ( + load_app_settings, + load_cookie_settings, + load_jwt_settings, + load_password_hasher_settings, + load_postgres_settings, + load_session_settings, + load_sqla_settings, +) +from app.main.run import make_ioc_container def generate_dependency_graph_d2(container: AsyncContainer) -> str: @@ -19,11 +24,18 @@ def generate_dependency_graph_d2(container: AsyncContainer) -> str: async def main() -> None: - settings: AppSettings = load_settings() - async with make_plot_data_container(settings)() as container: + async with make_ioc_container( + app_settings=load_app_settings(), + postgres_settings=load_postgres_settings(), + sqla_settings=load_sqla_settings(), + password_hasher_settings=load_password_hasher_settings(), + jwt_settings=load_jwt_settings(), + session_settings=load_session_settings(), + cookie_settings=load_cookie_settings(), + )() as container: print(generate_dependency_graph_d2(container)) await container.close() if __name__ == "__main__": - uvloop.run(main()) + asyncio.run(main()) diff --git a/scripts/makefile/pycache_del.sh b/scripts/makefile/pycache_del.sh index f98bcda7..cf70c768 100755 --- a/scripts/makefile/pycache_del.sh +++ b/scripts/makefile/pycache_del.sh @@ -1,2 +1,3 @@ #!/bin/bash -find . | grep -E "(/__pycache__$|\.pyc$|\.pyo$)" | xargs rm -rf + find . -type d -name '__pycache__' -prune -exec rm -rf {} +; \ + find . -type f \( -name '*.pyc' -o -name '*.pyo' \) -delete diff --git a/src/app/application/commands/activate_user.py b/src/app/application/commands/activate_user.py deleted file mode 100644 index 7e880b56..00000000 --- a/src/app/application/commands/activate_user.py +++ /dev/null @@ -1,93 +0,0 @@ -import logging -from dataclasses import dataclass -from uuid import UUID - -from app.application.common.ports.transaction_manager import ( - TransactionManager, -) -from app.application.common.ports.user_command_gateway import UserCommandGateway -from app.application.common.services.authorization.authorize import ( - authorize, -) -from app.application.common.services.authorization.permissions import ( - CanManageRole, - CanManageSubordinate, - RoleManagementContext, - UserManagementContext, -) -from app.application.common.services.current_user import CurrentUserService -from app.domain.entities.user import User -from app.domain.enums.user_role import UserRole -from app.domain.exceptions.user import ( - UserNotFoundByIdError, -) -from app.domain.services.user import UserService -from app.domain.value_objects.user_id import UserId - -log = logging.getLogger(__name__) - - -@dataclass(frozen=True, slots=True) -class ActivateUserRequest: - user_id: UUID - - -class ActivateUserInteractor: - """ - - Open to admins. - - Restores a previously soft-deleted user. - - Only super admins can activate other admins. - """ - - def __init__( - self, - current_user_service: CurrentUserService, - user_command_gateway: UserCommandGateway, - user_service: UserService, - transaction_manager: TransactionManager, - ) -> None: - self._current_user_service = current_user_service - self._user_command_gateway = user_command_gateway - self._user_service = user_service - self._transaction_manager = transaction_manager - - async def execute(self, request_data: ActivateUserRequest) -> None: - """ - :raises AuthenticationError: - :raises DataMapperError: - :raises AuthorizationError: - :raises UserNotFoundByIdError: - :raises ActivationChangeNotPermittedError: - """ - log.info("Activate user: started. Target user ID: '%s'.", request_data.user_id) - - current_user = await self._current_user_service.get_current_user() - - authorize( - CanManageRole(), - context=RoleManagementContext( - subject=current_user, - target_role=UserRole.USER, - ), - ) - - user_id = UserId(request_data.user_id) - user: User | None = await self._user_command_gateway.read_by_id( - user_id, - for_update=True, - ) - if user is None: - raise UserNotFoundByIdError(user_id) - - authorize( - CanManageSubordinate(), - context=UserManagementContext( - subject=current_user, - target=user, - ), - ) - - if self._user_service.toggle_user_activation(user, is_active=True): - await self._transaction_manager.commit() - - log.info("Activate user: done. Target user ID: '%s'.", user.id_.value) diff --git a/src/app/application/commands/create_user.py b/src/app/application/commands/create_user.py deleted file mode 100644 index 0d75514b..00000000 --- a/src/app/application/commands/create_user.py +++ /dev/null @@ -1,98 +0,0 @@ -import logging -from dataclasses import dataclass -from typing import TypedDict -from uuid import UUID - -from app.application.common.ports.flusher import Flusher -from app.application.common.ports.transaction_manager import ( - TransactionManager, -) -from app.application.common.ports.user_command_gateway import UserCommandGateway -from app.application.common.services.authorization.authorize import ( - authorize, -) -from app.application.common.services.authorization.permissions import ( - CanManageRole, - RoleManagementContext, -) -from app.application.common.services.current_user import CurrentUserService -from app.domain.enums.user_role import UserRole -from app.domain.exceptions.user import UsernameAlreadyExistsError -from app.domain.services.user import UserService -from app.domain.value_objects.raw_password import RawPassword -from app.domain.value_objects.username import Username - -log = logging.getLogger(__name__) - - -@dataclass(frozen=True, slots=True, kw_only=True) -class CreateUserRequest: - username: str - password: str - role: UserRole - - -class CreateUserResponse(TypedDict): - id: UUID - - -class CreateUserInteractor: - """ - - Open to admins. - - Creates a new user, including admins, if the username is unique. - - Only super admins can create new admins. - """ - - def __init__( - self, - current_user_service: CurrentUserService, - user_service: UserService, - user_command_gateway: UserCommandGateway, - flusher: Flusher, - transaction_manager: TransactionManager, - ) -> None: - self._current_user_service = current_user_service - self._user_service = user_service - self._user_command_gateway = user_command_gateway - self._flusher = flusher - self._transaction_manager = transaction_manager - - async def execute(self, request_data: CreateUserRequest) -> CreateUserResponse: - """ - :raises AuthenticationError: - :raises DataMapperError: - :raises AuthorizationError: - :raises DomainTypeError: - :raises PasswordHasherBusyError: - :raises RoleAssignmentNotPermittedError: - :raises UsernameAlreadyExistsError: - """ - log.info("Create user: started. Target username: '%s'.", request_data.username) - - current_user = await self._current_user_service.get_current_user() - - authorize( - CanManageRole(), - context=RoleManagementContext( - subject=current_user, - target_role=request_data.role, - ), - ) - - username = Username(request_data.username) - password = RawPassword(request_data.password) - user = await self._user_service.create_user( - username, password, request_data.role - ) - - self._user_command_gateway.add(user) - - try: - await self._flusher.flush() - except UsernameAlreadyExistsError: - raise - - await self._transaction_manager.commit() - - log.info("Create user: done. Target username: '%s'.", user.username.value) - return CreateUserResponse(id=user.id_.value) diff --git a/src/app/application/commands/deactivate_user.py b/src/app/application/commands/deactivate_user.py deleted file mode 100644 index 791ad75f..00000000 --- a/src/app/application/commands/deactivate_user.py +++ /dev/null @@ -1,103 +0,0 @@ -import logging -from dataclasses import dataclass -from uuid import UUID - -from app.application.common.ports.access_revoker import AccessRevoker -from app.application.common.ports.transaction_manager import ( - TransactionManager, -) -from app.application.common.ports.user_command_gateway import UserCommandGateway -from app.application.common.services.authorization.authorize import ( - authorize, -) -from app.application.common.services.authorization.permissions import ( - CanManageRole, - CanManageSubordinate, - RoleManagementContext, - UserManagementContext, -) -from app.application.common.services.current_user import CurrentUserService -from app.domain.entities.user import User -from app.domain.enums.user_role import UserRole -from app.domain.exceptions.user import ( - UserNotFoundByIdError, -) -from app.domain.services.user import UserService -from app.domain.value_objects.user_id import UserId - -log = logging.getLogger(__name__) - - -@dataclass(frozen=True, slots=True) -class DeactivateUserRequest: - user_id: UUID - - -class DeactivateUserInteractor: - """ - - Open to admins. - - Soft-deletes an existing user, making that user inactive. - - Also deletes the user's sessions. - - Only super admins can deactivate other admins. - - Super admins cannot be soft-deleted. - """ - - def __init__( - self, - current_user_service: CurrentUserService, - user_command_gateway: UserCommandGateway, - user_service: UserService, - transaction_manager: TransactionManager, - access_revoker: AccessRevoker, - ) -> None: - self._current_user_service = current_user_service - self._user_command_gateway = user_command_gateway - self._user_service = user_service - self._transaction_manager = transaction_manager - self._access_revoker = access_revoker - - async def execute(self, request_data: DeactivateUserRequest) -> None: - """ - :raises AuthenticationError: - :raises DataMapperError: - :raises AuthorizationError: - :raises UserNotFoundByIdError: - :raises ActivationChangeNotPermittedError: - """ - log.info( - "Deactivate user: started. Target user ID: '%s'.", - request_data.user_id, - ) - - current_user = await self._current_user_service.get_current_user() - - authorize( - CanManageRole(), - context=RoleManagementContext( - subject=current_user, - target_role=UserRole.USER, - ), - ) - - user_id = UserId(request_data.user_id) - user: User | None = await self._user_command_gateway.read_by_id( - user_id, - for_update=True, - ) - if user is None: - raise UserNotFoundByIdError(user_id) - - authorize( - CanManageSubordinate(), - context=UserManagementContext( - subject=current_user, - target=user, - ), - ) - - if self._user_service.toggle_user_activation(user, is_active=False): - await self._transaction_manager.commit() - - await self._access_revoker.remove_all_user_access(user.id_) - - log.info("Deactivate user: done. Target user ID: '%s'.", user.id_.value) diff --git a/src/app/application/commands/grant_admin.py b/src/app/application/commands/grant_admin.py deleted file mode 100644 index 645ee55d..00000000 --- a/src/app/application/commands/grant_admin.py +++ /dev/null @@ -1,83 +0,0 @@ -import logging -from dataclasses import dataclass -from uuid import UUID - -from app.application.common.ports.transaction_manager import ( - TransactionManager, -) -from app.application.common.ports.user_command_gateway import UserCommandGateway -from app.application.common.services.authorization.authorize import ( - authorize, -) -from app.application.common.services.authorization.permissions import ( - CanManageRole, - RoleManagementContext, -) -from app.application.common.services.current_user import CurrentUserService -from app.domain.entities.user import User -from app.domain.enums.user_role import UserRole -from app.domain.exceptions.user import ( - UserNotFoundByIdError, -) -from app.domain.services.user import UserService -from app.domain.value_objects.user_id import UserId - -log = logging.getLogger(__name__) - - -@dataclass(frozen=True, slots=True) -class GrantAdminRequest: - user_id: UUID - - -class GrantAdminInteractor: - """ - - Open to super admins. - - Grants admin rights to a specified user. - - Super admin rights cannot be changed. - """ - - def __init__( - self, - current_user_service: CurrentUserService, - user_command_gateway: UserCommandGateway, - user_service: UserService, - transaction_manager: TransactionManager, - ) -> None: - self._current_user_service = current_user_service - self._user_command_gateway = user_command_gateway - self._user_service = user_service - self._transaction_manager = transaction_manager - - async def execute(self, request_data: GrantAdminRequest) -> None: - """ - :raises AuthenticationError: - :raises DataMapperError: - :raises AuthorizationError: - :raises UserNotFoundByIdError: - :raises RoleChangeNotPermittedError: - """ - log.info("Grant admin: started. Target user ID: '%s'.", request_data.user_id) - - current_user = await self._current_user_service.get_current_user() - - authorize( - CanManageRole(), - context=RoleManagementContext( - subject=current_user, - target_role=UserRole.ADMIN, - ), - ) - - user_id = UserId(request_data.user_id) - user: User | None = await self._user_command_gateway.read_by_id( - user_id, - for_update=True, - ) - if user is None: - raise UserNotFoundByIdError(user_id) - - if self._user_service.toggle_user_admin_role(user, is_admin=True): - await self._transaction_manager.commit() - - log.info("Grant admin: done. Target user ID: '%s'.", user.id_.value) diff --git a/src/app/application/commands/revoke_admin.py b/src/app/application/commands/revoke_admin.py deleted file mode 100644 index eb1670e2..00000000 --- a/src/app/application/commands/revoke_admin.py +++ /dev/null @@ -1,81 +0,0 @@ -import logging -from dataclasses import dataclass -from uuid import UUID - -from app.application.common.ports.transaction_manager import ( - TransactionManager, -) -from app.application.common.ports.user_command_gateway import UserCommandGateway -from app.application.common.services.authorization.authorize import authorize -from app.application.common.services.authorization.permissions import ( - CanManageRole, - RoleManagementContext, -) -from app.application.common.services.current_user import CurrentUserService -from app.domain.entities.user import User -from app.domain.enums.user_role import UserRole -from app.domain.exceptions.user import ( - UserNotFoundByIdError, -) -from app.domain.services.user import UserService -from app.domain.value_objects.user_id import UserId - -log = logging.getLogger(__name__) - - -@dataclass(frozen=True, slots=True) -class RevokeAdminRequest: - user_id: UUID - - -class RevokeAdminInteractor: - """ - - Open to super admins. - - Revokes admin rights from a specified user. - - Super admin rights cannot be changed - """ - - def __init__( - self, - current_user_service: CurrentUserService, - user_command_gateway: UserCommandGateway, - user_service: UserService, - transaction_manager: TransactionManager, - ) -> None: - self._current_user_service = current_user_service - self._user_command_gateway = user_command_gateway - self._user_service = user_service - self._transaction_manager = transaction_manager - - async def execute(self, request_data: RevokeAdminRequest) -> None: - """ - :raises AuthenticationError: - :raises DataMapperError: - :raises AuthorizationError: - :raises UserNotFoundByIdError: - :raises RoleChangeNotPermittedError: - """ - log.info("Revoke admin: started. Target user ID: '%s'.", request_data.user_id) - - current_user = await self._current_user_service.get_current_user() - - authorize( - CanManageRole(), - context=RoleManagementContext( - subject=current_user, - target_role=UserRole.ADMIN, - ), - ) - - user_id = UserId(request_data.user_id) - user: User | None = await self._user_command_gateway.read_by_id( - user_id, - for_update=True, - ) - if user is None: - raise UserNotFoundByIdError(user_id) - - if self._user_service.toggle_user_admin_role(user, is_admin=False): - await self._transaction_manager.commit() - - log.info("Revoke admin: done. Target user ID: '%s'.", user.id_.value) diff --git a/src/app/application/commands/set_user_password.py b/src/app/application/commands/set_user_password.py deleted file mode 100644 index 78c96399..00000000 --- a/src/app/application/commands/set_user_password.py +++ /dev/null @@ -1,99 +0,0 @@ -import logging -from dataclasses import dataclass -from uuid import UUID - -from app.application.common.ports.transaction_manager import ( - TransactionManager, -) -from app.application.common.ports.user_command_gateway import UserCommandGateway -from app.application.common.services.authorization.authorize import ( - authorize, -) -from app.application.common.services.authorization.permissions import ( - CanManageRole, - CanManageSubordinate, - RoleManagementContext, - UserManagementContext, -) -from app.application.common.services.current_user import CurrentUserService -from app.domain.entities.user import User -from app.domain.enums.user_role import UserRole -from app.domain.exceptions.user import ( - UserNotFoundByIdError, -) -from app.domain.services.user import UserService -from app.domain.value_objects.raw_password import RawPassword -from app.domain.value_objects.user_id import UserId - -log = logging.getLogger(__name__) - - -@dataclass(frozen=True, slots=True, kw_only=True) -class SetUserPasswordRequest: - user_id: UUID - password: str - - -class SetUserPasswordInteractor: - """ - - Open to admins. - - Admins can set passwords of subordinate users. - """ - - def __init__( - self, - current_user_service: CurrentUserService, - user_command_gateway: UserCommandGateway, - user_service: UserService, - transaction_manager: TransactionManager, - ) -> None: - self._current_user_service = current_user_service - self._user_command_gateway = user_command_gateway - self._user_service = user_service - self._transaction_manager = transaction_manager - - async def execute(self, request_data: SetUserPasswordRequest) -> None: - """ - :raises AuthenticationError: - :raises DataMapperError: - :raises AuthorizationError: - :raises DomainTypeError: - :raises UserNotFoundByIdError: - :raises PasswordHasherBusyError: - """ - log.info( - "Set user password: started. Target user ID: '%s'.", - request_data.user_id, - ) - - current_user = await self._current_user_service.get_current_user() - - authorize( - CanManageRole(), - context=RoleManagementContext( - subject=current_user, - target_role=UserRole.USER, - ), - ) - - user_id = UserId(request_data.user_id) - password = RawPassword(request_data.password) - user: User | None = await self._user_command_gateway.read_by_id( - user_id, - for_update=True, - ) - if user is None: - raise UserNotFoundByIdError(user_id) - - authorize( - CanManageSubordinate(), - context=UserManagementContext( - subject=current_user, - target=user, - ), - ) - - await self._user_service.change_password(user, password) - await self._transaction_manager.commit() - - log.info("Set user password: done. Target user ID: '%s'.", user.id_.value) diff --git a/src/app/application/common/exceptions/authorization.py b/src/app/application/common/exceptions/authorization.py deleted file mode 100644 index 2a0a2a0f..00000000 --- a/src/app/application/common/exceptions/authorization.py +++ /dev/null @@ -1,5 +0,0 @@ -from app.application.common.exceptions.base import ApplicationError - - -class AuthorizationError(ApplicationError): - pass diff --git a/src/app/application/common/exceptions/base.py b/src/app/application/common/exceptions/base.py deleted file mode 100644 index 5f0f4986..00000000 --- a/src/app/application/common/exceptions/base.py +++ /dev/null @@ -1,2 +0,0 @@ -class ApplicationError(Exception): - pass diff --git a/src/app/application/common/exceptions/query.py b/src/app/application/common/exceptions/query.py deleted file mode 100644 index 6449dfd7..00000000 --- a/src/app/application/common/exceptions/query.py +++ /dev/null @@ -1,9 +0,0 @@ -from app.application.common.exceptions.base import ApplicationError - - -class PaginationError(ApplicationError): - pass - - -class SortingError(ApplicationError): - pass diff --git a/src/app/application/common/ports/flusher.py b/src/app/application/common/ports/flusher.py deleted file mode 100644 index 6f25c27b..00000000 --- a/src/app/application/common/ports/flusher.py +++ /dev/null @@ -1,17 +0,0 @@ -from abc import abstractmethod -from typing import Protocol - - -class Flusher(Protocol): - """ - Interface for flushing intermediate changes during a business transaction. - """ - - @abstractmethod - async def flush(self) -> None: - """ - :raises DataMapperError: - :raises UsernameAlreadyExists: - - Flush pending changes to validate constraints or trigger side effects. - """ diff --git a/src/app/application/common/ports/identity_provider.py b/src/app/application/common/ports/identity_provider.py deleted file mode 100644 index b0a7b9f5..00000000 --- a/src/app/application/common/ports/identity_provider.py +++ /dev/null @@ -1,10 +0,0 @@ -from abc import abstractmethod -from typing import Protocol - -from app.domain.value_objects.user_id import UserId - - -class IdentityProvider(Protocol): - @abstractmethod - async def get_current_user_id(self) -> UserId: - """:raises AuthenticationError:""" diff --git a/src/app/application/common/ports/user_command_gateway.py b/src/app/application/common/ports/user_command_gateway.py deleted file mode 100644 index 7593322b..00000000 --- a/src/app/application/common/ports/user_command_gateway.py +++ /dev/null @@ -1,28 +0,0 @@ -from abc import abstractmethod -from typing import Protocol - -from app.domain.entities.user import User -from app.domain.value_objects.user_id import UserId -from app.domain.value_objects.username import Username - - -class UserCommandGateway(Protocol): - @abstractmethod - def add(self, user: User) -> None: - """:raises DataMapperError:""" - - @abstractmethod - async def read_by_id( - self, - user_id: UserId, - for_update: bool = False, - ) -> User | None: - """:raises DataMapperError:""" - - @abstractmethod - async def read_by_username( - self, - username: Username, - for_update: bool = False, - ) -> User | None: - """:raises DataMapperError:""" diff --git a/src/app/application/common/ports/user_query_gateway.py b/src/app/application/common/ports/user_query_gateway.py deleted file mode 100644 index 0ca0985c..00000000 --- a/src/app/application/common/ports/user_query_gateway.py +++ /dev/null @@ -1,32 +0,0 @@ -from abc import abstractmethod -from typing import Protocol, TypedDict -from uuid import UUID - -from app.application.common.query_params.offset_pagination import OffsetPaginationParams -from app.application.common.query_params.sorting import SortingParams -from app.domain.enums.user_role import UserRole - - -class UserQueryModel(TypedDict): - id_: UUID - username: str - role: UserRole - is_active: bool - - -class ListUsersQM(TypedDict): - users: list[UserQueryModel] - total: int - - -class UserQueryGateway(Protocol): - @abstractmethod - async def read_all( - self, - pagination: OffsetPaginationParams, - sorting: SortingParams, - ) -> ListUsersQM: - """ - :raises SortingError: - :raises ReaderError: - """ diff --git a/src/app/application/common/query_params/offset_pagination.py b/src/app/application/common/query_params/offset_pagination.py deleted file mode 100644 index b0335bbc..00000000 --- a/src/app/application/common/query_params/offset_pagination.py +++ /dev/null @@ -1,20 +0,0 @@ -from dataclasses import dataclass - -from app.application.common.exceptions.query import PaginationError - - -@dataclass(frozen=True, slots=True, kw_only=True) -class OffsetPaginationParams: - """ - raises PaginationError - """ - - limit: int - offset: int - - def __post_init__(self) -> None: - """:raises PaginationError:""" - if self.limit <= 0: - raise PaginationError(f"Limit must be greater than 0, got {self.limit}") - if self.offset < 0: - raise PaginationError(f"Offset must be non-negative, got {self.offset}") diff --git a/src/app/application/common/services/authorization/authorize.py b/src/app/application/common/services/authorization/authorize.py deleted file mode 100644 index 5305c716..00000000 --- a/src/app/application/common/services/authorization/authorize.py +++ /dev/null @@ -1,16 +0,0 @@ -from app.application.common.exceptions.authorization import AuthorizationError -from app.application.common.services.authorization.base import ( - Permission, - PermissionContext, -) -from app.application.common.services.constants import AUTHZ_NOT_AUTHORIZED - - -def authorize[PC: PermissionContext]( - permission: Permission[PC], - *, - context: PC, -) -> None: - """:raises AuthorizationError:""" - if not permission.is_satisfied_by(context): - raise AuthorizationError(AUTHZ_NOT_AUTHORIZED) diff --git a/src/app/application/common/services/constants.py b/src/app/application/common/services/constants.py deleted file mode 100644 index 7cb6a652..00000000 --- a/src/app/application/common/services/constants.py +++ /dev/null @@ -1,6 +0,0 @@ -from typing import Final - -AUTHZ_NOT_AUTHORIZED: Final[str] = "Not authorized." -AUTHZ_NO_CURRENT_USER: Final[str] = ( - "Failed to retrieve current user. Removing all access." -) diff --git a/src/app/application/common/services/current_user.py b/src/app/application/common/services/current_user.py deleted file mode 100644 index 28517c30..00000000 --- a/src/app/application/common/services/current_user.py +++ /dev/null @@ -1,43 +0,0 @@ -import logging - -from app.application.common.exceptions.authorization import AuthorizationError -from app.application.common.ports.access_revoker import AccessRevoker -from app.application.common.ports.identity_provider import IdentityProvider -from app.application.common.ports.user_command_gateway import UserCommandGateway -from app.application.common.services.constants import ( - AUTHZ_NO_CURRENT_USER, - AUTHZ_NOT_AUTHORIZED, -) -from app.domain.entities.user import User - -log = logging.getLogger(__name__) - - -class CurrentUserService: - def __init__( - self, - identity_provider: IdentityProvider, - user_command_gateway: UserCommandGateway, - access_revoker: AccessRevoker, - ) -> None: - self._identity_provider = identity_provider - self._user_command_gateway = user_command_gateway - self._access_revoker = access_revoker - - async def get_current_user(self, for_update: bool = False) -> User: - """ - :raises AuthenticationError: - :raises DataMapperError: - :raises AuthorizationError: - """ - current_user_id = await self._identity_provider.get_current_user_id() - user: User | None = await self._user_command_gateway.read_by_id( - current_user_id, - for_update=for_update, - ) - if user is None or not user.is_active: - log.warning("%s ID: %s.", AUTHZ_NO_CURRENT_USER, current_user_id) - await self._access_revoker.remove_all_user_access(current_user_id) - raise AuthorizationError(AUTHZ_NOT_AUTHORIZED) - - return user diff --git a/src/app/application/queries/list_users.py b/src/app/application/queries/list_users.py deleted file mode 100644 index 35aa4c14..00000000 --- a/src/app/application/queries/list_users.py +++ /dev/null @@ -1,81 +0,0 @@ -import logging -from dataclasses import dataclass - -from app.application.common.ports.user_query_gateway import ( - ListUsersQM, - UserQueryGateway, -) -from app.application.common.query_params.offset_pagination import OffsetPaginationParams -from app.application.common.query_params.sorting import SortingOrder, SortingParams -from app.application.common.services.authorization.authorize import ( - authorize, -) -from app.application.common.services.authorization.permissions import ( - CanManageRole, - RoleManagementContext, -) -from app.application.common.services.current_user import CurrentUserService -from app.domain.enums.user_role import UserRole - -log = logging.getLogger(__name__) - - -@dataclass(frozen=True, slots=True, kw_only=True) -class ListUsersRequest: - limit: int - offset: int - sorting_field: str - sorting_order: SortingOrder - - -class ListUsersQueryService: - """ - - Open to admins. - - Retrieves a paginated list of existing users with relevant information. - """ - - def __init__( - self, - current_user_service: CurrentUserService, - user_query_gateway: UserQueryGateway, - ) -> None: - self._current_user_service = current_user_service - self._user_query_gateway = user_query_gateway - - async def execute(self, request_data: ListUsersRequest) -> ListUsersQM: - """ - :raises AuthenticationError: - :raises DataMapperError: - :raises AuthorizationError: - :raises PaginationError: - :raises SortingError: - :raises ReaderError: - """ - log.info("List users: started.") - - current_user = await self._current_user_service.get_current_user() - - authorize( - CanManageRole(), - context=RoleManagementContext( - subject=current_user, - target_role=UserRole.USER, - ), - ) - - log.debug("Retrieving list of users.") - pagination = OffsetPaginationParams( - limit=request_data.limit, - offset=request_data.offset, - ) - sorting = SortingParams( - field=request_data.sorting_field, - order=request_data.sorting_order, - ) - response = await self._user_query_gateway.read_all( - pagination=pagination, - sorting=sorting, - ) - - log.info("List users: done.") - return response diff --git a/config/__init__.py b/src/app/config/__init__.py similarity index 100% rename from config/__init__.py rename to src/app/config/__init__.py diff --git a/src/app/config/loader.py b/src/app/config/loader.py new file mode 100644 index 00000000..30e69a68 --- /dev/null +++ b/src/app/config/loader.py @@ -0,0 +1,82 @@ +from pathlib import Path +from typing import Final + +from pydantic_settings import BaseSettings, SettingsConfigDict + +from app.config.settings import ( + AppSettings, + CookieSettings, + JwtSettings, + PasswordHasherSettings, + PostgresSettings, + SessionSettings, + SqlaSettings, +) + +BASE_DIR: Final[Path] = Path(__file__).resolve().parents[3] +_ENV_FILE: Final[Path] = BASE_DIR.joinpath(".env") +_DEFAULT_CONFIG_DICT: Final[SettingsConfigDict] = SettingsConfigDict( + env_file=_ENV_FILE, + env_file_encoding="utf-8", + extra="ignore", +) + + +def _load_settings[E: BaseSettings](env_cls: type[E]) -> E: + return env_cls() + + +class AppEnvConfig(BaseSettings, AppSettings): + model_config = _DEFAULT_CONFIG_DICT | SettingsConfigDict(env_prefix="APP_") + + +class PostgresEnvConfig(BaseSettings, PostgresSettings): + model_config = _DEFAULT_CONFIG_DICT | SettingsConfigDict(env_prefix="POSTGRES_") + + +class SqlaEnvConfig(BaseSettings, SqlaSettings): + model_config = _DEFAULT_CONFIG_DICT | SettingsConfigDict(env_prefix="SQLA_") + + +class PasswordHasherEnvConfig(BaseSettings, PasswordHasherSettings): + model_config = _DEFAULT_CONFIG_DICT | SettingsConfigDict(env_prefix="PASSWORD_") + + +class JwtEnvConfig(BaseSettings, JwtSettings): + model_config = _DEFAULT_CONFIG_DICT | SettingsConfigDict(env_prefix="JWT_") + + +class SessionEnvConfig(BaseSettings, SessionSettings): + model_config = _DEFAULT_CONFIG_DICT | SettingsConfigDict(env_prefix="SESSION_") + + +class CookieEnvConfig(BaseSettings, CookieSettings): + model_config = _DEFAULT_CONFIG_DICT | SettingsConfigDict(env_prefix="COOKIE_") + + +def load_app_settings() -> AppSettings: + return _load_settings(AppEnvConfig) + + +def load_postgres_settings() -> PostgresSettings: + return _load_settings(PostgresEnvConfig) + + +def load_sqla_settings() -> SqlaSettings: + return _load_settings(SqlaEnvConfig) + + +def load_password_hasher_settings() -> PasswordHasherSettings: + return _load_settings(PasswordHasherEnvConfig) + + +def load_jwt_settings() -> JwtSettings: + return _load_settings(JwtEnvConfig) + + +def load_session_settings() -> SessionSettings: + return _load_settings(SessionEnvConfig) + + +def load_cookie_settings() -> CookieSettings: + return _load_settings(CookieEnvConfig) diff --git a/src/app/config/logging_.py b/src/app/config/logging_.py new file mode 100644 index 00000000..4fa6d96a --- /dev/null +++ b/src/app/config/logging_.py @@ -0,0 +1,20 @@ +from enum import StrEnum +from typing import Final + +# fmt: off +FMT: Final[str] = ( + "[%(asctime)s.%(msecs)03d] [%(threadName)s] " + "%(funcName)20s " + "%(module)s:%(lineno)d " + "%(levelname)-8s - %(message)s" +) +# fmt: on +DATEFMT: Final[str] = "%Y-%m-%d %H:%M:%S" + + +class LoggingLevel(StrEnum): + DEBUG = "DEBUG" + INFO = "INFO" + WARNING = "WARNING" + ERROR = "ERROR" + CRITICAL = "CRITICAL" diff --git a/src/app/config/settings.py b/src/app/config/settings.py new file mode 100644 index 00000000..def10f83 --- /dev/null +++ b/src/app/config/settings.py @@ -0,0 +1,79 @@ +from datetime import timedelta +from typing import Literal + +from pydantic import BaseModel, Field, PostgresDsn + +from app.config.logging_ import LoggingLevel +from app.infrastructure.auth_ctx.jwt_types import JwtAlgorithm + + +class AppSettings(BaseModel): + SERVICE_NAME: str = "clean-example" + VERSION: str = "development" + ROOT_PATH: str = "/" + DEBUG_MODE: bool = False + LOGGING_LEVEL: LoggingLevel = LoggingLevel.INFO + + +class PostgresSettings(BaseModel): + DB: str + HOST: str + PORT: int + USER: str + PASSWORD: str + + @property + def dsn(self) -> str: + return str( + PostgresDsn.build( + scheme="postgresql+psycopg", + username=self.USER, + password=self.PASSWORD, + host=self.HOST, + port=self.PORT, + path=self.DB, + ), + ) + + +class SqlaSettings(BaseModel): + ECHO: bool = False + ECHO_POOL: bool = False + POOL_SIZE: int = 15 + MAX_OVERFLOW: int = 0 + + +class PasswordHasherSettings(BaseModel): + # https://www.ietf.org/archive/id/draft-ietf-kitten-password-storage-04.html#section-4.2 + PEPPER: str = Field(min_length=32) + + # https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#introduction + WORK_FACTOR: int = 11 + # CPU-bound & GIL released: per-worker β‰ˆ max(1, floor(effective vCPUs / workers)) + MAX_THREADS: int = 8 + # Fail-fast cap: max semaphore wait before timeout (start ~1 second, tune to peak) + SEMAPHORE_WAIT_TIMEOUT_S: float = 1.0 + + +class JwtSettings(BaseModel): + # Min length 32 for 256-bit: https://www.rfc-editor.org/rfc/rfc7518#section-3.2 + SECRET: str = Field(min_length=32) + + ALGORITHM: JwtAlgorithm = "HS256" + + +class SessionSettings(BaseModel): + TTL_MIN: int = Field(ge=1, default=5) + REFRESH_THRESHOLD_RATIO: float = Field(gt=0, lt=1, default=0.2) + + @property + def ttl(self) -> timedelta: + return timedelta(minutes=self.TTL_MIN) + + +class CookieSettings(BaseModel): + NAME: str = "auth_token" + PATH: str = "/" + HTTPONLY: bool = True + SECURE: bool = False + SAMESITE: Literal["lax", "strict", "none"] = "lax" diff --git a/src/app/application/__init__.py b/src/app/core/__init__.py similarity index 100% rename from src/app/application/__init__.py rename to src/app/core/__init__.py diff --git a/src/app/application/commands/__init__.py b/src/app/core/commands/__init__.py similarity index 100% rename from src/app/application/commands/__init__.py rename to src/app/core/commands/__init__.py diff --git a/src/app/core/commands/activate_user.py b/src/app/core/commands/activate_user.py new file mode 100644 index 00000000..62a1df18 --- /dev/null +++ b/src/app/core/commands/activate_user.py @@ -0,0 +1,82 @@ +import logging +from dataclasses import dataclass +from uuid import UUID + +from app.core.commands.exceptions import UserNotFoundError +from app.core.commands.ports.transaction_manager import TransactionManager +from app.core.commands.ports.user_tx_storage import UserTxStorage +from app.core.commands.ports.utc_timer import UtcTimer +from app.core.common.authorization.authorize import authorize +from app.core.common.authorization.current_user_service import CurrentUserService +from app.core.common.authorization.permissions import ( + CanManageRole, + CanManageSubordinate, + RoleManagementContext, + UserManagementContext, +) +from app.core.common.entities.types_ import UserId, UserRole +from app.core.common.services.user import UserService + +logger = logging.getLogger(__name__) + + +@dataclass(frozen=True, slots=True) +class ActivateUserRequest: + user_id: UUID + + +class ActivateUser: + """ + - Open to admins. + - Restores previously soft-deleted user. + - Only super admins can activate other admins. + """ + + def __init__( + self, + current_user_service: CurrentUserService, + user_tx_storage: UserTxStorage, + user_service: UserService, + utc_timer: UtcTimer, + transaction_manager: TransactionManager, + ) -> None: + self._current_user_service = current_user_service + self._user_tx_storage = user_tx_storage + self._user_service = user_service + self._utc_timer = utc_timer + self._transaction_manager = transaction_manager + + async def execute(self, request: ActivateUserRequest) -> None: + logger.info("Activate user: started.") + + current_user = await self._current_user_service.get_current_user() + authorize( + CanManageRole(), + context=RoleManagementContext( + subject=current_user, + target_role=UserRole.USER, + ), + ) + user_id = UserId(request.user_id) + user = await self._user_tx_storage.get_by_id( + user_id, + for_update=True, + ) + if user is None: + raise UserNotFoundError + + authorize( + CanManageSubordinate(), + context=UserManagementContext( + subject=current_user, + target=user, + ), + ) + if self._user_service.set_activation( + user, + now=self._utc_timer.now, + is_active=True, + ): + await self._transaction_manager.commit() + + logger.info("Activate user: done.") diff --git a/src/app/core/commands/create_user.py b/src/app/core/commands/create_user.py new file mode 100644 index 00000000..ccffd9de --- /dev/null +++ b/src/app/core/commands/create_user.py @@ -0,0 +1,98 @@ +import logging +from dataclasses import dataclass +from datetime import datetime +from enum import StrEnum +from typing import TypedDict +from uuid import UUID + +from app.core.commands.exceptions import UsernameAlreadyExistsError +from app.core.commands.ports.flusher import Flusher +from app.core.commands.ports.transaction_manager import TransactionManager +from app.core.commands.ports.user_tx_storage import UserTxStorage +from app.core.commands.ports.utc_timer import UtcTimer +from app.core.common.authorization.authorize import authorize +from app.core.common.authorization.current_user_service import CurrentUserService +from app.core.common.authorization.permissions import CanManageRole, RoleManagementContext +from app.core.common.entities.types_ import UserRole +from app.core.common.factories.id_factory import create_user_id +from app.core.common.services.user import UserService +from app.core.common.value_objects.raw_password import RawPassword +from app.core.common.value_objects.username import Username + +logger = logging.getLogger(__name__) + + +class UserRoleRequestEnum(StrEnum): + USER = "user" + ADMIN = "admin" + + +@dataclass(frozen=True, slots=True, kw_only=True) +class CreateUserRequest: + username: str + password: str + role: UserRoleRequestEnum + + +class CreateUserResponse(TypedDict): + id: UUID + created_at: datetime + + +class CreateUser: + """ + - Open to admins. + - Creates new user, including admins, if the username is unique. + - Only super admins can create new admins. + """ + + def __init__( + self, + current_user_service: CurrentUserService, + user_service: UserService, + utc_timer: UtcTimer, + user_tx_storage: UserTxStorage, + flusher: Flusher, + transaction_manager: TransactionManager, + ) -> None: + self._current_user_service = current_user_service + self._user_service = user_service + self._utc_timer = utc_timer + self._user_tx_storage = user_tx_storage + self._flusher = flusher + self._transaction_manager = transaction_manager + + async def execute(self, request: CreateUserRequest) -> CreateUserResponse: + logger.info("Create user: started.") + + current_user = await self._current_user_service.get_current_user() + role = UserRole(request.role) + authorize( + CanManageRole(), + context=RoleManagementContext( + subject=current_user, + target_role=role, + ), + ) + username = Username(request.username) + password = RawPassword(request.password) + user = await self._user_service.create_user_with_raw_password( + user_id=create_user_id(), + username=username, + raw_password=password, + now=self._utc_timer.now, + role=role, + ) + self._user_tx_storage.add(user) + try: + await self._flusher.flush() + except UsernameAlreadyExistsError: + raise + + await self._transaction_manager.commit() + + logger.info("Create user: done.") + return CreateUserResponse( + id=user.id_, + created_at=user.created_at.value, + ) diff --git a/src/app/core/commands/deactivate_user.py b/src/app/core/commands/deactivate_user.py new file mode 100644 index 00000000..3d546157 --- /dev/null +++ b/src/app/core/commands/deactivate_user.py @@ -0,0 +1,89 @@ +import logging +from dataclasses import dataclass +from uuid import UUID + +from app.core.commands.exceptions import UserNotFoundError +from app.core.commands.ports.transaction_manager import TransactionManager +from app.core.commands.ports.user_tx_storage import UserTxStorage +from app.core.commands.ports.utc_timer import UtcTimer +from app.core.common.authorization.authorize import authorize +from app.core.common.authorization.current_user_service import CurrentUserService +from app.core.common.authorization.permissions import ( + CanManageRole, + CanManageSubordinate, + RoleManagementContext, + UserManagementContext, +) +from app.core.common.entities.types_ import UserId, UserRole +from app.core.common.ports.access_revoker import AccessRevoker +from app.core.common.services.user import UserService + +logger = logging.getLogger(__name__) + + +@dataclass(frozen=True, slots=True) +class DeactivateUserRequest: + user_id: UUID + + +class DeactivateUser: + """ + - Open to admins. + - Soft-deletes existing user, making that user inactive. + - Also deletes user's sessions. + - Only super admins can deactivate other admins. + - Super admins cannot be soft-deleted. + """ + + def __init__( + self, + current_user_service: CurrentUserService, + user_tx_storage: UserTxStorage, + user_service: UserService, + utc_timer: UtcTimer, + transaction_manager: TransactionManager, + access_revoker: AccessRevoker, + ) -> None: + self._current_user_service = current_user_service + self._user_tx_storage = user_tx_storage + self._user_service = user_service + self._utc_timer = utc_timer + self._transaction_manager = transaction_manager + self._access_revoker = access_revoker + + async def execute(self, request: DeactivateUserRequest) -> None: + logger.info("Deactivate user: started.") + + current_user = await self._current_user_service.get_current_user() + authorize( + CanManageRole(), + context=RoleManagementContext( + subject=current_user, + target_role=UserRole.USER, + ), + ) + user_id = UserId(request.user_id) + user = await self._user_tx_storage.get_by_id( + user_id, + for_update=True, + ) + if user is None: + raise UserNotFoundError + + authorize( + CanManageSubordinate(), + context=UserManagementContext( + subject=current_user, + target=user, + ), + ) + if self._user_service.set_activation( + user, + now=self._utc_timer.now, + is_active=False, + ): + await self._transaction_manager.commit() + + await self._access_revoker.remove_all_user_access(user.id_) + + logger.info("Deactivate user: done.") diff --git a/src/app/core/commands/exceptions.py b/src/app/core/commands/exceptions.py new file mode 100644 index 00000000..aa979fb2 --- /dev/null +++ b/src/app/core/commands/exceptions.py @@ -0,0 +1,11 @@ +from typing import ClassVar + +from app.core.common.exceptions import BaseError + + +class UsernameAlreadyExistsError(BaseError): + default_message: ClassVar[str] = "Username already exists." + + +class UserNotFoundError(BaseError): + default_message: ClassVar[str] = "User not found." diff --git a/src/app/core/commands/grant_admin.py b/src/app/core/commands/grant_admin.py new file mode 100644 index 00000000..f1d2e3d8 --- /dev/null +++ b/src/app/core/commands/grant_admin.py @@ -0,0 +1,82 @@ +import logging +from dataclasses import dataclass +from uuid import UUID + +from app.core.commands.exceptions import UserNotFoundError +from app.core.commands.ports.transaction_manager import TransactionManager +from app.core.commands.ports.user_tx_storage import UserTxStorage +from app.core.commands.ports.utc_timer import UtcTimer +from app.core.common.authorization.authorize import authorize +from app.core.common.authorization.current_user_service import CurrentUserService +from app.core.common.authorization.permissions import ( + CanManageRole, + CanManageSubordinate, + RoleManagementContext, + UserManagementContext, +) +from app.core.common.entities.types_ import UserId, UserRole +from app.core.common.services.user import UserService + +logger = logging.getLogger(__name__) + + +@dataclass(frozen=True, slots=True) +class GrantAdminRequest: + user_id: UUID + + +class GrantAdmin: + """ + - Open to super admins. + - Grants admin rights to specified user. + - Super admin rights cannot be changed. + """ + + def __init__( + self, + current_user_service: CurrentUserService, + user_tx_storage: UserTxStorage, + user_service: UserService, + utc_timer: UtcTimer, + transaction_manager: TransactionManager, + ) -> None: + self._current_user_service = current_user_service + self._user_tx_storage = user_tx_storage + self._user_service = user_service + self._utc_timer = utc_timer + self._transaction_manager = transaction_manager + + async def execute(self, request: GrantAdminRequest) -> None: + logger.info("Grant admin: started.") + + current_user = await self._current_user_service.get_current_user() + authorize( + CanManageRole(), + context=RoleManagementContext( + subject=current_user, + target_role=UserRole.ADMIN, + ), + ) + user_id = UserId(request.user_id) + user = await self._user_tx_storage.get_by_id( + user_id, + for_update=True, + ) + if user is None: + raise UserNotFoundError + + authorize( + CanManageSubordinate(), + context=UserManagementContext( + subject=current_user, + target=user, + ), + ) + if self._user_service.set_role( + user, + now=self._utc_timer.now, + is_admin=True, + ): + await self._transaction_manager.commit() + + logger.info("Grant admin: done.") diff --git a/src/app/application/common/__init__.py b/src/app/core/commands/ports/__init__.py similarity index 100% rename from src/app/application/common/__init__.py rename to src/app/core/commands/ports/__init__.py diff --git a/src/app/core/commands/ports/flusher.py b/src/app/core/commands/ports/flusher.py new file mode 100644 index 00000000..0eb77c6e --- /dev/null +++ b/src/app/core/commands/ports/flusher.py @@ -0,0 +1,10 @@ +from abc import abstractmethod +from typing import Protocol + + +class Flusher(Protocol): + """Interface for flushing intermediate changes during a business transaction.""" + + @abstractmethod + async def flush(self) -> None: + """Flush pending changes to validate constraints or trigger side effects.""" diff --git a/src/app/application/common/ports/transaction_manager.py b/src/app/core/commands/ports/transaction_manager.py similarity index 73% rename from src/app/application/common/ports/transaction_manager.py rename to src/app/core/commands/ports/transaction_manager.py index 72f9cccd..3193ad31 100644 --- a/src/app/application/common/ports/transaction_manager.py +++ b/src/app/core/commands/ports/transaction_manager.py @@ -11,8 +11,4 @@ class TransactionManager(Protocol): @abstractmethod async def commit(self) -> None: - """ - :raises DataMapperError: - - Commit the successful outcome of a business transaction. - """ + """Commit the successful outcome of a business transaction.""" diff --git a/src/app/core/commands/ports/user_tx_storage.py b/src/app/core/commands/ports/user_tx_storage.py new file mode 100644 index 00000000..fe65da86 --- /dev/null +++ b/src/app/core/commands/ports/user_tx_storage.py @@ -0,0 +1,20 @@ +from abc import abstractmethod +from typing import Protocol + +from app.core.common.entities.types_ import UserId +from app.core.common.entities.user import User + + +class UserTxStorage(Protocol): + """Transactional: commit required.""" + + @abstractmethod + def add(self, user: User) -> None: ... + + @abstractmethod + async def get_by_id( + self, + user_id: UserId, + *, + for_update: bool = False, + ) -> User | None: ... diff --git a/src/app/core/commands/ports/utc_timer.py b/src/app/core/commands/ports/utc_timer.py new file mode 100644 index 00000000..6dff8549 --- /dev/null +++ b/src/app/core/commands/ports/utc_timer.py @@ -0,0 +1,10 @@ +from abc import abstractmethod +from typing import Protocol + +from app.core.common.value_objects.utc_datetime import UtcDatetime + + +class UtcTimer(Protocol): + @property + @abstractmethod + def now(self) -> UtcDatetime: ... diff --git a/src/app/core/commands/revoke_admin.py b/src/app/core/commands/revoke_admin.py new file mode 100644 index 00000000..2e2a7b6c --- /dev/null +++ b/src/app/core/commands/revoke_admin.py @@ -0,0 +1,82 @@ +import logging +from dataclasses import dataclass +from uuid import UUID + +from app.core.commands.exceptions import UserNotFoundError +from app.core.commands.ports.transaction_manager import TransactionManager +from app.core.commands.ports.user_tx_storage import UserTxStorage +from app.core.commands.ports.utc_timer import UtcTimer +from app.core.common.authorization.authorize import authorize +from app.core.common.authorization.current_user_service import CurrentUserService +from app.core.common.authorization.permissions import ( + CanManageRole, + CanManageSubordinate, + RoleManagementContext, + UserManagementContext, +) +from app.core.common.entities.types_ import UserId, UserRole +from app.core.common.services.user import UserService + +logger = logging.getLogger(__name__) + + +@dataclass(frozen=True, slots=True) +class RevokeAdminRequest: + user_id: UUID + + +class RevokeAdmin: + """ + - Open to super admins. + - Revokes admin rights from specified user. + - Super admin rights cannot be changed + """ + + def __init__( + self, + current_user_service: CurrentUserService, + user_tx_storage: UserTxStorage, + user_service: UserService, + utc_timer: UtcTimer, + transaction_manager: TransactionManager, + ) -> None: + self._current_user_service = current_user_service + self._user_tx_storage = user_tx_storage + self._user_service = user_service + self._utc_timer = utc_timer + self._transaction_manager = transaction_manager + + async def execute(self, request: RevokeAdminRequest) -> None: + logger.info("Revoke admin: started.") + + current_user = await self._current_user_service.get_current_user() + authorize( + CanManageRole(), + context=RoleManagementContext( + subject=current_user, + target_role=UserRole.ADMIN, + ), + ) + user_id = UserId(request.user_id) + user = await self._user_tx_storage.get_by_id( + user_id, + for_update=True, + ) + if user is None: + raise UserNotFoundError + + authorize( + CanManageSubordinate(), + context=UserManagementContext( + subject=current_user, + target=user, + ), + ) + if self._user_service.set_role( + user, + now=self._utc_timer.now, + is_admin=False, + ): + await self._transaction_manager.commit() + + logger.info("Revoke admin: done.") diff --git a/src/app/core/commands/set_user_password.py b/src/app/core/commands/set_user_password.py new file mode 100644 index 00000000..fbc18387 --- /dev/null +++ b/src/app/core/commands/set_user_password.py @@ -0,0 +1,85 @@ +import logging +from dataclasses import dataclass +from uuid import UUID + +from app.core.commands.exceptions import UserNotFoundError +from app.core.commands.ports.transaction_manager import TransactionManager +from app.core.commands.ports.user_tx_storage import UserTxStorage +from app.core.commands.ports.utc_timer import UtcTimer +from app.core.common.authorization.authorize import authorize +from app.core.common.authorization.current_user_service import CurrentUserService +from app.core.common.authorization.permissions import ( + CanManageRole, + CanManageSubordinate, + RoleManagementContext, + UserManagementContext, +) +from app.core.common.entities.types_ import UserId, UserRole +from app.core.common.services.user import UserService +from app.core.common.value_objects.raw_password import RawPassword + +logger = logging.getLogger(__name__) + + +@dataclass(frozen=True, slots=True) +class SetUserPasswordRequest: + user_id: UUID + password: str + + +class SetUserPassword: + """ + - Open to admins. + - Admins can set passwords of subordinate users. + """ + + def __init__( + self, + current_user_service: CurrentUserService, + user_tx_storage: UserTxStorage, + user_service: UserService, + utc_timer: UtcTimer, + transaction_manager: TransactionManager, + ) -> None: + self._current_user_service = current_user_service + self._user_tx_storage = user_tx_storage + self._user_service = user_service + self._utc_timer = utc_timer + self._transaction_manager = transaction_manager + + async def execute(self, request: SetUserPasswordRequest) -> None: + logger.info("Set user password: started.") + + current_user = await self._current_user_service.get_current_user() + authorize( + CanManageRole(), + context=RoleManagementContext( + subject=current_user, + target_role=UserRole.USER, + ), + ) + user_id = UserId(request.user_id) + password = RawPassword(request.password) + user = await self._user_tx_storage.get_by_id( + user_id, + for_update=True, + ) + if user is None: + raise UserNotFoundError + + authorize( + CanManageSubordinate(), + context=UserManagementContext( + subject=current_user, + target=user, + ), + ) + + await self._user_service.change_password( + user, + password, + now=self._utc_timer.now, + ) + await self._transaction_manager.commit() + + logger.info("Set user password: done.") diff --git a/src/app/application/common/exceptions/__init__.py b/src/app/core/common/__init__.py similarity index 100% rename from src/app/application/common/exceptions/__init__.py rename to src/app/core/common/__init__.py diff --git a/src/app/application/common/ports/__init__.py b/src/app/core/common/authorization/__init__.py similarity index 100% rename from src/app/application/common/ports/__init__.py rename to src/app/core/common/authorization/__init__.py diff --git a/src/app/core/common/authorization/authorize.py b/src/app/core/common/authorization/authorize.py new file mode 100644 index 00000000..61aea7dd --- /dev/null +++ b/src/app/core/common/authorization/authorize.py @@ -0,0 +1,11 @@ +from app.core.common.authorization.base import Permission, PermissionContext +from app.core.common.authorization.exceptions import AuthorizationError + + +def authorize[PC: PermissionContext]( + permission: Permission[PC], + *, + context: PC, +) -> None: + if not permission.is_satisfied_by(context): + raise AuthorizationError diff --git a/src/app/application/common/services/authorization/base.py b/src/app/core/common/authorization/base.py similarity index 86% rename from src/app/application/common/services/authorization/base.py rename to src/app/core/common/authorization/base.py index a824a9c5..5b08c226 100644 --- a/src/app/application/common/services/authorization/base.py +++ b/src/app/core/common/authorization/base.py @@ -2,7 +2,7 @@ from dataclasses import dataclass -@dataclass(frozen=True) +@dataclass(frozen=True, slots=True) class PermissionContext: pass diff --git a/src/app/application/common/services/authorization/composite.py b/src/app/core/common/authorization/composite.py similarity index 72% rename from src/app/application/common/services/authorization/composite.py rename to src/app/core/common/authorization/composite.py index 3ac1dae4..8c72daa4 100644 --- a/src/app/application/common/services/authorization/composite.py +++ b/src/app/core/common/authorization/composite.py @@ -1,7 +1,4 @@ -from app.application.common.services.authorization.base import ( - Permission, - PermissionContext, -) +from app.core.common.authorization.base import Permission, PermissionContext class AnyOf[PC: PermissionContext](Permission[PC]): diff --git a/src/app/core/common/authorization/current_user_service.py b/src/app/core/common/authorization/current_user_service.py new file mode 100644 index 00000000..5bcd2bc6 --- /dev/null +++ b/src/app/core/common/authorization/current_user_service.py @@ -0,0 +1,33 @@ +import logging +from typing import Final + +from app.core.common.authorization.exceptions import AuthorizationError +from app.core.common.authorization.ports import AuthzUserFinder +from app.core.common.entities.user import User +from app.core.common.ports.access_revoker import AccessRevoker +from app.core.common.ports.identity_provider import IdentityProvider + +AUTHZ_NO_CURRENT_USER: Final[str] = "Failed to retrieve current user. Removing all access." + +logger = logging.getLogger(__name__) + + +class CurrentUserService: + def __init__( + self, + identity_provider: IdentityProvider, + authz_user_finder: AuthzUserFinder, + access_revoker: AccessRevoker, + ) -> None: + self._identity_provider = identity_provider + self._authz_user_finder = authz_user_finder + self._access_revoker = access_revoker + + async def get_current_user(self, *, for_update: bool = False) -> User: + current_user_id = await self._identity_provider.get_current_user_id() + user = await self._authz_user_finder.get_by_id(current_user_id, for_update=for_update) + if user is None or not user.is_active: + logger.warning("%s ID: %s.", AUTHZ_NO_CURRENT_USER, current_user_id) + await self._access_revoker.remove_all_user_access(current_user_id) + raise AuthorizationError + return user diff --git a/src/app/core/common/authorization/exceptions.py b/src/app/core/common/authorization/exceptions.py new file mode 100644 index 00000000..df6eb334 --- /dev/null +++ b/src/app/core/common/authorization/exceptions.py @@ -0,0 +1,7 @@ +from typing import ClassVar + +from app.core.common.exceptions import BaseError + + +class AuthorizationError(BaseError): + default_message: ClassVar[str] = "Not authorized." diff --git a/src/app/application/common/services/authorization/permissions.py b/src/app/core/common/authorization/permissions.py similarity index 62% rename from src/app/application/common/services/authorization/permissions.py rename to src/app/core/common/authorization/permissions.py index 8e302f97..66a348ea 100644 --- a/src/app/application/common/services/authorization/permissions.py +++ b/src/app/core/common/authorization/permissions.py @@ -1,18 +1,13 @@ from collections.abc import Mapping from dataclasses import dataclass -from app.application.common.services.authorization.base import ( - Permission, - PermissionContext, -) -from app.application.common.services.authorization.role_hierarchy import ( - SUBORDINATE_ROLES, -) -from app.domain.entities.user import User -from app.domain.enums.user_role import UserRole - - -@dataclass(frozen=True, kw_only=True) +from app.core.common.authorization.base import Permission, PermissionContext +from app.core.common.authorization.role_hierarchy import ROLE_HIERARCHY +from app.core.common.entities.types_ import UserRole +from app.core.common.entities.user import User + + +@dataclass(frozen=True, slots=True, kw_only=True) class UserManagementContext(PermissionContext): subject: User target: User @@ -24,10 +19,7 @@ def is_satisfied_by(self, context: UserManagementContext) -> bool: class CanManageSubordinate(Permission[UserManagementContext]): - def __init__( - self, - role_hierarchy: Mapping[UserRole, set[UserRole]] = SUBORDINATE_ROLES, - ) -> None: + def __init__(self, role_hierarchy: Mapping[UserRole, set[UserRole]] = ROLE_HIERARCHY) -> None: self._role_hierarchy = role_hierarchy def is_satisfied_by(self, context: UserManagementContext) -> bool: @@ -35,17 +27,14 @@ def is_satisfied_by(self, context: UserManagementContext) -> bool: return context.target.role in allowed_roles -@dataclass(frozen=True, kw_only=True) +@dataclass(frozen=True, slots=True, kw_only=True) class RoleManagementContext(PermissionContext): subject: User target_role: UserRole class CanManageRole(Permission[RoleManagementContext]): - def __init__( - self, - role_hierarchy: Mapping[UserRole, set[UserRole]] = SUBORDINATE_ROLES, - ) -> None: + def __init__(self, role_hierarchy: Mapping[UserRole, set[UserRole]] = ROLE_HIERARCHY) -> None: self._role_hierarchy = role_hierarchy def is_satisfied_by(self, context: RoleManagementContext) -> bool: diff --git a/src/app/core/common/authorization/ports.py b/src/app/core/common/authorization/ports.py new file mode 100644 index 00000000..0bb7c60b --- /dev/null +++ b/src/app/core/common/authorization/ports.py @@ -0,0 +1,10 @@ +from abc import abstractmethod +from typing import Protocol + +from app.core.common.entities.types_ import UserId +from app.core.common.entities.user import User + + +class AuthzUserFinder(Protocol): + @abstractmethod + async def get_by_id(self, user_id: UserId, *, for_update: bool = False) -> User | None: ... diff --git a/src/app/application/common/services/authorization/role_hierarchy.py b/src/app/core/common/authorization/role_hierarchy.py similarity index 62% rename from src/app/application/common/services/authorization/role_hierarchy.py rename to src/app/core/common/authorization/role_hierarchy.py index cc14fd45..93eb270f 100644 --- a/src/app/application/common/services/authorization/role_hierarchy.py +++ b/src/app/core/common/authorization/role_hierarchy.py @@ -1,9 +1,9 @@ from collections.abc import Mapping from typing import Final -from app.domain.enums.user_role import UserRole +from app.core.common.entities.types_ import UserRole -SUBORDINATE_ROLES: Final[Mapping[UserRole, set[UserRole]]] = { +ROLE_HIERARCHY: Final[Mapping[UserRole, set[UserRole]]] = { UserRole.SUPER_ADMIN: {UserRole.ADMIN, UserRole.USER}, UserRole.ADMIN: {UserRole.USER}, UserRole.USER: set(), diff --git a/src/app/application/common/query_params/__init__.py b/src/app/core/common/entities/__init__.py similarity index 100% rename from src/app/application/common/query_params/__init__.py rename to src/app/core/common/entities/__init__.py diff --git a/src/app/domain/entities/base.py b/src/app/core/common/entities/base.py similarity index 100% rename from src/app/domain/entities/base.py rename to src/app/core/common/entities/base.py diff --git a/src/app/core/common/entities/types_.py b/src/app/core/common/entities/types_.py new file mode 100644 index 00000000..195a22e3 --- /dev/null +++ b/src/app/core/common/entities/types_.py @@ -0,0 +1,16 @@ +from enum import StrEnum +from typing import NewType +from uuid import UUID + +UserId = NewType("UserId", UUID) +UserPasswordHash = NewType("UserPasswordHash", bytes) + + +class UserRole(StrEnum): + SUPER_ADMIN = "super_admin" + ADMIN = "admin" + USER = "user" + + @property + def is_system(self) -> bool: + return self == UserRole.SUPER_ADMIN diff --git a/src/app/core/common/entities/user.py b/src/app/core/common/entities/user.py new file mode 100644 index 00000000..5651ce6f --- /dev/null +++ b/src/app/core/common/entities/user.py @@ -0,0 +1,29 @@ +from app.core.common.entities.base import Entity +from app.core.common.entities.types_ import UserId, UserPasswordHash, UserRole +from app.core.common.value_objects.username import Username +from app.core.common.value_objects.utc_datetime import UtcDatetime + + +class User(Entity[UserId]): + def __init__( + self, + *, + id_: UserId, + username: Username, + password_hash: UserPasswordHash, + role: UserRole, + is_active: bool, + created_at: UtcDatetime, + updated_at: UtcDatetime, + ) -> None: + super().__init__(id_=id_) + self.username = username + self.password_hash = password_hash + self.role = role + self.is_active = is_active + self._created_at = created_at + self.updated_at = updated_at + + @property + def created_at(self) -> UtcDatetime: + return self._created_at diff --git a/src/app/core/common/exceptions.py b/src/app/core/common/exceptions.py new file mode 100644 index 00000000..eaca1520 --- /dev/null +++ b/src/app/core/common/exceptions.py @@ -0,0 +1,25 @@ +from typing import ClassVar + + +class BaseError(Exception): + default_message: ClassVar[str | None] = None + + def __init__(self, message: str | None = None) -> None: + msg = message if message is not None else self.default_message + super().__init__() if msg is None else super().__init__(msg) + + +class BusinessTypeError(BaseError): + """Invalid construction of business logic types (Value Objects).""" + + +class RoleAssignmentNotPermittedError(BaseError): + default_message: ClassVar[str] = "Assignment of role is not permitted." + + +class ActivationChangeNotPermittedError(BaseError): + default_message: ClassVar[str] = "Activation change is not permitted." + + +class RoleChangeNotPermittedError(BaseError): + default_message: ClassVar[str] = "Role change is not permitted." diff --git a/src/app/application/common/services/__init__.py b/src/app/core/common/factories/__init__.py similarity index 100% rename from src/app/application/common/services/__init__.py rename to src/app/core/common/factories/__init__.py diff --git a/src/app/core/common/factories/id_factory.py b/src/app/core/common/factories/id_factory.py new file mode 100644 index 00000000..c5f0974d --- /dev/null +++ b/src/app/core/common/factories/id_factory.py @@ -0,0 +1,9 @@ +from uuid import UUID + +from uuid_utils import compat as uuid_utils + +from app.core.common.entities.types_ import UserId + + +def create_user_id(value: UUID | None = None) -> UserId: + return UserId(value if value is not None else uuid_utils.uuid7()) diff --git a/src/app/application/common/services/authorization/__init__.py b/src/app/core/common/ports/__init__.py similarity index 100% rename from src/app/application/common/services/authorization/__init__.py rename to src/app/core/common/ports/__init__.py diff --git a/src/app/application/common/ports/access_revoker.py b/src/app/core/common/ports/access_revoker.py similarity index 64% rename from src/app/application/common/ports/access_revoker.py rename to src/app/core/common/ports/access_revoker.py index 9dba834c..9752a839 100644 --- a/src/app/application/common/ports/access_revoker.py +++ b/src/app/core/common/ports/access_revoker.py @@ -1,10 +1,9 @@ from abc import abstractmethod from typing import Protocol -from app.domain.value_objects.user_id import UserId +from app.core.common.entities.types_ import UserId class AccessRevoker(Protocol): @abstractmethod - async def remove_all_user_access(self, user_id: UserId) -> None: - """:raises DataMapperError:""" + async def remove_all_user_access(self, user_id: UserId) -> None: ... diff --git a/src/app/core/common/ports/identity_provider.py b/src/app/core/common/ports/identity_provider.py new file mode 100644 index 00000000..f292219e --- /dev/null +++ b/src/app/core/common/ports/identity_provider.py @@ -0,0 +1,9 @@ +from abc import abstractmethod +from typing import Protocol + +from app.core.common.entities.types_ import UserId + + +class IdentityProvider(Protocol): + @abstractmethod + async def get_current_user_id(self) -> UserId: ... diff --git a/src/app/core/common/ports/password_hasher.py b/src/app/core/common/ports/password_hasher.py new file mode 100644 index 00000000..4da35ac3 --- /dev/null +++ b/src/app/core/common/ports/password_hasher.py @@ -0,0 +1,13 @@ +from abc import abstractmethod +from typing import Protocol + +from app.core.common.entities.types_ import UserPasswordHash +from app.core.common.value_objects.raw_password import RawPassword + + +class PasswordHasher(Protocol): + @abstractmethod + async def hash(self, raw_password: RawPassword) -> UserPasswordHash: ... + + @abstractmethod + async def verify(self, raw_password: RawPassword, hashed_password: UserPasswordHash) -> bool: ... diff --git a/src/app/application/queries/__init__.py b/src/app/core/common/services/__init__.py similarity index 100% rename from src/app/application/queries/__init__.py rename to src/app/core/common/services/__init__.py diff --git a/src/app/core/common/services/user.py b/src/app/core/common/services/user.py new file mode 100644 index 00000000..29fa3ba3 --- /dev/null +++ b/src/app/core/common/services/user.py @@ -0,0 +1,105 @@ +from app.core.common.entities.types_ import UserId, UserPasswordHash, UserRole +from app.core.common.entities.user import User +from app.core.common.exceptions import ( + ActivationChangeNotPermittedError, + RoleAssignmentNotPermittedError, + RoleChangeNotPermittedError, +) +from app.core.common.ports.password_hasher import PasswordHasher +from app.core.common.value_objects.raw_password import RawPassword +from app.core.common.value_objects.username import Username +from app.core.common.value_objects.utc_datetime import UtcDatetime + + +class UserService: + def __init__(self, password_hasher: PasswordHasher) -> None: + self._password_hasher = password_hasher + + def create_user( + self, + user_id: UserId, + username: Username, + password_hash: UserPasswordHash, + *, + now: UtcDatetime, + role: UserRole = UserRole.USER, + is_active: bool = True, + ) -> User: + if role.is_system: + raise RoleAssignmentNotPermittedError + return User( + id_=user_id, + username=username, + password_hash=password_hash, + role=role, + is_active=is_active, + created_at=now, + updated_at=now, + ) + + async def create_user_with_raw_password( + self, + user_id: UserId, + username: Username, + raw_password: RawPassword, + *, + now: UtcDatetime, + role: UserRole = UserRole.USER, + is_active: bool = True, + ) -> User: + password_hash = await self._password_hasher.hash(raw_password) + return self.create_user( + user_id, + username, + password_hash, + now=now, + role=role, + is_active=is_active, + ) + + async def is_password_valid(self, user: User, raw_password: RawPassword) -> bool: + return await self._password_hasher.verify( + raw_password=raw_password, + hashed_password=user.password_hash, + ) + + async def change_password( + self, + user: User, + raw_password: RawPassword, + *, + now: UtcDatetime, + ) -> None: + user.password_hash = await self._password_hasher.hash(raw_password) + user.updated_at = now + + def set_role( + self, + user: User, + *, + now: UtcDatetime, + is_admin: bool, + ) -> bool: + if user.role.is_system: + raise RoleChangeNotPermittedError + target_role = UserRole.ADMIN if is_admin else UserRole.USER + if user.role == target_role: + return False + user.role = target_role + user.updated_at = now + return True + + def set_activation( + self, + user: User, + *, + now: UtcDatetime, + is_active: bool, + ) -> bool: + if user.role.is_system: + raise ActivationChangeNotPermittedError + if user.is_active == is_active: + return False + user.is_active = is_active + user.updated_at = now + return True diff --git a/src/app/domain/__init__.py b/src/app/core/common/value_objects/__init__.py similarity index 100% rename from src/app/domain/__init__.py rename to src/app/core/common/value_objects/__init__.py diff --git a/src/app/domain/value_objects/base.py b/src/app/core/common/value_objects/base.py similarity index 68% rename from src/app/domain/value_objects/base.py rename to src/app/core/common/value_objects/base.py index ab02c8ac..f07295a4 100644 --- a/src/app/domain/value_objects/base.py +++ b/src/app/core/common/value_objects/base.py @@ -5,7 +5,7 @@ @dataclass(frozen=True, slots=True, repr=False) class ValueObject: """ - Base class for immutable value objects (VO) in domain. + Base class for immutable value objects (VO). Subclassing is optional; any implementation honoring this contract is valid. Defined by instance attributes only; these must be immutable. For simple type tagging, consider `typing.NewType` instead of subclassing. @@ -14,16 +14,12 @@ class ValueObject: fields with `repr=False` are omitted to avoid leaking secrets. If no fields have `repr=True`, '' is shown. - Typing/runtime mismatch when working with class constants: - By current typing rules, `Final` should wrap `ClassVar` β†’ `Final[ClassVar[T]]`. - At runtime, `dataclasses.fields()` includes it as an instance field (and with - `__slots__` it becomes a `member_descriptor`). Use `ClassVar[Final[T]]` - (or `ClassVar[T]`) so class constants are not treated as instance attributes. - As of now, mypy does not enforce `Final` inside `ClassVar`; reassignment is - allowed, so `ClassVar[Final[T]]` is effectively `ClassVar[T]`. We keep `Final` - for forward-compatibility, expecting future enforcement. - https://github.com/python/cpython/issues/89547 - https://github.com/python/mypy/issues/19607 + Typing/runtime note for class constants: + Don't combine `ClassVar` with `Final` in any order for safety reasons. + Python 3.12 doesn't support this at runtime, Python 3.13 has compatibility issues. + + Dataclasses exclude only `ClassVar` attributes from instance fields, so use `ClassVar[T]` + for class-level constants and enforce non-reassignment via convention/linting/type-checker. """ def __new__(cls, *_args: Any, **_kwargs: Any) -> Self: @@ -34,7 +30,10 @@ def __new__(cls, *_args: Any, **_kwargs: Any) -> Self: return object.__new__(cls) def __post_init__(self) -> None: - """Hook for additional initialization and ensuring invariants.""" + """ + Hook for additional initialization and ensuring invariants. + If this hook ever becomes non-empty, subclasses must call `super().__post_init__()`. + """ def __repr__(self) -> str: """ diff --git a/src/app/core/common/value_objects/raw_password.py b/src/app/core/common/value_objects/raw_password.py new file mode 100644 index 00000000..2b643d3e --- /dev/null +++ b/src/app/core/common/value_objects/raw_password.py @@ -0,0 +1,21 @@ +from dataclasses import dataclass, field +from typing import ClassVar + +from app.core.common.exceptions import BusinessTypeError +from app.core.common.value_objects.base import ValueObject + + +@dataclass(frozen=True, slots=True, repr=False) +class RawPassword(ValueObject): + MIN_LEN: ClassVar[int] = 6 + + value: bytes = field(init=False, repr=False) + + def __init__(self, value: str) -> None: + self._validate(value) + object.__setattr__(self, "value", value.encode()) + + @classmethod + def _validate(cls, value: str) -> None: + if len(value) < cls.MIN_LEN: + raise BusinessTypeError(f"Password must be at least {cls.MIN_LEN} characters long.") diff --git a/src/app/core/common/value_objects/username.py b/src/app/core/common/value_objects/username.py new file mode 100644 index 00000000..2c9a94f7 --- /dev/null +++ b/src/app/core/common/value_objects/username.py @@ -0,0 +1,41 @@ +import re +from dataclasses import dataclass +from typing import ClassVar + +from app.core.common.exceptions import BusinessTypeError +from app.core.common.value_objects.base import ValueObject + + +@dataclass(frozen=True, slots=True, repr=False) +class Username(ValueObject): + MIN_LEN: ClassVar[int] = 5 + MAX_LEN: ClassVar[int] = 20 + + # 1) allowed alphabet only + PATTERN_ALLOWED_CHARS: ClassVar[re.Pattern[str]] = re.compile(r"^[A-Za-z0-9._-]+$") + # 2) start / end must be alnum + PATTERN_START: ClassVar[re.Pattern[str]] = re.compile(r"^[A-Za-z0-9]") + PATTERN_END: ClassVar[re.Pattern[str]] = re.compile(r"[A-Za-z0-9]$") + # 3) no consecutive specials + PATTERN_CONSECUTIVE_SPECIALS: ClassVar[re.Pattern[str]] = re.compile(r"[._-]{2,}") + + value: str + + def __post_init__(self) -> None: + self._validate(self.value) + + @classmethod + def _validate(cls, value: str) -> None: + if len(value) < cls.MIN_LEN or len(value) > cls.MAX_LEN: + raise BusinessTypeError(f"{cls.__name__} must be between {cls.MIN_LEN} and {cls.MAX_LEN} characters.") + if not cls.PATTERN_ALLOWED_CHARS.fullmatch(value): + raise BusinessTypeError( + f"{cls.__name__} can only contain letters (A-Z, a-z), digits (0-9), " + "dots (.), hyphens (-), and underscores (_)." + ) + if not cls.PATTERN_START.match(value): + raise BusinessTypeError(f"{cls.__name__} must start with a letter (A-Z, a-z) or a digit (0-9).") + if not cls.PATTERN_END.search(value): + raise BusinessTypeError(f"{cls.__name__} must end with a letter (A-Z, a-z) or a digit (0-9).") + if cls.PATTERN_CONSECUTIVE_SPECIALS.search(value): + raise BusinessTypeError(f"{cls.__name__} cannot contain consecutive special characters like .., --, or __.") diff --git a/src/app/core/common/value_objects/utc_datetime.py b/src/app/core/common/value_objects/utc_datetime.py new file mode 100644 index 00000000..58e5b6bd --- /dev/null +++ b/src/app/core/common/value_objects/utc_datetime.py @@ -0,0 +1,23 @@ +from dataclasses import dataclass +from datetime import UTC, datetime + +from app.core.common.exceptions import BusinessTypeError +from app.core.common.value_objects.base import ValueObject + + +@dataclass(frozen=True, slots=True, repr=False) +class UtcDatetime(ValueObject): + value: datetime + + def __post_init__(self) -> None: + self._ensure_is_tz_aware(self.value) + object.__setattr__(self, "value", self._normalize(self.value)) + + @classmethod + def _ensure_is_tz_aware(cls, dt: datetime) -> None: + if dt.tzinfo is None or dt.utcoffset() is None: + raise BusinessTypeError(f"{cls.__name__}: timezone-aware datetime required, got {dt!r}") + + @classmethod + def _normalize(cls, dt: datetime) -> datetime: + return dt.astimezone(UTC) diff --git a/src/app/domain/entities/__init__.py b/src/app/core/queries/__init__.py similarity index 100% rename from src/app/domain/entities/__init__.py rename to src/app/core/queries/__init__.py diff --git a/src/app/core/queries/list_users.py b/src/app/core/queries/list_users.py new file mode 100644 index 00000000..9950fd48 --- /dev/null +++ b/src/app/core/queries/list_users.py @@ -0,0 +1,71 @@ +import logging +from dataclasses import dataclass +from enum import StrEnum + +from app.core.common.authorization.authorize import authorize +from app.core.common.authorization.current_user_service import CurrentUserService +from app.core.common.authorization.permissions import CanManageRole, RoleManagementContext +from app.core.common.entities.types_ import UserRole +from app.core.queries.ports.user_reader import ListUsersQm, UserReader +from app.core.queries.query_support.offset_pagination import OffsetPaginationParams +from app.core.queries.query_support.sorting import SortingOrder, SortingParams + +logger = logging.getLogger(__name__) + + +class UserSortingField(StrEnum): + USERNAME = "username" + ROLE = "role" + IS_ACTIVE = "is_active" + CREATED_AT = "created_at" + UPDATED_AT = "updated_at" + + +@dataclass(frozen=True, slots=True, kw_only=True) +class ListUsersRequest: + limit: int + offset: int + sorting_field: UserSortingField + sorting_order: SortingOrder + + +class ListUsers: + """ + - Open to admins. + - Retrieves paginated list of existing users with relevant info. + """ + + def __init__( + self, + current_user_service: CurrentUserService, + user_reader: UserReader, + ) -> None: + self._current_user_service = current_user_service + self._user_reader = user_reader + + async def execute(self, request: ListUsersRequest) -> ListUsersQm: + logger.info("List users: started.") + + current_user = await self._current_user_service.get_current_user() + authorize( + CanManageRole(), + context=RoleManagementContext( + subject=current_user, + target_role=UserRole.USER, + ), + ) + pagination = OffsetPaginationParams( + limit=request.limit, + offset=request.offset, + ) + sorting = SortingParams( + field=request.sorting_field, + order=request.sorting_order, + ) + users = await self._user_reader.list_users( + pagination=pagination, + sorting=sorting, + ) + + logger.info("List users: done.") + return users diff --git a/src/app/domain/enums/__init__.py b/src/app/core/queries/models/__init__.py similarity index 100% rename from src/app/domain/enums/__init__.py rename to src/app/core/queries/models/__init__.py diff --git a/src/app/core/queries/models/user.py b/src/app/core/queries/models/user.py new file mode 100644 index 00000000..c5df7e6c --- /dev/null +++ b/src/app/core/queries/models/user.py @@ -0,0 +1,13 @@ +from dataclasses import dataclass +from datetime import datetime +from uuid import UUID + + +@dataclass(frozen=True, slots=True) +class UserQm: + id: UUID + username: str + role: str + is_active: bool + created_at: datetime + updated_at: datetime diff --git a/src/app/domain/exceptions/__init__.py b/src/app/core/queries/ports/__init__.py similarity index 100% rename from src/app/domain/exceptions/__init__.py rename to src/app/core/queries/ports/__init__.py diff --git a/src/app/core/queries/ports/user_reader.py b/src/app/core/queries/ports/user_reader.py new file mode 100644 index 00000000..baddf715 --- /dev/null +++ b/src/app/core/queries/ports/user_reader.py @@ -0,0 +1,23 @@ +from abc import abstractmethod +from typing import Protocol, TypedDict + +from app.core.queries.models.user import UserQm +from app.core.queries.query_support.offset_pagination import OffsetPaginationParams +from app.core.queries.query_support.sorting import SortingParams + + +class ListUsersQm(TypedDict): + users: list[UserQm] + total: int + limit: int + offset: int + + +class UserReader(Protocol): + @abstractmethod + async def list_users( + self, + *, + pagination: OffsetPaginationParams, + sorting: SortingParams, + ) -> ListUsersQm: ... diff --git a/src/app/domain/ports/__init__.py b/src/app/core/queries/query_support/__init__.py similarity index 100% rename from src/app/domain/ports/__init__.py rename to src/app/core/queries/query_support/__init__.py diff --git a/src/app/core/queries/query_support/exceptions.py b/src/app/core/queries/query_support/exceptions.py new file mode 100644 index 00000000..c24533fb --- /dev/null +++ b/src/app/core/queries/query_support/exceptions.py @@ -0,0 +1,9 @@ +from app.core.common.exceptions import BaseError + + +class PaginationError(BaseError): + pass + + +class SortingError(BaseError): + pass diff --git a/src/app/core/queries/query_support/offset_pagination.py b/src/app/core/queries/query_support/offset_pagination.py new file mode 100644 index 00000000..5e6e8db7 --- /dev/null +++ b/src/app/core/queries/query_support/offset_pagination.py @@ -0,0 +1,26 @@ +from dataclasses import dataclass +from typing import ClassVar + +from app.core.queries.query_support.exceptions import PaginationError + + +@dataclass(frozen=True, slots=True, kw_only=True) +class OffsetPaginationParams: + MAX_INT32: ClassVar[int] = 2**31 - 1 + + limit: int + offset: int + + def __post_init__(self) -> None: + self._validate(limit=self.limit, offset=self.offset) + + @classmethod + def _validate(cls, limit: int, offset: int) -> None: + if limit <= 0: + raise PaginationError(f"Limit must be greater than 0, got {limit}") + if limit > cls.MAX_INT32: + raise PaginationError(f"Limit cannot be greater than {cls.MAX_INT32}, got {limit}") + if offset < 0: + raise PaginationError(f"Offset must be non-negative, got {offset}") + if offset > cls.MAX_INT32: + raise PaginationError(f"Offset cannot be greater than {cls.MAX_INT32}, got {offset}") diff --git a/src/app/application/common/query_params/sorting.py b/src/app/core/queries/query_support/sorting.py similarity index 100% rename from src/app/application/common/query_params/sorting.py rename to src/app/core/queries/query_support/sorting.py diff --git a/src/app/domain/entities/user.py b/src/app/domain/entities/user.py deleted file mode 100644 index d7864130..00000000 --- a/src/app/domain/entities/user.py +++ /dev/null @@ -1,22 +0,0 @@ -from app.domain.entities.base import Entity -from app.domain.enums.user_role import UserRole -from app.domain.value_objects.user_id import UserId -from app.domain.value_objects.user_password_hash import UserPasswordHash -from app.domain.value_objects.username import Username - - -class User(Entity[UserId]): - def __init__( - self, - *, - id_: UserId, - username: Username, - password_hash: UserPasswordHash, - role: UserRole, - is_active: bool, - ) -> None: - super().__init__(id_=id_) - self.username = username - self.password_hash = password_hash - self.role = role - self.is_active = is_active diff --git a/src/app/domain/enums/user_role.py b/src/app/domain/enums/user_role.py deleted file mode 100644 index dd41083b..00000000 --- a/src/app/domain/enums/user_role.py +++ /dev/null @@ -1,15 +0,0 @@ -from enum import StrEnum - - -class UserRole(StrEnum): - SUPER_ADMIN = "super_admin" - ADMIN = "admin" - USER = "user" - - @property - def is_assignable(self) -> bool: - return self != UserRole.SUPER_ADMIN - - @property - def is_changeable(self) -> bool: - return self != UserRole.SUPER_ADMIN diff --git a/src/app/domain/exceptions/base.py b/src/app/domain/exceptions/base.py deleted file mode 100644 index 656b7d23..00000000 --- a/src/app/domain/exceptions/base.py +++ /dev/null @@ -1,6 +0,0 @@ -class DomainTypeError(Exception): - """Invalid construction of domain types (Value Objects).""" - - -class DomainError(Exception): - """Domain rule violation not tied to domain type construction.""" diff --git a/src/app/domain/exceptions/user.py b/src/app/domain/exceptions/user.py deleted file mode 100644 index a6ef7df5..00000000 --- a/src/app/domain/exceptions/user.py +++ /dev/null @@ -1,44 +0,0 @@ -from typing import Any - -from app.domain.enums.user_role import UserRole -from app.domain.exceptions.base import DomainError -from app.domain.value_objects.user_id import UserId -from app.domain.value_objects.username import Username - - -class UsernameAlreadyExistsError(DomainError): - def __init__(self, username: Any) -> None: - message = f"User with {username!r} already exists." - super().__init__(message) - - -class UserNotFoundByIdError(DomainError): - def __init__(self, user_id: UserId) -> None: - message = f"User with {user_id.value!r} is not found." - super().__init__(message) - - -class UserNotFoundByUsernameError(DomainError): - def __init__(self, username: Username) -> None: - message = f"User with {username.value!r} is not found." - super().__init__(message) - - -class ActivationChangeNotPermittedError(DomainError): - def __init__(self, username: Username, role: UserRole) -> None: - message = ( - f"Changing activation of user {username.value!r} ({role}) is not permitted." - ) - super().__init__(message) - - -class RoleAssignmentNotPermittedError(DomainError): - def __init__(self, role: UserRole) -> None: - message = f"Assignment of role {role} is not permitted." - super().__init__(message) - - -class RoleChangeNotPermittedError(DomainError): - def __init__(self, username: Username, role: UserRole) -> None: - message = f"Changing role of user {username.value!r} ({role}) is not permitted." - super().__init__(message) diff --git a/src/app/domain/ports/password_hasher.py b/src/app/domain/ports/password_hasher.py deleted file mode 100644 index 1add22ed..00000000 --- a/src/app/domain/ports/password_hasher.py +++ /dev/null @@ -1,19 +0,0 @@ -from abc import abstractmethod -from typing import Protocol - -from app.domain.value_objects.raw_password import RawPassword -from app.domain.value_objects.user_password_hash import UserPasswordHash - - -class PasswordHasher(Protocol): - @abstractmethod - async def hash(self, raw_password: RawPassword) -> UserPasswordHash: - """:raises PasswordHasherBusyError:""" - - @abstractmethod - async def verify( - self, - raw_password: RawPassword, - hashed_password: UserPasswordHash, - ) -> bool: - """:raises PasswordHasherBusyError:""" diff --git a/src/app/domain/ports/user_id_generator.py b/src/app/domain/ports/user_id_generator.py deleted file mode 100644 index a1de1ce6..00000000 --- a/src/app/domain/ports/user_id_generator.py +++ /dev/null @@ -1,8 +0,0 @@ -from abc import abstractmethod - -from app.domain.value_objects.user_id import UserId - - -class UserIdGenerator: - @abstractmethod - def generate(self) -> UserId: ... diff --git a/src/app/domain/services/user.py b/src/app/domain/services/user.py deleted file mode 100644 index 54c57fd5..00000000 --- a/src/app/domain/services/user.py +++ /dev/null @@ -1,75 +0,0 @@ -from app.domain.entities.user import User -from app.domain.enums.user_role import UserRole -from app.domain.exceptions.user import ( - ActivationChangeNotPermittedError, - RoleAssignmentNotPermittedError, - RoleChangeNotPermittedError, -) -from app.domain.ports.password_hasher import PasswordHasher -from app.domain.ports.user_id_generator import UserIdGenerator -from app.domain.value_objects.raw_password import RawPassword -from app.domain.value_objects.username import Username - - -class UserService: - def __init__( - self, - user_id_generator: UserIdGenerator, - password_hasher: PasswordHasher, - ) -> None: - self._user_id_generator = user_id_generator - self._password_hasher = password_hasher - - async def create_user( - self, - username: Username, - raw_password: RawPassword, - role: UserRole = UserRole.USER, - is_active: bool = True, - ) -> User: - """ - :raises RoleAssignmentNotPermittedError: - :raises PasswordHasherBusyError: - """ - if not role.is_assignable: - raise RoleAssignmentNotPermittedError(role) - - user_id = self._user_id_generator.generate() - password_hash = await self._password_hasher.hash(raw_password) - return User( - id_=user_id, - username=username, - password_hash=password_hash, - role=role, - is_active=is_active, - ) - - async def is_password_valid(self, user: User, raw_password: RawPassword) -> bool: - """:raises PasswordHasherBusyError:""" - return await self._password_hasher.verify( - raw_password=raw_password, - hashed_password=user.password_hash, - ) - - async def change_password(self, user: User, raw_password: RawPassword) -> None: - """:raises PasswordHasherBusyError:""" - user.password_hash = await self._password_hasher.hash(raw_password) - - def toggle_user_activation(self, user: User, *, is_active: bool) -> bool: - """:raises ActivationChangeNotPermittedError:""" - if not user.role.is_changeable: - raise ActivationChangeNotPermittedError(user.username, user.role) - if user.is_active == is_active: - return False - user.is_active = is_active - return True - - def toggle_user_admin_role(self, user: User, *, is_admin: bool) -> bool: - """:raises RoleChangeNotPermittedError:""" - if not user.role.is_changeable: - raise RoleChangeNotPermittedError(user.username, user.role) - target_role = UserRole.ADMIN if is_admin else UserRole.USER - if user.role == target_role: - return False - user.role = target_role - return True diff --git a/src/app/domain/value_objects/raw_password.py b/src/app/domain/value_objects/raw_password.py deleted file mode 100644 index ec209c6b..00000000 --- a/src/app/domain/value_objects/raw_password.py +++ /dev/null @@ -1,26 +0,0 @@ -from dataclasses import dataclass, field -from typing import ClassVar, Final - -from app.domain.exceptions.base import DomainTypeError -from app.domain.value_objects.base import ValueObject - - -@dataclass(frozen=True, slots=True, repr=False) -class RawPassword(ValueObject): - """raises DomainTypeError""" - - MIN_LEN: ClassVar[Final[int]] = 6 - - value: bytes = field(init=False, repr=False) - - def __init__(self, value: str) -> None: - """:raises DomainTypeError:""" - self._validate_password_length(value) - object.__setattr__(self, "value", value.encode()) - - def _validate_password_length(self, password_value: str) -> None: - """:raises DomainTypeError:""" - if len(password_value) < self.MIN_LEN: - raise DomainTypeError( - f"Password must be at least {self.MIN_LEN} characters long.", - ) diff --git a/src/app/domain/value_objects/user_id.py b/src/app/domain/value_objects/user_id.py deleted file mode 100644 index 89032534..00000000 --- a/src/app/domain/value_objects/user_id.py +++ /dev/null @@ -1,9 +0,0 @@ -from dataclasses import dataclass -from uuid import UUID - -from app.domain.value_objects.base import ValueObject - - -@dataclass(frozen=True, slots=True, repr=False) -class UserId(ValueObject): - value: UUID diff --git a/src/app/domain/value_objects/user_password_hash.py b/src/app/domain/value_objects/user_password_hash.py deleted file mode 100644 index ee8b4f3b..00000000 --- a/src/app/domain/value_objects/user_password_hash.py +++ /dev/null @@ -1,8 +0,0 @@ -from dataclasses import dataclass - -from app.domain.value_objects.base import ValueObject - - -@dataclass(frozen=True, slots=True, repr=False) -class UserPasswordHash(ValueObject): - value: bytes diff --git a/src/app/domain/value_objects/username.py b/src/app/domain/value_objects/username.py deleted file mode 100644 index 8ae842fb..00000000 --- a/src/app/domain/value_objects/username.py +++ /dev/null @@ -1,69 +0,0 @@ -import re -from dataclasses import dataclass -from typing import ClassVar, Final - -from app.domain.exceptions.base import DomainTypeError -from app.domain.value_objects.base import ValueObject - - -@dataclass(frozen=True, slots=True, repr=False) -class Username(ValueObject): - """raises DomainTypeError""" - - MIN_LEN: ClassVar[Final[int]] = 5 - MAX_LEN: ClassVar[Final[int]] = 20 - - # Pattern for validating a username: - # - starts with a letter (A-Z, a-z) or a digit (0-9) - PATTERN_START: ClassVar[Final[re.Pattern[str]]] = re.compile( - r"^[a-zA-Z0-9]", - ) - # - can contain multiple special characters . - _ between letters and digits, - PATTERN_ALLOWED_CHARS: ClassVar[Final[re.Pattern[str]]] = re.compile( - r"[a-zA-Z0-9._-]*", - ) - # but only one special character can appear consecutively - PATTERN_NO_CONSECUTIVE_SPECIALS: ClassVar[Final[re.Pattern[str]]] = re.compile( - r"^[a-zA-Z0-9]+([._-]?[a-zA-Z0-9]+)*[._-]?$", - ) - # - ends with a letter (A-Z, a-z) or a digit (0-9) - PATTERN_END: ClassVar[Final[re.Pattern[str]]] = re.compile( - r".*[a-zA-Z0-9]$", - ) - - value: str - - def __post_init__(self) -> None: - """:raises DomainTypeError:""" - self._validate_username_length(self.value) - self._validate_username_pattern(self.value) - - def _validate_username_length(self, username_value: str) -> None: - """:raises DomainTypeError:""" - if len(username_value) < self.MIN_LEN or len(username_value) > self.MAX_LEN: - raise DomainTypeError( - f"Username must be between " - f"{self.MIN_LEN} and " - f"{self.MAX_LEN} characters.", - ) - - def _validate_username_pattern(self, username_value: str) -> None: - """:raises DomainTypeError:""" - if not re.match(self.PATTERN_START, username_value): - raise DomainTypeError( - "Username must start with a letter (A-Z, a-z) or a digit (0-9).", - ) - if not re.fullmatch(self.PATTERN_ALLOWED_CHARS, username_value): - raise DomainTypeError( - "Username can only contain letters (A-Z, a-z), digits (0-9), " - "dots (.), hyphens (-), and underscores (_).", - ) - if not re.fullmatch(self.PATTERN_NO_CONSECUTIVE_SPECIALS, username_value): - raise DomainTypeError( - "Username cannot contain consecutive special characters" - " like .., --, or __.", - ) - if not re.match(self.PATTERN_END, username_value): - raise DomainTypeError( - "Username must end with a letter (A-Z, a-z) or a digit (0-9).", - ) diff --git a/src/app/infrastructure/adapters/auth_session_access_revoker.py b/src/app/infrastructure/adapters/auth_session_access_revoker.py new file mode 100644 index 00000000..2fa0fbc3 --- /dev/null +++ b/src/app/infrastructure/adapters/auth_session_access_revoker.py @@ -0,0 +1,11 @@ +from app.core.common.entities.types_ import UserId +from app.core.common.ports.access_revoker import AccessRevoker +from app.infrastructure.auth_ctx.service import AuthService + + +class AuthSessionAccessRevoker(AccessRevoker): + def __init__(self, auth_service: AuthService) -> None: + self._auth_service = auth_service + + async def remove_all_user_access(self, user_id: UserId) -> None: + await self._auth_service.revoke_all_sessions(user_id) diff --git a/src/app/infrastructure/adapters/auth_session_identity_provider.py b/src/app/infrastructure/adapters/auth_session_identity_provider.py new file mode 100644 index 00000000..239fb3eb --- /dev/null +++ b/src/app/infrastructure/adapters/auth_session_identity_provider.py @@ -0,0 +1,11 @@ +from app.core.common.entities.types_ import UserId +from app.core.common.ports.identity_provider import IdentityProvider +from app.infrastructure.auth_ctx.service import AuthService + + +class AuthSessionIdentityProvider(IdentityProvider): + def __init__(self, auth_service: AuthService) -> None: + self._auth_service = auth_service + + async def get_current_user_id(self) -> UserId: + return await self._auth_service.get_current_user_id() diff --git a/src/app/infrastructure/adapters/password_hasher_bcrypt.py b/src/app/infrastructure/adapters/bcrypt_password_hasher.py similarity index 76% rename from src/app/infrastructure/adapters/password_hasher_bcrypt.py rename to src/app/infrastructure/adapters/bcrypt_password_hasher.py index 01585b00..8a9ed29f 100644 --- a/src/app/infrastructure/adapters/password_hasher_bcrypt.py +++ b/src/app/infrastructure/adapters/bcrypt_password_hasher.py @@ -2,19 +2,20 @@ import base64 import hashlib import hmac -import logging from collections.abc import AsyncIterator +from concurrent.futures import ThreadPoolExecutor from contextlib import asynccontextmanager +from typing import NewType import bcrypt -from app.domain.ports.password_hasher import PasswordHasher -from app.domain.value_objects.raw_password import RawPassword -from app.domain.value_objects.user_password_hash import UserPasswordHash -from app.infrastructure.adapters.types import HasherSemaphore, HasherThreadPoolExecutor -from app.infrastructure.exceptions.password_hasher import PasswordHasherBusyError +from app.core.common.entities.types_ import UserPasswordHash +from app.core.common.ports.password_hasher import PasswordHasher +from app.core.common.value_objects.raw_password import RawPassword +from app.infrastructure.adapters.exceptions import PasswordHasherBusyError -log = logging.getLogger(__name__) +HasherThreadPoolExecutor = NewType("HasherThreadPoolExecutor", ThreadPoolExecutor) +HasherSemaphore = NewType("HasherSemaphore", asyncio.Semaphore) class BcryptPasswordHasher(PasswordHasher): @@ -33,7 +34,6 @@ def __init__( self._semaphore_wait_timeout_s = semaphore_wait_timeout_s async def hash(self, raw_password: RawPassword) -> UserPasswordHash: - """:raises PasswordHasherBusyError:""" async with self._permit(): loop = asyncio.get_running_loop() return await loop.run_in_executor( @@ -47,7 +47,6 @@ async def verify( raw_password: RawPassword, hashed_password: UserPasswordHash, ) -> bool: - """:raises PasswordHasherBusyError:""" async with self._permit(): loop = asyncio.get_running_loop() return await loop.run_in_executor( @@ -59,14 +58,13 @@ async def verify( @asynccontextmanager async def _permit(self) -> AsyncIterator[None]: - """:raises PasswordHasherBusyError:""" try: await asyncio.wait_for( self._semaphore.acquire(), timeout=self._semaphore_wait_timeout_s, ) - except TimeoutError as err: - raise PasswordHasherBusyError from err + except TimeoutError as e: + raise PasswordHasherBusyError from e try: yield finally: @@ -79,17 +77,13 @@ def hash_sync(self, raw_password: RawPassword) -> UserPasswordHash: Work factor: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#introduction """ - log.debug("hash") base64_hmac_peppered = self._add_pepper(raw_password, self._pepper) salt = bcrypt.gensalt(rounds=self._work_factor) return UserPasswordHash(bcrypt.hashpw(base64_hmac_peppered, salt)) - def verify_sync( - self, raw_password: RawPassword, hashed_password: UserPasswordHash - ) -> bool: - log.debug("verify") + def verify_sync(self, raw_password: RawPassword, hashed_password: UserPasswordHash) -> bool: base64_hmac_peppered = self._add_pepper(raw_password, self._pepper) - return bcrypt.checkpw(base64_hmac_peppered, hashed_password.value) + return bcrypt.checkpw(base64_hmac_peppered, hashed_password) @staticmethod def _add_pepper(raw_password: RawPassword, pepper: bytes) -> bytes: diff --git a/src/app/infrastructure/adapters/constants.py b/src/app/infrastructure/adapters/constants.py deleted file mode 100644 index 6f355f22..00000000 --- a/src/app/infrastructure/adapters/constants.py +++ /dev/null @@ -1,8 +0,0 @@ -from typing import Final - -DB_CONSTRAINT_VIOLATION: Final[str] = "Database constraint violation." -DB_COMMIT_DONE: Final[str] = "Commit was done." -DB_COMMIT_FAILED: Final[str] = "Commit failed." -DB_FLUSH_DONE: Final[str] = "Flush was done." -DB_FLUSH_FAILED: Final[str] = "Flush failed." -DB_QUERY_FAILED: Final[str] = "Database query failed." diff --git a/src/app/infrastructure/adapters/exceptions.py b/src/app/infrastructure/adapters/exceptions.py new file mode 100644 index 00000000..d769a1b6 --- /dev/null +++ b/src/app/infrastructure/adapters/exceptions.py @@ -0,0 +1,5 @@ +from app.core.common.exceptions import BaseError + + +class PasswordHasherBusyError(BaseError): + pass diff --git a/src/app/infrastructure/adapters/main_flusher_sqla.py b/src/app/infrastructure/adapters/main_flusher_sqla.py deleted file mode 100644 index 8b997af2..00000000 --- a/src/app/infrastructure/adapters/main_flusher_sqla.py +++ /dev/null @@ -1,43 +0,0 @@ -import logging -from collections.abc import Mapping -from typing import Any, cast - -from sqlalchemy.exc import IntegrityError, SQLAlchemyError - -from app.application.common.ports.flusher import Flusher -from app.domain.exceptions.user import UsernameAlreadyExistsError -from app.infrastructure.adapters.constants import ( - DB_CONSTRAINT_VIOLATION, - DB_FLUSH_DONE, - DB_FLUSH_FAILED, - DB_QUERY_FAILED, -) -from app.infrastructure.adapters.types import MainAsyncSession -from app.infrastructure.exceptions.gateway import DataMapperError - -log = logging.getLogger(__name__) - - -class SqlaMainFlusher(Flusher): - def __init__(self, session: MainAsyncSession) -> None: - self._session = session - - async def flush(self) -> None: - """ - :raises DataMapperError: - :raises UsernameAlreadyExists: - """ - try: - await self._session.flush() - log.debug("%s Main session.", DB_FLUSH_DONE) - - except IntegrityError as err: - if "uq_users_username" in str(err): - params: Mapping[str, Any] = cast(Mapping[str, Any], err.params) - username = str(params.get("username", "unknown")) - raise UsernameAlreadyExistsError(username) from err - - raise DataMapperError(DB_CONSTRAINT_VIOLATION) from err - - except SQLAlchemyError as err: - raise DataMapperError(f"{DB_QUERY_FAILED} {DB_FLUSH_FAILED}") from err diff --git a/src/app/infrastructure/adapters/main_transaction_manager_sqla.py b/src/app/infrastructure/adapters/main_transaction_manager_sqla.py deleted file mode 100644 index 4fd1f7da..00000000 --- a/src/app/infrastructure/adapters/main_transaction_manager_sqla.py +++ /dev/null @@ -1,30 +0,0 @@ -import logging - -from sqlalchemy.exc import SQLAlchemyError - -from app.application.common.ports.transaction_manager import ( - TransactionManager, -) -from app.infrastructure.adapters.constants import ( - DB_COMMIT_DONE, - DB_COMMIT_FAILED, - DB_QUERY_FAILED, -) -from app.infrastructure.adapters.types import MainAsyncSession -from app.infrastructure.exceptions.gateway import DataMapperError - -log = logging.getLogger(__name__) - - -class SqlaMainTransactionManager(TransactionManager): - def __init__(self, session: MainAsyncSession) -> None: - self._session = session - - async def commit(self) -> None: - """:raises DataMapperError:""" - try: - await self._session.commit() - log.debug("%s Main session.", DB_COMMIT_DONE) - - except SQLAlchemyError as err: - raise DataMapperError(f"{DB_QUERY_FAILED} {DB_COMMIT_FAILED}") from err diff --git a/src/app/infrastructure/adapters/sqla_flusher.py b/src/app/infrastructure/adapters/sqla_flusher.py new file mode 100644 index 00000000..743f8fee --- /dev/null +++ b/src/app/infrastructure/adapters/sqla_flusher.py @@ -0,0 +1,43 @@ +import logging +from collections.abc import Mapping +from typing import Final + +from sqlalchemy.exc import IntegrityError, SQLAlchemyError +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.commands.exceptions import UsernameAlreadyExistsError +from app.core.commands.ports.flusher import Flusher +from app.infrastructure.exceptions import StorageError +from app.infrastructure.persistence_sqla import constraint_names as cn + +logger = logging.getLogger(__name__) + +DB_CONSTRAINT_VIOLATION: Final[str] = "Database constraint violation." +DB_FLUSH_DONE: Final[str] = "Flush was done." +DB_FLUSH_FAILED: Final[str] = "Flush failed." + +CONSTRAINT_TO_ERROR: Final[Mapping[str, type[Exception]]] = { + cn.UQ_USERS_USERNAME: UsernameAlreadyExistsError, +} + + +class SqlaFlusher(Flusher): + def __init__(self, session: AsyncSession) -> None: + self._session = session + + async def flush(self) -> None: + try: + await self._session.flush() + logger.debug("%s.", DB_FLUSH_DONE) + + except IntegrityError as e: + msg = str(e) + for name, exc_type in CONSTRAINT_TO_ERROR.items(): + if name in msg: + raise exc_type from e + + logger.warning("Unhandled integrity error: %s", msg) + raise StorageError(DB_CONSTRAINT_VIOLATION) from e + + except SQLAlchemyError as e: + raise StorageError(DB_FLUSH_FAILED) from e diff --git a/src/app/infrastructure/adapters/sqla_transaction_manager.py b/src/app/infrastructure/adapters/sqla_transaction_manager.py new file mode 100644 index 00000000..f84273d4 --- /dev/null +++ b/src/app/infrastructure/adapters/sqla_transaction_manager.py @@ -0,0 +1,26 @@ +import logging +from typing import Final + +from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.commands.ports.transaction_manager import TransactionManager +from app.infrastructure.exceptions import StorageError + +DB_COMMIT_DONE: Final[str] = "Commit was done." +DB_COMMIT_FAILED: Final[str] = "Commit failed." + +logger = logging.getLogger(__name__) + + +class SqlaTransactionManager(TransactionManager): + def __init__(self, session: AsyncSession) -> None: + self._session = session + + async def commit(self) -> None: + try: + await self._session.commit() + logger.debug("%s.", DB_COMMIT_DONE) + + except SQLAlchemyError as e: + raise StorageError(DB_COMMIT_FAILED) from e diff --git a/src/app/infrastructure/adapters/sqla_user_reader.py b/src/app/infrastructure/adapters/sqla_user_reader.py new file mode 100644 index 00000000..e41eab8c --- /dev/null +++ b/src/app/infrastructure/adapters/sqla_user_reader.py @@ -0,0 +1,76 @@ +from sqlalchemy import func, select +from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.queries.models.user import UserQm +from app.core.queries.ports.user_reader import ListUsersQm, UserReader +from app.core.queries.query_support.exceptions import SortingError +from app.core.queries.query_support.offset_pagination import OffsetPaginationParams +from app.core.queries.query_support.sorting import SortingOrder, SortingParams +from app.infrastructure.exceptions import ReaderError +from app.infrastructure.persistence_sqla.mappings.user import users_table + + +class SqlaUserReader(UserReader): + def __init__(self, session: AsyncSession) -> None: + self._session = session + + async def list_users( + self, + *, + pagination: OffsetPaginationParams, + sorting: SortingParams, + ) -> ListUsersQm: + sorting_column = users_table.c.get(sorting.field) + if sorting_column is None: + raise SortingError(f"Invalid sorting field: '{sorting.field}'") + order_by_expression = sorting_column.asc() if sorting.order == SortingOrder.ASC else sorting_column.desc() + secondary_order_by = users_table.c.id.asc() if sorting.order == SortingOrder.ASC else users_table.c.id.desc() + stmt = ( + select( + users_table.c.id, + users_table.c.username, + users_table.c.role, + users_table.c.is_active, + users_table.c.created_at, + users_table.c.updated_at, + func.count().over().label("total"), + ) + .order_by(order_by_expression, secondary_order_by) + .limit(pagination.limit) + .offset(pagination.offset) + ) + try: + result = await self._session.execute(stmt) + rows = result.all() + except SQLAlchemyError as e: + raise ReaderError from e + if not rows: + total_stmt = select(func.count()).select_from(users_table) + try: + total = int(await self._session.scalar(total_stmt) or 0) + except SQLAlchemyError as e: + raise ReaderError from e + return ListUsersQm( + users=[], + total=total, + limit=pagination.limit, + offset=pagination.offset, + ) + users = [ + UserQm( + id=row.id, + username=row.username, + role=row.role, + is_active=row.is_active, + created_at=row.created_at, + updated_at=row.updated_at, + ) + for row in rows + ] + return ListUsersQm( + users=users, + total=rows[0].total, + limit=pagination.limit, + offset=pagination.offset, + ) diff --git a/src/app/infrastructure/adapters/sqla_user_tx_storage.py b/src/app/infrastructure/adapters/sqla_user_tx_storage.py new file mode 100644 index 00000000..a1fbf865 --- /dev/null +++ b/src/app/infrastructure/adapters/sqla_user_tx_storage.py @@ -0,0 +1,34 @@ +from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.commands.ports.user_tx_storage import UserTxStorage +from app.core.common.authorization.ports import AuthzUserFinder +from app.core.common.entities.types_ import UserId +from app.core.common.entities.user import User +from app.infrastructure.exceptions import StorageError + + +class SqlaUserTxStorage(UserTxStorage, AuthzUserFinder): + def __init__(self, session: AsyncSession) -> None: + self._session = session + + def add(self, user: User) -> None: + try: + self._session.add(user) + except SQLAlchemyError as e: + raise StorageError from e + + async def get_by_id( + self, + user_id: UserId, + *, + for_update: bool = False, + ) -> User | None: + try: + return await self._session.get( + User, + user_id, + with_for_update=for_update, + ) + except SQLAlchemyError as e: + raise StorageError from e diff --git a/src/app/infrastructure/adapters/system_utc_timer.py b/src/app/infrastructure/adapters/system_utc_timer.py new file mode 100644 index 00000000..e296c3af --- /dev/null +++ b/src/app/infrastructure/adapters/system_utc_timer.py @@ -0,0 +1,10 @@ +from datetime import UTC, datetime + +from app.core.commands.ports.utc_timer import UtcTimer +from app.core.common.value_objects.utc_datetime import UtcDatetime + + +class SystemUtcTimer(UtcTimer): + @property + def now(self) -> UtcDatetime: + return UtcDatetime(datetime.now(UTC)) diff --git a/src/app/infrastructure/adapters/types.py b/src/app/infrastructure/adapters/types.py deleted file mode 100644 index 46489e2a..00000000 --- a/src/app/infrastructure/adapters/types.py +++ /dev/null @@ -1,9 +0,0 @@ -import asyncio -from concurrent.futures import ThreadPoolExecutor -from typing import NewType - -from sqlalchemy.ext.asyncio import AsyncSession - -MainAsyncSession = NewType("MainAsyncSession", AsyncSession) -HasherThreadPoolExecutor = NewType("HasherThreadPoolExecutor", ThreadPoolExecutor) -HasherSemaphore = NewType("HasherSemaphore", asyncio.Semaphore) diff --git a/src/app/infrastructure/adapters/user_data_mapper_sqla.py b/src/app/infrastructure/adapters/user_data_mapper_sqla.py deleted file mode 100644 index 2153f2e9..00000000 --- a/src/app/infrastructure/adapters/user_data_mapper_sqla.py +++ /dev/null @@ -1,54 +0,0 @@ -from sqlalchemy import select -from sqlalchemy.exc import SQLAlchemyError - -from app.application.common.ports.user_command_gateway import UserCommandGateway -from app.domain.entities.user import User -from app.domain.value_objects.user_id import UserId -from app.domain.value_objects.username import Username -from app.infrastructure.adapters.constants import DB_QUERY_FAILED -from app.infrastructure.adapters.types import MainAsyncSession -from app.infrastructure.exceptions.gateway import DataMapperError - - -class SqlaUserDataMapper(UserCommandGateway): - def __init__(self, session: MainAsyncSession) -> None: - self._session = session - - def add(self, user: User) -> None: - """:raises DataMapperError:""" - try: - self._session.add(user) - except SQLAlchemyError as err: - raise DataMapperError(DB_QUERY_FAILED) from err - - async def read_by_id( - self, - user_id: UserId, - for_update: bool = False, - ) -> User | None: - """:raises DataMapperError:""" - stmt = select(User).where(User.id_ == user_id) # type: ignore - - if for_update: - stmt = stmt.with_for_update() - - try: - return (await self._session.execute(stmt)).scalar_one_or_none() - except SQLAlchemyError as err: - raise DataMapperError(DB_QUERY_FAILED) from err - - async def read_by_username( - self, - username: Username, - for_update: bool = False, - ) -> User | None: - """:raises DataMapperError:""" - stmt = select(User).where(User.username == username) # type: ignore - - if for_update: - stmt = stmt.with_for_update() - - try: - return (await self._session.execute(stmt)).scalar_one_or_none() - except SQLAlchemyError as err: - raise DataMapperError(DB_QUERY_FAILED) from err diff --git a/src/app/infrastructure/adapters/user_id_generator_uuid.py b/src/app/infrastructure/adapters/user_id_generator_uuid.py deleted file mode 100644 index 3ce9341c..00000000 --- a/src/app/infrastructure/adapters/user_id_generator_uuid.py +++ /dev/null @@ -1,9 +0,0 @@ -import uuid_utils.compat as uuid_utils - -from app.domain.ports.user_id_generator import UserIdGenerator -from app.domain.value_objects.user_id import UserId - - -class UuidUserIdGenerator(UserIdGenerator): - def generate(self) -> UserId: - return UserId(uuid_utils.uuid7()) diff --git a/src/app/infrastructure/adapters/user_reader_sqla.py b/src/app/infrastructure/adapters/user_reader_sqla.py deleted file mode 100644 index 5d046629..00000000 --- a/src/app/infrastructure/adapters/user_reader_sqla.py +++ /dev/null @@ -1,77 +0,0 @@ -import logging - -from sqlalchemy import func, select -from sqlalchemy.exc import SQLAlchemyError - -from app.application.common.exceptions.query import SortingError -from app.application.common.ports.user_query_gateway import ( - ListUsersQM, - UserQueryGateway, - UserQueryModel, -) -from app.application.common.query_params.offset_pagination import OffsetPaginationParams -from app.application.common.query_params.sorting import SortingOrder, SortingParams -from app.infrastructure.adapters.constants import DB_QUERY_FAILED -from app.infrastructure.adapters.types import MainAsyncSession -from app.infrastructure.exceptions.gateway import ReaderError -from app.infrastructure.persistence_sqla.mappings.user import users_table - -log = logging.getLogger(__name__) - - -class SqlaUserReader(UserQueryGateway): - def __init__(self, session: MainAsyncSession) -> None: - self._session = session - - async def read_all( - self, - pagination: OffsetPaginationParams, - sorting: SortingParams, - ) -> ListUsersQM: - """ - :raises SortingError: - :raises ReaderError: - """ - sorting_col = users_table.c.get(sorting.field) - if sorting_col is None: - raise SortingError(f"Invalid sorting field: '{sorting.field}'") - - order_by = ( - sorting_col.asc() - if sorting.order == SortingOrder.ASC - else sorting_col.desc() - ) - - stmt = ( - select( - users_table.c.id, - users_table.c.username, - users_table.c.role, - users_table.c.is_active, - func.count().over().label("total"), - ) - .order_by(order_by) - .limit(pagination.limit) - .offset(pagination.offset) - ) - - try: - result = await self._session.execute(stmt) - rows = result.all() - except SQLAlchemyError as err: - raise ReaderError(DB_QUERY_FAILED) from err - - if not rows: - return ListUsersQM(users=[], total=0) - - users = [ - UserQueryModel( - id_=row.id, - username=row.username, - role=row.role, - is_active=row.is_active, - ) - for row in rows - ] - total = rows[0].total - return ListUsersQM(users=users, total=total) diff --git a/src/app/infrastructure/auth/adapters/access_revoker.py b/src/app/infrastructure/auth/adapters/access_revoker.py deleted file mode 100644 index 89581ef1..00000000 --- a/src/app/infrastructure/auth/adapters/access_revoker.py +++ /dev/null @@ -1,12 +0,0 @@ -from app.application.common.ports.access_revoker import AccessRevoker -from app.domain.value_objects.user_id import UserId -from app.infrastructure.auth.session.service import AuthSessionService - - -class AuthSessionAccessRevoker(AccessRevoker): - def __init__(self, auth_session_service: AuthSessionService) -> None: - self._auth_session_service = auth_session_service - - async def remove_all_user_access(self, user_id: UserId) -> None: - """:raises DataMapperError:""" - await self._auth_session_service.terminate_all_sessions_for_user(user_id) diff --git a/src/app/infrastructure/auth/adapters/data_mapper_sqla.py b/src/app/infrastructure/auth/adapters/data_mapper_sqla.py deleted file mode 100644 index 8814bb03..00000000 --- a/src/app/infrastructure/auth/adapters/data_mapper_sqla.py +++ /dev/null @@ -1,61 +0,0 @@ -from sqlalchemy import delete -from sqlalchemy.exc import SQLAlchemyError - -from app.domain.value_objects.user_id import UserId -from app.infrastructure.adapters.constants import DB_QUERY_FAILED -from app.infrastructure.auth.adapters.types import AuthAsyncSession -from app.infrastructure.auth.session.model import AuthSession -from app.infrastructure.auth.session.ports.gateway import ( - AuthSessionGateway, -) -from app.infrastructure.exceptions.gateway import DataMapperError - - -class SqlaAuthSessionDataMapper(AuthSessionGateway): - def __init__(self, session: AuthAsyncSession) -> None: - self._session = session - - def add(self, auth_session: AuthSession) -> None: - """:raises DataMapperError:""" - try: - self._session.add(auth_session) - except SQLAlchemyError as err: - raise DataMapperError(DB_QUERY_FAILED) from err - - async def read_by_id( - self, - auth_session_id: str, - for_update: bool = False, - ) -> AuthSession | None: - """:raises DataMapperError:""" - try: - return await self._session.get( - AuthSession, - auth_session_id, - with_for_update=for_update, - ) - except SQLAlchemyError as err: - raise DataMapperError(DB_QUERY_FAILED) from err - - async def update(self, auth_session: AuthSession) -> None: - """:raises DataMapperError:""" - try: - await self._session.merge(auth_session) - except SQLAlchemyError as err: - raise DataMapperError(DB_QUERY_FAILED) from err - - async def delete(self, auth_session_id: str) -> None: - """:raises DataMapperError:""" - stmt = delete(AuthSession).where(AuthSession.id_ == auth_session_id) # type: ignore - try: - await self._session.execute(stmt) - except SQLAlchemyError as err: - raise DataMapperError(DB_QUERY_FAILED) from err - - async def delete_all_for_user(self, user_id: UserId) -> None: - """:raises DataMapperError:""" - stmt = delete(AuthSession).where(AuthSession.user_id == user_id) # type: ignore - try: - await self._session.execute(stmt) - except SQLAlchemyError as err: - raise DataMapperError(DB_QUERY_FAILED) from err diff --git a/src/app/infrastructure/auth/adapters/identity_provider.py b/src/app/infrastructure/auth/adapters/identity_provider.py deleted file mode 100644 index a44e60a0..00000000 --- a/src/app/infrastructure/auth/adapters/identity_provider.py +++ /dev/null @@ -1,12 +0,0 @@ -from app.application.common.ports.identity_provider import IdentityProvider -from app.domain.value_objects.user_id import UserId -from app.infrastructure.auth.session.service import AuthSessionService - - -class AuthSessionIdentityProvider(IdentityProvider): - def __init__(self, auth_session_service: AuthSessionService) -> None: - self._auth_session_service = auth_session_service - - async def get_current_user_id(self) -> UserId: - """:raises AuthenticationError:""" - return await self._auth_session_service.get_authenticated_user_id() diff --git a/src/app/infrastructure/auth/adapters/transaction_manager_sqla.py b/src/app/infrastructure/auth/adapters/transaction_manager_sqla.py deleted file mode 100644 index 2e2c7d2e..00000000 --- a/src/app/infrastructure/auth/adapters/transaction_manager_sqla.py +++ /dev/null @@ -1,30 +0,0 @@ -import logging - -from sqlalchemy.exc import SQLAlchemyError - -from app.infrastructure.adapters.constants import ( - DB_COMMIT_DONE, - DB_COMMIT_FAILED, - DB_QUERY_FAILED, -) -from app.infrastructure.auth.adapters.types import AuthAsyncSession -from app.infrastructure.auth.session.ports.transaction_manager import ( - AuthSessionTransactionManager, -) -from app.infrastructure.exceptions.gateway import DataMapperError - -log = logging.getLogger(__name__) - - -class SqlaAuthSessionTransactionManager(AuthSessionTransactionManager): - def __init__(self, session: AuthAsyncSession) -> None: - self._session = session - - async def commit(self) -> None: - """:raises DataMapperError:""" - try: - await self._session.commit() - log.debug("%s Auth session.", DB_COMMIT_DONE) - - except SQLAlchemyError as err: - raise DataMapperError(f"{DB_QUERY_FAILED} {DB_COMMIT_FAILED}") from err diff --git a/src/app/infrastructure/auth/exceptions.py b/src/app/infrastructure/auth/exceptions.py deleted file mode 100644 index 82176ce5..00000000 --- a/src/app/infrastructure/auth/exceptions.py +++ /dev/null @@ -1,17 +0,0 @@ -from app.infrastructure.exceptions.base import InfrastructureError - - -class AuthenticationError(InfrastructureError): - pass - - -class AlreadyAuthenticatedError(InfrastructureError): - pass - - -class ReAuthenticationError(InfrastructureError): - pass - - -class AuthenticationChangeError(InfrastructureError): - pass diff --git a/src/app/infrastructure/auth/handlers/change_password.py b/src/app/infrastructure/auth/handlers/change_password.py deleted file mode 100644 index cdc40d29..00000000 --- a/src/app/infrastructure/auth/handlers/change_password.py +++ /dev/null @@ -1,75 +0,0 @@ -import logging -from dataclasses import dataclass - -from app.application.common.ports.transaction_manager import ( - TransactionManager, -) -from app.application.common.services.current_user import CurrentUserService -from app.domain.services.user import UserService -from app.domain.value_objects.raw_password import RawPassword -from app.infrastructure.auth.exceptions import ( - AuthenticationChangeError, - ReAuthenticationError, -) -from app.infrastructure.auth.handlers.constants import ( - AUTH_PASSWORD_INVALID, - AUTH_PASSWORD_NEW_SAME_AS_CURRENT, -) - -log = logging.getLogger(__name__) - - -@dataclass(frozen=True, slots=True, kw_only=True) -class ChangePasswordRequest: - current_password: str - new_password: str - - -class ChangePasswordHandler: - """ - - Open to authenticated users. - - The current user can change their password. - - New password must differ from current password. - """ - - def __init__( - self, - current_user_service: CurrentUserService, - user_service: UserService, - transaction_manager: TransactionManager, - ) -> None: - self._current_user_service = current_user_service - self._user_service = user_service - self._transaction_manager = transaction_manager - - async def execute(self, request_data: ChangePasswordRequest) -> None: - """ - :raises AuthenticationError: - :raises DataMapperError: - :raises AuthorizationError: - :raises DomainTypeError: - :raises AuthenticationChangeError: - :raises ReAuthenticationError: - :raises PasswordHasherBusyError: - """ - log.info("Change password: started.") - - current_user = await self._current_user_service.get_current_user( - for_update=True - ) - - current_password = RawPassword(request_data.current_password) - new_password = RawPassword(request_data.new_password) - if current_password == new_password: - raise AuthenticationChangeError(AUTH_PASSWORD_NEW_SAME_AS_CURRENT) - - if not await self._user_service.is_password_valid( - current_user, - current_password, - ): - raise ReAuthenticationError(AUTH_PASSWORD_INVALID) - - await self._user_service.change_password(current_user, new_password) - await self._transaction_manager.commit() - - log.info("Change password: done. User ID: '%s'.", current_user.id_.value) diff --git a/src/app/infrastructure/auth/handlers/constants.py b/src/app/infrastructure/auth/handlers/constants.py deleted file mode 100644 index 6a422343..00000000 --- a/src/app/infrastructure/auth/handlers/constants.py +++ /dev/null @@ -1,10 +0,0 @@ -from typing import Final - -AUTH_ACCOUNT_INACTIVE: Final[str] = "Your account is inactive. Please contact support." -AUTH_ALREADY_AUTHENTICATED: Final[str] = ( - "You are already authenticated. Consider logging out." -) -AUTH_PASSWORD_INVALID: Final[str] = "Invalid password." -AUTH_PASSWORD_NEW_SAME_AS_CURRENT: Final[str] = ( - "New password must differ from current password." -) diff --git a/src/app/infrastructure/auth/handlers/log_in.py b/src/app/infrastructure/auth/handlers/log_in.py deleted file mode 100644 index 281fbb0a..00000000 --- a/src/app/infrastructure/auth/handlers/log_in.py +++ /dev/null @@ -1,95 +0,0 @@ -import logging -from dataclasses import dataclass - -from app.application.common.ports.user_command_gateway import UserCommandGateway -from app.application.common.services.current_user import CurrentUserService -from app.domain.entities.user import User -from app.domain.exceptions.user import UserNotFoundByUsernameError -from app.domain.services.user import UserService -from app.domain.value_objects.raw_password import RawPassword -from app.domain.value_objects.username import Username -from app.infrastructure.auth.exceptions import ( - AlreadyAuthenticatedError, - AuthenticationError, -) -from app.infrastructure.auth.handlers.constants import ( - AUTH_ACCOUNT_INACTIVE, - AUTH_ALREADY_AUTHENTICATED, - AUTH_PASSWORD_INVALID, -) -from app.infrastructure.auth.session.service import AuthSessionService - -log = logging.getLogger(__name__) - - -@dataclass(frozen=True, slots=True, kw_only=True) -class LogInRequest: - username: str - password: str - - -class LogInHandler: - """ - - Open to everyone. - - Authenticates registered user, - sets a JWT access token with a session ID in cookies, - and creates a session. - - A logged-in user cannot log in again - until the session expires or is terminated. - - Authentication renews automatically - when accessing protected routes before expiration. - - If the JWT is invalid, expired, or the session is terminated, - the user loses authentication. - """ - - def __init__( - self, - current_user_service: CurrentUserService, - user_command_gateway: UserCommandGateway, - user_service: UserService, - auth_session_service: AuthSessionService, - ) -> None: - self._current_user_service = current_user_service - self._user_command_gateway = user_command_gateway - self._user_service = user_service - self._auth_session_service = auth_session_service - - async def execute(self, request_data: LogInRequest) -> None: - """ - :raises AlreadyAuthenticatedError: - :raises AuthorizationError: - :raises DataMapperError: - :raises DomainTypeError: - :raises UserNotFoundByUsernameError: - :raises PasswordHasherBusyError: - :raises AuthenticationError: - """ - log.info("Log in: started. Username: '%s'.", request_data.username) - - try: - await self._current_user_service.get_current_user() - raise AlreadyAuthenticatedError(AUTH_ALREADY_AUTHENTICATED) - except AuthenticationError: - pass - - username = Username(request_data.username) - password = RawPassword(request_data.password) - - user: User | None = await self._user_command_gateway.read_by_username(username) - if user is None: - raise UserNotFoundByUsernameError(username) - - if not await self._user_service.is_password_valid(user, password): - raise AuthenticationError(AUTH_PASSWORD_INVALID) - - if not user.is_active: - raise AuthenticationError(AUTH_ACCOUNT_INACTIVE) - - await self._auth_session_service.issue_session(user.id_) - - log.info( - "Log in: done. User, ID: '%s', username '%s', role '%s'.", - user.id_.value, - user.username.value, - user.role.value, - ) diff --git a/src/app/infrastructure/auth/handlers/log_out.py b/src/app/infrastructure/auth/handlers/log_out.py deleted file mode 100644 index b70d6e49..00000000 --- a/src/app/infrastructure/auth/handlers/log_out.py +++ /dev/null @@ -1,38 +0,0 @@ -import logging - -from app.application.common.services.current_user import CurrentUserService -from app.infrastructure.auth.session.service import AuthSessionService - -log = logging.getLogger(__name__) - - -class LogOutHandler: - """ - - Open to authenticated users. - - Logs the user out by deleting the JWT access token from cookies - and removing the session from the database. - """ - - def __init__( - self, - current_user_service: CurrentUserService, - auth_session_service: AuthSessionService, - ) -> None: - self._current_user_service = current_user_service - self._auth_session_service = auth_session_service - - async def execute(self) -> None: - """ - :raises AuthenticationError: - :raises DataMapperError: - :raises AuthorizationError: - """ - log.info("Log out: started for unknown user.") - - current_user = await self._current_user_service.get_current_user() - - log.info("Log out: user identified. User ID: '%s'.", current_user.id_) - - await self._auth_session_service.terminate_current_session() - - log.info("Log out: done. User ID: '%s'.", current_user.id_) diff --git a/src/app/infrastructure/auth/handlers/sign_up.py b/src/app/infrastructure/auth/handlers/sign_up.py deleted file mode 100644 index f58da98f..00000000 --- a/src/app/infrastructure/auth/handlers/sign_up.py +++ /dev/null @@ -1,90 +0,0 @@ -import logging -from dataclasses import dataclass -from typing import TypedDict -from uuid import UUID - -from app.application.common.ports.flusher import Flusher -from app.application.common.ports.transaction_manager import TransactionManager -from app.application.common.ports.user_command_gateway import UserCommandGateway -from app.application.common.services.current_user import CurrentUserService -from app.domain.exceptions.user import UsernameAlreadyExistsError -from app.domain.services.user import UserService -from app.domain.value_objects.raw_password import RawPassword -from app.domain.value_objects.username import Username -from app.infrastructure.auth.exceptions import ( - AlreadyAuthenticatedError, - AuthenticationError, -) -from app.infrastructure.auth.handlers.constants import ( - AUTH_ALREADY_AUTHENTICATED, -) - -log = logging.getLogger(__name__) - - -@dataclass(frozen=True, slots=True, kw_only=True) -class SignUpRequest: - username: str - password: str - - -class SignUpResponse(TypedDict): - id: UUID - - -class SignUpHandler: - """ - - Open to everyone. - - Registers a new user with validation and uniqueness checks. - - Passwords are peppered, salted, and stored as hashes. - - A logged-in user cannot sign up until the session expires or is terminated. - """ - - def __init__( - self, - current_user_service: CurrentUserService, - user_service: UserService, - user_command_gateway: UserCommandGateway, - flusher: Flusher, - transaction_manager: TransactionManager, - ) -> None: - self._current_user_service = current_user_service - self._user_service = user_service - self._user_command_gateway = user_command_gateway - self._flusher = flusher - self._transaction_manager = transaction_manager - - async def execute(self, request_data: SignUpRequest) -> SignUpResponse: - """ - :raises AlreadyAuthenticatedError: - :raises AuthorizationError: - :raises DataMapperError: - :raises DomainTypeError: - :raises PasswordHasherBusyError: - :raises RoleAssignmentNotPermittedError: - :raises UsernameAlreadyExistsError: - """ - log.info("Sign up: started. Username: '%s'.", request_data.username) - - try: - await self._current_user_service.get_current_user() - raise AlreadyAuthenticatedError(AUTH_ALREADY_AUTHENTICATED) - except AuthenticationError: - pass - - username = Username(request_data.username) - password = RawPassword(request_data.password) - - user = await self._user_service.create_user(username, password) - - self._user_command_gateway.add(user) - - try: - await self._flusher.flush() - except UsernameAlreadyExistsError: - raise - - await self._transaction_manager.commit() - - log.info("Sign up: done. Username: '%s'.", user.username.value) - return SignUpResponse(id=user.id_.value) diff --git a/src/app/infrastructure/auth/session/id_generator_str.py b/src/app/infrastructure/auth/session/id_generator_str.py deleted file mode 100644 index 2f16011f..00000000 --- a/src/app/infrastructure/auth/session/id_generator_str.py +++ /dev/null @@ -1,6 +0,0 @@ -import secrets - - -class StrAuthSessionIdGenerator: - def generate(self) -> str: - return secrets.token_urlsafe(32) diff --git a/src/app/infrastructure/auth/session/ports/gateway.py b/src/app/infrastructure/auth/session/ports/gateway.py deleted file mode 100644 index fe78577a..00000000 --- a/src/app/infrastructure/auth/session/ports/gateway.py +++ /dev/null @@ -1,32 +0,0 @@ -from abc import abstractmethod -from typing import Protocol - -from app.domain.value_objects.user_id import UserId -from app.infrastructure.auth.session.model import AuthSession - - -class AuthSessionGateway(Protocol): - """ - Defined to allow easier mocking and swapping - of implementations in the same layer. - """ - - @abstractmethod - def add(self, auth_session: AuthSession) -> None: - """:raises DataMapperError:""" - - @abstractmethod - async def read_by_id(self, auth_session_id: str) -> AuthSession | None: - """:raises DataMapperError:""" - - @abstractmethod - async def update(self, auth_session: AuthSession) -> None: - """:raises DataMapperError:""" - - @abstractmethod - async def delete(self, auth_session_id: str) -> None: - """:raises DataMapperError:""" - - @abstractmethod - async def delete_all_for_user(self, user_id: UserId) -> None: - """:raises DataMapperError:""" diff --git a/src/app/infrastructure/auth/session/ports/transaction_manager.py b/src/app/infrastructure/auth/session/ports/transaction_manager.py deleted file mode 100644 index 3ed24564..00000000 --- a/src/app/infrastructure/auth/session/ports/transaction_manager.py +++ /dev/null @@ -1,21 +0,0 @@ -from abc import abstractmethod -from typing import Protocol - - -class AuthSessionTransactionManager(Protocol): - """ - Defined to allow easier mocking and swapping - of implementations in the same layer. - - UoW-compatible interface for committing a business transaction. - May be extended with rollback support. - The implementation may be an ORM session, such as SQLAlchemy's. - """ - - @abstractmethod - async def commit(self) -> None: - """ - :raises DataMapperError: - - Commit the successful outcome of a business transaction. - """ diff --git a/src/app/infrastructure/auth/session/ports/transport.py b/src/app/infrastructure/auth/session/ports/transport.py deleted file mode 100644 index 8b9350cb..00000000 --- a/src/app/infrastructure/auth/session/ports/transport.py +++ /dev/null @@ -1,15 +0,0 @@ -from abc import abstractmethod -from typing import Protocol - -from app.infrastructure.auth.session.model import AuthSession - - -class AuthSessionTransport(Protocol): - @abstractmethod - def deliver(self, auth_session: AuthSession) -> None: ... - - @abstractmethod - def extract_id(self) -> str | None: ... - - @abstractmethod - def remove_current(self) -> None: ... diff --git a/src/app/infrastructure/auth/session/service.py b/src/app/infrastructure/auth/session/service.py deleted file mode 100644 index 8cf7e28b..00000000 --- a/src/app/infrastructure/auth/session/service.py +++ /dev/null @@ -1,242 +0,0 @@ -import logging -from datetime import datetime -from typing import Final - -from app.domain.value_objects.user_id import UserId -from app.infrastructure.auth.exceptions import AuthenticationError -from app.infrastructure.auth.session.id_generator_str import ( - StrAuthSessionIdGenerator, -) -from app.infrastructure.auth.session.model import AuthSession -from app.infrastructure.auth.session.ports.gateway import ( - AuthSessionGateway, -) -from app.infrastructure.auth.session.ports.transaction_manager import ( - AuthSessionTransactionManager, -) -from app.infrastructure.auth.session.ports.transport import AuthSessionTransport -from app.infrastructure.auth.session.timer_utc import UtcAuthSessionTimer -from app.infrastructure.exceptions.gateway import DataMapperError - -log = logging.getLogger(__name__) - -AUTH_UNAVAILABLE: Final[str] = ( - "Authentication is currently unavailable. Please try again later." -) -AUTH_NOT_AUTHENTICATED: Final[str] = "Not authenticated." -AUTH_SESSION_EXPIRED: Final[str] = "Auth session expired." -AUTH_SESSION_EXTENSION_FAILED: Final[str] = "Auth session extension failed." -AUTH_SESSION_EXTRACTION_FAILED: Final[str] = "Auth session extraction failed." -AUTH_SESSION_NOT_FOUND: Final[str] = "Auth session not found." - - -class AuthSessionService: - def __init__( - self, - auth_session_gateway: AuthSessionGateway, - auth_session_transport: AuthSessionTransport, - auth_transaction_manager: AuthSessionTransactionManager, - auth_session_id_generator: StrAuthSessionIdGenerator, - auth_session_timer: UtcAuthSessionTimer, - ) -> None: - self._auth_session_gateway = auth_session_gateway - self._auth_session_transport = auth_session_transport - self._auth_transaction_manager = auth_transaction_manager - self._auth_session_id_generator = auth_session_id_generator - self._auth_session_timer = auth_session_timer - self._cached_auth_session: AuthSession | None = None - - async def issue_session(self, user_id: UserId) -> None: - """:raises AuthenticationError:""" - log.debug("Issue auth session: started. User ID: '%s'.", user_id.value) - - auth_session_id: str = self._auth_session_id_generator.generate() - expiration: datetime = self._auth_session_timer.auth_session_expiration - auth_session = AuthSession( - id_=auth_session_id, - user_id=user_id, - expiration=expiration, - ) - - try: - self._auth_session_gateway.add(auth_session) - await self._auth_transaction_manager.commit() - - except DataMapperError as err: - raise AuthenticationError(AUTH_UNAVAILABLE) from err - - self._auth_session_transport.deliver(auth_session) - - log.debug( - "Issue auth session: done. User ID: '%s', Auth session ID: '%s'.", - user_id.value, - auth_session.id_, - ) - - async def get_authenticated_user_id(self) -> UserId: - """:raises AuthenticationError:""" - log.debug("Get authenticated user ID: started.") - - raw_auth_session = await self._get_current_auth_session() - valid_auth_session = await self._validate_and_extend_session(raw_auth_session) - self._cached_auth_session = valid_auth_session - - log.debug( - "Get authenticated user ID: done. Auth session ID: '%s'. User ID: '%s'.", - valid_auth_session.id_, - valid_auth_session.user_id.value, - ) - return valid_auth_session.user_id - - async def terminate_current_session(self) -> None: - log.debug("Terminate current session: started. Auth session ID: unknown.") - - auth_session_id: str | None - if self._cached_auth_session is not None: - auth_session_id = self._cached_auth_session.id_ - log.debug( - "Terminate current session: using ID from cache. " - "Auth session ID: '%s'.", - auth_session_id, - ) - else: - auth_session_id = self._auth_session_transport.extract_id() - if auth_session_id is None: - log.warning( - "Terminate current session failed: partially failed. " - "Session ID can't be extracted from transport. " - "Auth session can't be identified.", - ) - return - log.debug( - "Terminate current session: using ID from transport. " - "Auth session ID: '%s'.", - auth_session_id, - ) - - self._auth_session_transport.remove_current() - - try: - await self._auth_session_gateway.delete(auth_session_id) - await self._auth_transaction_manager.commit() - log.debug( - "Terminate current session: done (transport cleared, storage deleted). " - "Auth session ID: '%s'.", - auth_session_id, - ) - - except DataMapperError: - log.warning( - "Terminate current session: partially failed " - "(transport cleared, storage delete failed). " - "Auth session ID: '%s'.", - auth_session_id, - ) - - self._cached_auth_session = None - - async def terminate_all_sessions_for_user(self, user_id: UserId) -> None: - """:raises DataMapperError:""" - log.debug( - "Terminate all sessions for user: started. User ID: '%s'.", - user_id.value, - ) - - await self._auth_session_gateway.delete_all_for_user(user_id) - await self._auth_transaction_manager.commit() - - if self._cached_auth_session and self._cached_auth_session.user_id == user_id: - self._auth_session_transport.remove_current() - self._cached_auth_session = None - - log.debug( - "Terminate all sessions for user: done. User ID: '%s'.", - user_id.value, - ) - - async def _get_current_auth_session(self) -> AuthSession: - """:raises AuthenticationError:""" - log.debug("Get current auth session: started. Auth session ID: unknown.") - - if self._cached_auth_session is not None: - log.debug( - "Get current auth session: done (from cache). Auth session ID: '%s'.", - self._cached_auth_session.id_, - ) - return self._cached_auth_session - - auth_session_id: str | None = self._auth_session_transport.extract_id() - if auth_session_id is None: - log.debug(AUTH_SESSION_NOT_FOUND) - raise AuthenticationError(AUTH_NOT_AUTHENTICATED) - - log.debug( - "Get current auth session: reading from storage. Auth session ID: '%s'.", - auth_session_id, - ) - - try: - auth_session: ( - AuthSession | None - ) = await self._auth_session_gateway.read_by_id(auth_session_id) - - except DataMapperError as err: - log.error("%s: '%s'", AUTH_SESSION_EXTRACTION_FAILED, err) - raise AuthenticationError(AUTH_NOT_AUTHENTICATED) from err - - if auth_session is None: - log.debug(AUTH_SESSION_NOT_FOUND) - raise AuthenticationError(AUTH_NOT_AUTHENTICATED) - - log.debug( - "Get current auth session: done. Auth session ID: '%s'.", auth_session.id_ - ) - return auth_session - - async def _validate_and_extend_session( - self, - auth_session: AuthSession, - ) -> AuthSession: - """:raises AuthenticationError:""" - log.debug( - "Validate and extend auth session: started. Auth session ID: '%s'.", - auth_session.id_, - ) - - now = self._auth_session_timer.current_time - if auth_session.expiration <= now: - log.debug(AUTH_SESSION_EXPIRED) - raise AuthenticationError(AUTH_NOT_AUTHENTICATED) - - if ( - auth_session.expiration - now - > self._auth_session_timer.refresh_trigger_interval - ): - log.debug( - "Validate and extend auth session: validated without extension. " - "Auth session ID: '%s'.", - auth_session.id_, - ) - return auth_session - - original_expiration = auth_session.expiration - auth_session.expiration = self._auth_session_timer.auth_session_expiration - - try: - await self._auth_session_gateway.update(auth_session) - await self._auth_transaction_manager.commit() - - except DataMapperError as err: - log.error("%s: '%s'", AUTH_SESSION_EXTENSION_FAILED, err) - auth_session.expiration = original_expiration - return auth_session - - self._auth_session_transport.deliver(auth_session) - - log.debug( - "Validate and extend auth session: done. " - "Auth session ID: '%s'. New expiration: '%s'.", - auth_session.id_, - auth_session.expiration.isoformat(), - ) - return auth_session diff --git a/src/app/infrastructure/auth/session/timer_utc.py b/src/app/infrastructure/auth/session/timer_utc.py deleted file mode 100644 index 439fd89d..00000000 --- a/src/app/infrastructure/auth/session/timer_utc.py +++ /dev/null @@ -1,19 +0,0 @@ -from datetime import UTC, datetime, timedelta - - -class UtcAuthSessionTimer: - def __init__(self, ttl_min: timedelta, refresh_threshold: float) -> None: - self._ttl_min = ttl_min - self._refresh_threshold = refresh_threshold - - @property - def current_time(self) -> datetime: - return datetime.now(tz=UTC) - - @property - def auth_session_expiration(self) -> datetime: - return self.current_time + self._ttl_min - - @property - def refresh_trigger_interval(self) -> timedelta: - return self._ttl_min * self._refresh_threshold diff --git a/src/app/domain/services/__init__.py b/src/app/infrastructure/auth_ctx/__init__.py similarity index 100% rename from src/app/domain/services/__init__.py rename to src/app/infrastructure/auth_ctx/__init__.py diff --git a/src/app/infrastructure/auth_ctx/cookie_manager.py b/src/app/infrastructure/auth_ctx/cookie_manager.py new file mode 100644 index 00000000..d165aa58 --- /dev/null +++ b/src/app/infrastructure/auth_ctx/cookie_manager.py @@ -0,0 +1,22 @@ +from typing import Final, NewType + +from starlette.requests import Request + +CookieName = NewType("CookieName", str) + +STAGED_COOKIE: Final[str] = "staged_cookie" + + +class CookieManager: + def __init__(self, request: Request, cookie_name: CookieName) -> None: + self._request = request + self._cookie_name = cookie_name + + def read(self) -> str | None: + return self._request.cookies.get(self._cookie_name) + + def stage_set(self, value: str) -> None: + setattr(self._request.state, STAGED_COOKIE, value) + + def stage_delete(self) -> None: + setattr(self._request.state, STAGED_COOKIE, None) diff --git a/src/app/infrastructure/auth_ctx/exceptions.py b/src/app/infrastructure/auth_ctx/exceptions.py new file mode 100644 index 00000000..94178594 --- /dev/null +++ b/src/app/infrastructure/auth_ctx/exceptions.py @@ -0,0 +1,19 @@ +from typing import ClassVar + +from app.core.common.exceptions import BaseError + + +class AuthenticationError(BaseError): + default_message: ClassVar[str] = "Not authenticated." + + +class AlreadyAuthenticatedError(BaseError): + default_message: ClassVar[str] = "You are already authenticated. Consider logging out." + + +class ReAuthenticationError(BaseError): + default_message: ClassVar[str] = "Invalid password." + + +class AuthenticationChangeError(BaseError): + default_message: ClassVar[str] = "New password must differ from current password." diff --git a/src/app/domain/value_objects/__init__.py b/src/app/infrastructure/auth_ctx/handlers/__init__.py similarity index 100% rename from src/app/domain/value_objects/__init__.py rename to src/app/infrastructure/auth_ctx/handlers/__init__.py diff --git a/src/app/infrastructure/auth_ctx/handlers/change_password.py b/src/app/infrastructure/auth_ctx/handlers/change_password.py new file mode 100644 index 00000000..43c25165 --- /dev/null +++ b/src/app/infrastructure/auth_ctx/handlers/change_password.py @@ -0,0 +1,61 @@ +import logging +from dataclasses import dataclass + +from app.core.commands.ports.transaction_manager import TransactionManager +from app.core.commands.ports.utc_timer import UtcTimer +from app.core.common.authorization.current_user_service import CurrentUserService +from app.core.common.services.user import UserService +from app.core.common.value_objects.raw_password import RawPassword +from app.infrastructure.auth_ctx.exceptions import ( + AuthenticationChangeError, + ReAuthenticationError, +) + +logger = logging.getLogger(__name__) + + +@dataclass(frozen=True, slots=True, kw_only=True) +class ChangePasswordRequest: + current_password: str + new_password: str + + +class ChangePassword: + """ + - Open to authenticated users. + - Current user can change their password. + - New password must differ from current password. + """ + + def __init__( + self, + current_user_service: CurrentUserService, + user_service: UserService, + utc_timer: UtcTimer, + transaction_manager: TransactionManager, + ) -> None: + self._current_user_service = current_user_service + self._user_service = user_service + self._utc_timer = utc_timer + self._transaction_manager = transaction_manager + + async def execute(self, request: ChangePasswordRequest) -> None: + logger.info("Change password: started.") + + current_user = await self._current_user_service.get_current_user(for_update=True) + current_password = RawPassword(request.current_password) + new_password = RawPassword(request.new_password) + if current_password == new_password: + raise AuthenticationChangeError + + if not await self._user_service.is_password_valid(current_user, current_password): + raise ReAuthenticationError + + await self._user_service.change_password( + current_user, + new_password, + now=self._utc_timer.now, + ) + await self._transaction_manager.commit() + + logger.info("Change password: done.") diff --git a/src/app/infrastructure/auth_ctx/handlers/log_in.py b/src/app/infrastructure/auth_ctx/handlers/log_in.py new file mode 100644 index 00000000..5f9eb101 --- /dev/null +++ b/src/app/infrastructure/auth_ctx/handlers/log_in.py @@ -0,0 +1,73 @@ +import logging +from dataclasses import dataclass +from typing import Final + +from app.core.commands.exceptions import UserNotFoundError +from app.core.common.authorization.current_user_service import CurrentUserService +from app.core.common.services.user import UserService +from app.core.common.value_objects.raw_password import RawPassword +from app.core.common.value_objects.username import Username +from app.infrastructure.auth_ctx.exceptions import ( + AlreadyAuthenticatedError, + AuthenticationError, +) +from app.infrastructure.auth_ctx.service import AuthService +from app.infrastructure.auth_ctx.sqla_user_tx_storage import AuthSqlaUserTxStorage + +AUTH_ACCOUNT_INACTIVE: Final[str] = "Your account is inactive. Please contact support." +AUTH_PASSWORD_INVALID: Final[str] = "Invalid password." # noqa: S105 + +logger = logging.getLogger(__name__) + + +@dataclass(frozen=True, slots=True, kw_only=True) +class LogInRequest: + username: str + password: str + + +class LogIn: + """ + - Open to everyone. + - Authenticates registered user, sets JWT with session ID in cookies, and creates session. + - Logged-in user cannot log in again until session expires or is terminated. + - Authentication renews automatically when accessing protected routes before expiration. + - If JWT is invalid, expired, or session is terminated, user loses authentication. + """ + + def __init__( + self, + current_user_service: CurrentUserService, + user_tx_storage: AuthSqlaUserTxStorage, + user_service: UserService, + auth_service: AuthService, + ) -> None: + self._current_user_service = current_user_service + self._user_tx_storage = user_tx_storage + self._user_service = user_service + self._auth_service = auth_service + + async def execute(self, request: LogInRequest) -> None: + logger.info("Log in: started.") + + try: + await self._current_user_service.get_current_user() + raise AlreadyAuthenticatedError + except AuthenticationError: + pass + + username = Username(request.username) + password = RawPassword(request.password) + user = await self._user_tx_storage.get_by_username(username) + if user is None: + raise UserNotFoundError + + if not await self._user_service.is_password_valid(user, password): + raise AuthenticationError(AUTH_PASSWORD_INVALID) + + if not user.is_active: + raise AuthenticationError(AUTH_ACCOUNT_INACTIVE) + + await self._auth_service.issue_session(user.id_) + + logger.info("Log in: done.") diff --git a/src/app/infrastructure/auth_ctx/handlers/log_out.py b/src/app/infrastructure/auth_ctx/handlers/log_out.py new file mode 100644 index 00000000..3b141afd --- /dev/null +++ b/src/app/infrastructure/auth_ctx/handlers/log_out.py @@ -0,0 +1,29 @@ +import logging + +from app.core.common.authorization.current_user_service import CurrentUserService +from app.infrastructure.auth_ctx.service import AuthService + +logger = logging.getLogger(__name__) + + +class LogOut: + """ + - Open to authenticated users. + - Logs user out by deleting JWT from cookies and removing session from database. + """ + + def __init__( + self, + current_user_service: CurrentUserService, + auth_service: AuthService, + ) -> None: + self._current_user_service = current_user_service + self._auth_service = auth_service + + async def execute(self) -> None: + logger.info("Log out: started.") + + await self._current_user_service.get_current_user() + await self._auth_service.logout_current_session() + + logger.info("Log out: done.") diff --git a/src/app/infrastructure/auth_ctx/handlers/sign_up.py b/src/app/infrastructure/auth_ctx/handlers/sign_up.py new file mode 100644 index 00000000..8a21a1c1 --- /dev/null +++ b/src/app/infrastructure/auth_ctx/handlers/sign_up.py @@ -0,0 +1,78 @@ +import logging +from dataclasses import dataclass + +from app.core.commands.exceptions import UsernameAlreadyExistsError +from app.core.commands.ports.flusher import Flusher +from app.core.commands.ports.transaction_manager import TransactionManager +from app.core.commands.ports.utc_timer import UtcTimer +from app.core.common.authorization.current_user_service import CurrentUserService +from app.core.common.factories.id_factory import create_user_id +from app.core.common.services.user import UserService +from app.core.common.value_objects.raw_password import RawPassword +from app.core.common.value_objects.username import Username +from app.infrastructure.auth_ctx.exceptions import ( + AlreadyAuthenticatedError, + AuthenticationError, +) +from app.infrastructure.auth_ctx.sqla_user_tx_storage import AuthSqlaUserTxStorage + +logger = logging.getLogger(__name__) + + +@dataclass(frozen=True, slots=True, kw_only=True) +class SignUpRequest: + username: str + password: str + + +class SignUp: + """ + - Open to everyone. + - Registers new user with validation and uniqueness checks. + - Passwords are peppered, salted, and stored as hashes. + - Logged-in user cannot sign up until session expires or is terminated. + """ + + def __init__( + self, + current_user_service: CurrentUserService, + utc_timer: UtcTimer, + user_service: UserService, + user_tx_storage: AuthSqlaUserTxStorage, + flusher: Flusher, + transaction_manager: TransactionManager, + ) -> None: + self._current_user_service = current_user_service + self._utc_timer = utc_timer + self._user_service = user_service + self._user_tx_storage = user_tx_storage + self._flusher = flusher + self._transaction_manager = transaction_manager + + async def execute(self, request: SignUpRequest) -> None: + logger.info("Sign up: started.") + + try: + await self._current_user_service.get_current_user() + raise AlreadyAuthenticatedError + except AuthenticationError: + pass + + username = Username(request.username) + password = RawPassword(request.password) + now = self._utc_timer.now + user = await self._user_service.create_user_with_raw_password( + user_id=create_user_id(), + username=username, + raw_password=password, + now=now, + ) + self._user_tx_storage.add(user) + try: + await self._flusher.flush() + except UsernameAlreadyExistsError: + raise + + await self._transaction_manager.commit() + + logger.info("Sign up: done.") diff --git a/src/app/infrastructure/auth_ctx/id_factory.py b/src/app/infrastructure/auth_ctx/id_factory.py new file mode 100644 index 00000000..b936c7f9 --- /dev/null +++ b/src/app/infrastructure/auth_ctx/id_factory.py @@ -0,0 +1,7 @@ +import secrets + +from app.infrastructure.auth_ctx.model import SessionId + + +def create_session_id(value: str | None = None) -> SessionId: + return SessionId(value if value is not None else secrets.token_urlsafe(32)) diff --git a/src/app/infrastructure/auth_ctx/jwt_processor.py b/src/app/infrastructure/auth_ctx/jwt_processor.py new file mode 100644 index 00000000..340da028 --- /dev/null +++ b/src/app/infrastructure/auth_ctx/jwt_processor.py @@ -0,0 +1,33 @@ +from typing import Any, ClassVar + +import jwt + +from app.infrastructure.auth_ctx.jwt_types import JwtAlgorithm +from app.infrastructure.auth_ctx.model import AuthSession, SessionId + + +class JwtProcessor: + SESSION_ID_CLAIM: ClassVar[str] = "sid" + EXPIRATION_CLAIM: ClassVar[str] = "exp" + + def __init__(self, secret: str, algorithm: JwtAlgorithm) -> None: + self._secret = secret + self._algorithm = algorithm + + def encode(self, auth_session: AuthSession) -> str: + payload: dict[str, Any] = { + self.SESSION_ID_CLAIM: auth_session.id_, + self.EXPIRATION_CLAIM: auth_session.expiration.value.timestamp(), + } + return jwt.encode(payload, key=self._secret, algorithm=self._algorithm) + + def decode_session_id(self, token: str) -> SessionId | None: + try: + payload = jwt.decode(token, key=self._secret, algorithms=[self._algorithm]) + except jwt.PyJWTError: + return None + + value = payload.get(self.SESSION_ID_CLAIM) + if not isinstance(value, str): + return None + return SessionId(value) diff --git a/src/app/infrastructure/auth_ctx/jwt_types.py b/src/app/infrastructure/auth_ctx/jwt_types.py new file mode 100644 index 00000000..50822ee7 --- /dev/null +++ b/src/app/infrastructure/auth_ctx/jwt_types.py @@ -0,0 +1,10 @@ +from typing import Literal + +type JwtAlgorithm = Literal[ + "HS256", + "HS384", + "HS512", + "RS256", + "RS384", + "RS512", +] diff --git a/src/app/infrastructure/auth/session/model.py b/src/app/infrastructure/auth_ctx/model.py similarity index 56% rename from src/app/infrastructure/auth/session/model.py rename to src/app/infrastructure/auth_ctx/model.py index 0ae45246..26212ff9 100644 --- a/src/app/infrastructure/auth/session/model.py +++ b/src/app/infrastructure/auth_ctx/model.py @@ -1,7 +1,10 @@ from dataclasses import dataclass -from datetime import datetime +from typing import NewType -from app.domain.value_objects.user_id import UserId +from app.core.common.entities.types_ import UserId +from app.core.common.value_objects.utc_datetime import UtcDatetime + +SessionId = NewType("SessionId", str) @dataclass(eq=False, kw_only=True) @@ -11,9 +14,9 @@ class AuthSession: a monolithic architecture to become modular, while the other classes working with it are likely to become application and infrastructure layer components. - For example, `LogInHandler` can become an interactor. + For example, `LogIn` can become an interactor. """ - id_: str + id_: SessionId user_id: UserId - expiration: datetime + expiration: UtcDatetime diff --git a/src/app/infrastructure/auth_ctx/service.py b/src/app/infrastructure/auth_ctx/service.py new file mode 100644 index 00000000..59d8f04f --- /dev/null +++ b/src/app/infrastructure/auth_ctx/service.py @@ -0,0 +1,74 @@ +from app.core.common.entities.types_ import UserId +from app.infrastructure.auth_ctx.cookie_manager import CookieManager +from app.infrastructure.auth_ctx.exceptions import AuthenticationError +from app.infrastructure.auth_ctx.id_factory import create_session_id +from app.infrastructure.auth_ctx.jwt_processor import JwtProcessor +from app.infrastructure.auth_ctx.model import AuthSession, SessionId +from app.infrastructure.auth_ctx.sqla_transaction_manager import AuthSqlaTransactionManager +from app.infrastructure.auth_ctx.sqla_tx_storage import AuthSessionSqlaTxStorage +from app.infrastructure.auth_ctx.utc_timer import AuthSessionUtcTimer + + +class AuthService: + def __init__( + self, + session_timer: AuthSessionUtcTimer, + session_tx_storage: AuthSessionSqlaTxStorage, + transaction_manager: AuthSqlaTransactionManager, + jwt_processor: JwtProcessor, + cookie_manager: CookieManager, + ) -> None: + self._session_timer = session_timer + self._session_tx_storage = session_tx_storage + self._transaction_manager = transaction_manager + self._jwt_processor = jwt_processor + self._cookie_manager = cookie_manager + + async def issue_session(self, user_id: UserId) -> None: + session = AuthSession( + id_=create_session_id(), + user_id=user_id, + expiration=self._session_timer.expiration_from_now, + ) + self._session_tx_storage.add(session) + await self._transaction_manager.commit() + token = self._jwt_processor.encode(session) + self._cookie_manager.stage_set(token) + + async def get_current_user_id(self) -> UserId: + session_id = self._get_session_id() + if session_id is None: + raise AuthenticationError + + session = await self._session_tx_storage.get_by_id(session_id) + if session is None: + raise AuthenticationError + + if self._session_timer.is_expired(session): + raise AuthenticationError + + if self._session_timer.needs_refresh(session): + session.expiration = self._session_timer.expiration_from_now + await self._session_tx_storage.update(session) + await self._transaction_manager.commit() + token = self._jwt_processor.encode(session) + self._cookie_manager.stage_set(token) + + return session.user_id + + async def logout_current_session(self) -> None: + self._cookie_manager.stage_delete() + session_id = self._get_session_id() + if session_id is not None: + await self._session_tx_storage.delete(session_id) + await self._transaction_manager.commit() + + async def revoke_all_sessions(self, user_id: UserId) -> None: + await self._session_tx_storage.delete_all_for_user(user_id) + await self._transaction_manager.commit() + + def _get_session_id(self) -> SessionId | None: + token = self._cookie_manager.read() + if token is None: + return None + return self._jwt_processor.decode_session_id(token) diff --git a/src/app/infrastructure/auth_ctx/sqla_transaction_manager.py b/src/app/infrastructure/auth_ctx/sqla_transaction_manager.py new file mode 100644 index 00000000..8cec23df --- /dev/null +++ b/src/app/infrastructure/auth_ctx/sqla_transaction_manager.py @@ -0,0 +1,25 @@ +import logging +from typing import Final + +from sqlalchemy.exc import SQLAlchemyError + +from app.infrastructure.auth_ctx.types_ import AuthAsyncSession +from app.infrastructure.exceptions import StorageError + +DB_COMMIT_DONE: Final[str] = "Commit was done." +DB_COMMIT_FAILED: Final[str] = "Commit failed." + +logger = logging.getLogger(__name__) + + +class AuthSqlaTransactionManager: + def __init__(self, session: AuthAsyncSession) -> None: + self._session = session + + async def commit(self) -> None: + try: + await self._session.commit() + logger.debug("%s.", DB_COMMIT_DONE) + + except SQLAlchemyError as e: + raise StorageError(DB_COMMIT_FAILED) from e diff --git a/src/app/infrastructure/auth_ctx/sqla_tx_storage.py b/src/app/infrastructure/auth_ctx/sqla_tx_storage.py new file mode 100644 index 00000000..77dab19b --- /dev/null +++ b/src/app/infrastructure/auth_ctx/sqla_tx_storage.py @@ -0,0 +1,54 @@ +from sqlalchemy import delete +from sqlalchemy.exc import SQLAlchemyError + +from app.core.common.entities.types_ import UserId +from app.infrastructure.auth_ctx.model import AuthSession, SessionId +from app.infrastructure.auth_ctx.types_ import AuthAsyncSession +from app.infrastructure.exceptions import StorageError +from app.infrastructure.persistence_sqla.mappings.auth_session import auth_sessions_table + + +class AuthSessionSqlaTxStorage: + def __init__(self, session: AuthAsyncSession) -> None: + self._session = session + + def add(self, auth_session: AuthSession) -> None: + try: + self._session.add(auth_session) + except SQLAlchemyError as e: + raise StorageError from e + + async def get_by_id( + self, + session_id: SessionId, + *, + for_update: bool = False, + ) -> AuthSession | None: + try: + return await self._session.get( + AuthSession, + session_id, + with_for_update=for_update, + ) + except SQLAlchemyError as e: + raise StorageError from e + + async def update(self, auth_session: AuthSession) -> None: + try: + await self._session.merge(auth_session) + except SQLAlchemyError as e: + raise StorageError from e + + async def delete(self, session_id: SessionId) -> None: + stmt = delete(auth_sessions_table).where(auth_sessions_table.c.id == session_id) + try: + await self._session.execute(stmt) + except SQLAlchemyError as e: + raise StorageError from e + + async def delete_all_for_user(self, user_id: UserId) -> None: + stmt = delete(auth_sessions_table).where(auth_sessions_table.c.user_id == user_id) + try: + await self._session.execute(stmt) + except SQLAlchemyError as e: + raise StorageError from e diff --git a/src/app/infrastructure/auth_ctx/sqla_user_tx_storage.py b/src/app/infrastructure/auth_ctx/sqla_user_tx_storage.py new file mode 100644 index 00000000..17bf092a --- /dev/null +++ b/src/app/infrastructure/auth_ctx/sqla_user_tx_storage.py @@ -0,0 +1,34 @@ +from sqlalchemy import select +from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.common.entities.user import User +from app.core.common.value_objects.username import Username +from app.infrastructure.exceptions import StorageError +from app.infrastructure.persistence_sqla.mappings.user import users_table + + +class AuthSqlaUserTxStorage: + def __init__(self, session: AsyncSession) -> None: + self._session = session + + def add(self, user: User) -> None: + try: + self._session.add(user) + except SQLAlchemyError as e: + raise StorageError from e + + async def get_by_username( + self, + username: Username, + *, + for_update: bool = False, + ) -> User | None: + stmt = select(User).where(users_table.c.username == username.value) + if for_update: + stmt = stmt.with_for_update() + try: + result = await self._session.execute(stmt) + except SQLAlchemyError as e: + raise StorageError from e + return result.scalar_one_or_none() diff --git a/src/app/infrastructure/auth/adapters/types.py b/src/app/infrastructure/auth_ctx/types_.py similarity index 100% rename from src/app/infrastructure/auth/adapters/types.py rename to src/app/infrastructure/auth_ctx/types_.py diff --git a/src/app/infrastructure/auth_ctx/utc_timer.py b/src/app/infrastructure/auth_ctx/utc_timer.py new file mode 100644 index 00000000..9cbe3a9f --- /dev/null +++ b/src/app/infrastructure/auth_ctx/utc_timer.py @@ -0,0 +1,29 @@ +from datetime import UTC, datetime, timedelta + +from app.core.common.value_objects.utc_datetime import UtcDatetime +from app.infrastructure.auth_ctx.model import AuthSession + + +class AuthSessionUtcTimer: + def __init__( + self, + ttl: timedelta, + refresh_threshold_ratio: float, + ) -> None: + self._ttl = ttl + self._refresh_threshold_ratio = refresh_threshold_ratio + + @property + def now(self) -> UtcDatetime: + return UtcDatetime(datetime.now(UTC)) + + @property + def expiration_from_now(self) -> UtcDatetime: + return UtcDatetime(self.now.value + self._ttl) + + def is_expired(self, session: AuthSession) -> bool: + return session.expiration.value <= self.now.value + + def needs_refresh(self, session: AuthSession) -> bool: + remaining = session.expiration.value - self.now.value + return remaining <= self._ttl * self._refresh_threshold_ratio diff --git a/src/app/infrastructure/exceptions.py b/src/app/infrastructure/exceptions.py new file mode 100644 index 00000000..1da0f314 --- /dev/null +++ b/src/app/infrastructure/exceptions.py @@ -0,0 +1,9 @@ +from app.core.common.exceptions import BaseError + + +class StorageError(BaseError): + pass + + +class ReaderError(BaseError): + pass diff --git a/src/app/infrastructure/exceptions/base.py b/src/app/infrastructure/exceptions/base.py deleted file mode 100644 index 3d16ea1a..00000000 --- a/src/app/infrastructure/exceptions/base.py +++ /dev/null @@ -1,2 +0,0 @@ -class InfrastructureError(Exception): - pass diff --git a/src/app/infrastructure/exceptions/gateway.py b/src/app/infrastructure/exceptions/gateway.py deleted file mode 100644 index c66a0f66..00000000 --- a/src/app/infrastructure/exceptions/gateway.py +++ /dev/null @@ -1,9 +0,0 @@ -from app.infrastructure.exceptions.base import InfrastructureError - - -class DataMapperError(InfrastructureError): - pass - - -class ReaderError(InfrastructureError): - pass diff --git a/src/app/infrastructure/exceptions/password_hasher.py b/src/app/infrastructure/exceptions/password_hasher.py deleted file mode 100644 index 2d902b24..00000000 --- a/src/app/infrastructure/exceptions/password_hasher.py +++ /dev/null @@ -1,5 +0,0 @@ -from app.infrastructure.exceptions.base import InfrastructureError - - -class PasswordHasherBusyError(InfrastructureError): - pass diff --git a/src/app/infrastructure/persistence_sqla/alembic/README b/src/app/infrastructure/persistence_sqla/alembic/README index e0d0858f..a23d4fb5 100644 --- a/src/app/infrastructure/persistence_sqla/alembic/README +++ b/src/app/infrastructure/persistence_sqla/alembic/README @@ -1 +1 @@ -Generic single-database configuration with an async dbapi. \ No newline at end of file +Generic single-database configuration with an async dbapi. diff --git a/src/app/infrastructure/persistence_sqla/alembic/env.py b/src/app/infrastructure/persistence_sqla/alembic/env.py index 4258d53d..6988e4e2 100644 --- a/src/app/infrastructure/persistence_sqla/alembic/env.py +++ b/src/app/infrastructure/persistence_sqla/alembic/env.py @@ -9,9 +9,10 @@ from sqlalchemy.engine import Connection from sqlalchemy.ext.asyncio import async_engine_from_config +from app.config.loader import load_postgres_settings +from app.config.settings import PostgresSettings from app.infrastructure.persistence_sqla.mappings.all import map_tables from app.infrastructure.persistence_sqla.registry import mapper_registry -from app.setup.config.settings import AppSettings, load_settings # this is the Alembic Config object, which provides # access to the values within the .ini file in use. @@ -33,9 +34,9 @@ # can be acquired: # my_important_option = config.get_main_option("my_important_option") # ... etc. -settings: AppSettings = load_settings() +settings: PostgresSettings = load_postgres_settings() -config.set_main_option("sqlalchemy.url", settings.postgres.dsn) +config.set_main_option("sqlalchemy.url", settings.dsn) def run_migrations_offline() -> None: diff --git a/src/app/infrastructure/persistence_sqla/alembic/script.py.mako b/src/app/infrastructure/persistence_sqla/alembic/script.py.mako index fbc4b07d..11016301 100644 --- a/src/app/infrastructure/persistence_sqla/alembic/script.py.mako +++ b/src/app/infrastructure/persistence_sqla/alembic/script.py.mako @@ -13,14 +13,16 @@ ${imports if imports else ""} # revision identifiers, used by Alembic. revision: str = ${repr(up_revision)} -down_revision: Union[str, None] = ${repr(down_revision)} +down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)} branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} def upgrade() -> None: + """Upgrade schema.""" ${upgrades if upgrades else "pass"} def downgrade() -> None: + """Downgrade schema.""" ${downgrades if downgrades else "pass"} diff --git a/src/app/infrastructure/persistence_sqla/alembic/versions/2025_06_11_2058-e325187c1eeb_users_auth.py b/src/app/infrastructure/persistence_sqla/alembic/versions/2025_06_11_2058-e325187c1eeb_users_auth.py deleted file mode 100644 index 766646c5..00000000 --- a/src/app/infrastructure/persistence_sqla/alembic/versions/2025_06_11_2058-e325187c1eeb_users_auth.py +++ /dev/null @@ -1,52 +0,0 @@ -"""users,auth - -Revision ID: e325187c1eeb -Revises: -Create Date: 2025-06-11 20:58:58.908948 - -""" - -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import postgresql - -# revision identifiers, used by Alembic. -revision: str = "e325187c1eeb" -down_revision: Union[str, None] = None -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - sa.Enum("SUPER_ADMIN", "ADMIN", "USER", name="userrole").create(op.get_bind()) - op.create_table( - "auth_sessions", - sa.Column("id", sa.String(), nullable=False), - sa.Column("user_id", sa.UUID(), nullable=False), - sa.Column("expiration", sa.DateTime(timezone=True), nullable=False), - sa.PrimaryKeyConstraint("id", name=op.f("pk_auth_sessions")), - ) - op.create_table( - "users", - sa.Column("id", sa.UUID(), nullable=False), - sa.Column("username", sa.String(length=20), nullable=False), - sa.Column("password_hash", sa.LargeBinary(), nullable=False), - sa.Column( - "role", - postgresql.ENUM( - "SUPER_ADMIN", "ADMIN", "USER", name="userrole", create_type=False - ), - nullable=False, - ), - sa.Column("is_active", sa.Boolean(), nullable=False), - sa.PrimaryKeyConstraint("id", name=op.f("pk_users")), - sa.UniqueConstraint("username", name=op.f("uq_users_username")), - ) - - -def downgrade() -> None: - op.drop_table("users") - op.drop_table("auth_sessions") - sa.Enum("SUPER_ADMIN", "ADMIN", "USER", name="userrole").drop(op.get_bind()) diff --git a/src/app/infrastructure/persistence_sqla/alembic/versions/2026-03-02_221518_users.py b/src/app/infrastructure/persistence_sqla/alembic/versions/2026-03-02_221518_users.py new file mode 100644 index 00000000..784e49ad --- /dev/null +++ b/src/app/infrastructure/persistence_sqla/alembic/versions/2026-03-02_221518_users.py @@ -0,0 +1,40 @@ +"""users + +Revision ID: c64b121a3428 +Revises: +Create Date: 2026-03-02 22:15:18.425263 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = "c64b121a3428" +down_revision: Union[str, Sequence[str], None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + op.create_table( + "users", + sa.Column("id", sa.UUID(), nullable=False), + sa.Column("username", sa.String(length=20), nullable=False), + sa.Column("password_hash", sa.LargeBinary(), nullable=False), + sa.Column("role", sa.Enum("SUPER_ADMIN", "ADMIN", "USER", name="user_role", native_enum=False), nullable=False), + sa.Column("is_active", sa.Boolean(), nullable=False), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False), + sa.PrimaryKeyConstraint("id", name=op.f("pk_users")), + sa.UniqueConstraint("username", name=op.f("uq_users_username")), + ) + + +def downgrade() -> None: + """Downgrade schema.""" + op.drop_table("users") diff --git a/src/app/infrastructure/persistence_sqla/alembic/versions/2026-03-02_230628_auth_sessions.py b/src/app/infrastructure/persistence_sqla/alembic/versions/2026-03-02_230628_auth_sessions.py new file mode 100644 index 00000000..01284c02 --- /dev/null +++ b/src/app/infrastructure/persistence_sqla/alembic/versions/2026-03-02_230628_auth_sessions.py @@ -0,0 +1,38 @@ +"""auth_sessions + +Revision ID: 7b50faaefa7c +Revises: c64b121a3428 +Create Date: 2026-03-02 23:06:28.995808 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = "7b50faaefa7c" +down_revision: Union[str, Sequence[str], None] = "c64b121a3428" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + op.create_table( + "auth_sessions", + sa.Column("id", sa.String(), nullable=False), + sa.Column("user_id", sa.UUID(), nullable=False), + sa.Column("expiration", sa.DateTime(timezone=True), nullable=False), + sa.ForeignKeyConstraint( + ["user_id"], ["users.id"], name=op.f("fk_auth_sessions_user_id_users"), ondelete="CASCADE" + ), + sa.PrimaryKeyConstraint("id", name=op.f("pk_auth_sessions")), + ) + + +def downgrade() -> None: + """Downgrade schema.""" + op.drop_table("auth_sessions") diff --git a/src/app/infrastructure/persistence_sqla/constraint_names.py b/src/app/infrastructure/persistence_sqla/constraint_names.py new file mode 100644 index 00000000..0520a907 --- /dev/null +++ b/src/app/infrastructure/persistence_sqla/constraint_names.py @@ -0,0 +1,3 @@ +from typing import Final + +UQ_USERS_USERNAME: Final[str] = "uq_users_username" diff --git a/src/app/infrastructure/persistence_sqla/mappings/all.py b/src/app/infrastructure/persistence_sqla/mappings/all.py index 61c29d32..78bd6435 100644 --- a/src/app/infrastructure/persistence_sqla/mappings/all.py +++ b/src/app/infrastructure/persistence_sqla/mappings/all.py @@ -21,12 +21,13 @@ during database migrations. """ -from app.infrastructure.persistence_sqla.mappings.auth_session import ( - map_auth_sessions_table, -) +from app.infrastructure.persistence_sqla.mappings.auth_session import map_auth_sessions_table from app.infrastructure.persistence_sqla.mappings.user import map_users_table +from app.infrastructure.persistence_sqla.registry import mapper_registry def map_tables() -> None: + if mapper_registry.mappers: + return map_users_table() map_auth_sessions_table() diff --git a/src/app/infrastructure/persistence_sqla/mappings/auth_session.py b/src/app/infrastructure/persistence_sqla/mappings/auth_session.py index 306aa415..f41c4343 100644 --- a/src/app/infrastructure/persistence_sqla/mappings/auth_session.py +++ b/src/app/infrastructure/persistence_sqla/mappings/auth_session.py @@ -1,15 +1,20 @@ -from sqlalchemy import UUID, Column, DateTime, String, Table +from sqlalchemy import UUID, Column, DateTime, ForeignKey, String, Table from sqlalchemy.orm import composite -from app.domain.value_objects.user_id import UserId -from app.infrastructure.auth.session.model import AuthSession +from app.core.common.value_objects.utc_datetime import UtcDatetime +from app.infrastructure.auth_ctx.model import AuthSession from app.infrastructure.persistence_sqla.registry import mapper_registry auth_sessions_table = Table( "auth_sessions", mapper_registry.metadata, Column("id", String, primary_key=True), - Column("user_id", UUID(as_uuid=True), nullable=False), + Column( + "user_id", + UUID(as_uuid=True), + ForeignKey("users.id", ondelete="CASCADE"), + nullable=False, + ), Column("expiration", DateTime(timezone=True), nullable=False), ) @@ -20,8 +25,8 @@ def map_auth_sessions_table() -> None: auth_sessions_table, properties={ "id_": auth_sessions_table.c.id, - "user_id": composite(UserId, auth_sessions_table.c.user_id), - "expiration": auth_sessions_table.c.expiration, + "user_id": auth_sessions_table.c.user_id, + "expiration": composite(UtcDatetime, auth_sessions_table.c.expiration), }, - column_prefix="_", + column_prefix="__", ) diff --git a/src/app/infrastructure/persistence_sqla/mappings/user.py b/src/app/infrastructure/persistence_sqla/mappings/user.py index 915c3670..8df05d58 100644 --- a/src/app/infrastructure/persistence_sqla/mappings/user.py +++ b/src/app/infrastructure/persistence_sqla/mappings/user.py @@ -1,11 +1,10 @@ -from sqlalchemy import UUID, Boolean, Column, Enum, LargeBinary, String, Table +from sqlalchemy import UUID, Boolean, Column, DateTime, Enum, LargeBinary, String, Table from sqlalchemy.orm import composite -from app.domain.entities.user import User -from app.domain.enums.user_role import UserRole -from app.domain.value_objects.user_id import UserId -from app.domain.value_objects.user_password_hash import UserPasswordHash -from app.domain.value_objects.username import Username +from app.core.common.entities.types_ import UserRole +from app.core.common.entities.user import User +from app.core.common.value_objects.username import Username +from app.core.common.value_objects.utc_datetime import UtcDatetime from app.infrastructure.persistence_sqla.registry import mapper_registry users_table = Table( @@ -16,11 +15,17 @@ Column("password_hash", LargeBinary, nullable=False), Column( "role", - Enum(UserRole, name="userrole"), - default=UserRole.USER, + Enum( + UserRole, + name="user_role", + native_enum=False, + validate_strings=True, + ), nullable=False, ), - Column("is_active", Boolean, default=True, nullable=False), + Column("is_active", Boolean, nullable=False), + Column("created_at", DateTime(timezone=True), nullable=False), + Column("updated_at", DateTime(timezone=True), nullable=False), ) @@ -29,11 +34,13 @@ def map_users_table() -> None: User, users_table, properties={ - "id_": composite(UserId, users_table.c.id), + "id_": users_table.c.id, "username": composite(Username, users_table.c.username), - "password_hash": composite(UserPasswordHash, users_table.c.password_hash), + "password_hash": users_table.c.password_hash, "role": users_table.c.role, "is_active": users_table.c.is_active, + "_created_at": composite(UtcDatetime, users_table.c.created_at), + "updated_at": composite(UtcDatetime, users_table.c.updated_at), }, - column_prefix="_", + column_prefix="__", ) diff --git a/src/app/infrastructure/auth/__init__.py b/src/app/main/__init__.py similarity index 100% rename from src/app/infrastructure/auth/__init__.py rename to src/app/main/__init__.py diff --git a/src/app/infrastructure/auth/adapters/__init__.py b/src/app/main/ioc/__init__.py similarity index 100% rename from src/app/infrastructure/auth/adapters/__init__.py rename to src/app/main/ioc/__init__.py diff --git a/src/app/main/ioc/core.py b/src/app/main/ioc/core.py new file mode 100644 index 00000000..42aab8d3 --- /dev/null +++ b/src/app/main/ioc/core.py @@ -0,0 +1,81 @@ +from dishka import Provider, Scope, provide + +from app.config.settings import PasswordHasherSettings +from app.core.commands.activate_user import ActivateUser +from app.core.commands.create_user import CreateUser +from app.core.commands.deactivate_user import DeactivateUser +from app.core.commands.grant_admin import GrantAdmin +from app.core.commands.ports.flusher import Flusher +from app.core.commands.ports.transaction_manager import TransactionManager +from app.core.commands.ports.user_tx_storage import UserTxStorage +from app.core.commands.ports.utc_timer import UtcTimer +from app.core.commands.revoke_admin import RevokeAdmin +from app.core.commands.set_user_password import SetUserPassword +from app.core.common.authorization.current_user_service import CurrentUserService +from app.core.common.authorization.ports import AuthzUserFinder +from app.core.common.ports.access_revoker import AccessRevoker +from app.core.common.ports.identity_provider import IdentityProvider +from app.core.common.ports.password_hasher import PasswordHasher +from app.core.common.services.user import UserService +from app.core.queries.list_users import ListUsers +from app.core.queries.ports.user_reader import UserReader +from app.infrastructure.adapters.auth_session_access_revoker import AuthSessionAccessRevoker +from app.infrastructure.adapters.auth_session_identity_provider import AuthSessionIdentityProvider +from app.infrastructure.adapters.bcrypt_password_hasher import ( + BcryptPasswordHasher, + HasherSemaphore, + HasherThreadPoolExecutor, +) +from app.infrastructure.adapters.sqla_flusher import SqlaFlusher +from app.infrastructure.adapters.sqla_transaction_manager import SqlaTransactionManager +from app.infrastructure.adapters.sqla_user_reader import SqlaUserReader +from app.infrastructure.adapters.sqla_user_tx_storage import SqlaUserTxStorage +from app.infrastructure.adapters.system_utc_timer import SystemUtcTimer + + +class CoreProvider(Provider): + scope = Scope.REQUEST + + # Services + user_service = provide(UserService, scope=Scope.APP) + current_user_service = provide(CurrentUserService) + + # Common Ports + @provide(scope=Scope.APP) + def provide_password_hasher( + self, + settings: PasswordHasherSettings, + executor: HasherThreadPoolExecutor, + semaphore: HasherSemaphore, + ) -> PasswordHasher: + return BcryptPasswordHasher( + pepper=settings.PEPPER.encode(), + work_factor=settings.WORK_FACTOR, + executor=executor, + semaphore=semaphore, + semaphore_wait_timeout_s=settings.SEMAPHORE_WAIT_TIMEOUT_S, + ) + + identity_provider = provide(AuthSessionIdentityProvider, provides=IdentityProvider) + authz_user_finder = provide(SqlaUserTxStorage, provides=AuthzUserFinder) + access_revoker = provide(AuthSessionAccessRevoker, provides=AccessRevoker) + + # Commands Ports + utc_timer = provide(SystemUtcTimer, provides=UtcTimer) + user_tx_storage = provide(SqlaUserTxStorage, provides=UserTxStorage) + flusher = provide(SqlaFlusher, provides=Flusher) + tx_manager = provide(SqlaTransactionManager, provides=TransactionManager) + + # Commands + create_user = provide(CreateUser) + set_user_password = provide(SetUserPassword) + grant_admin = provide(GrantAdmin) + revoke_admin = provide(RevokeAdmin) + activate_user = provide(ActivateUser) + deactivate_user = provide(DeactivateUser) + + # Query Ports + user_reader = provide(SqlaUserReader, provides=UserReader) + + # Queries + list_users = provide(ListUsers) diff --git a/src/app/main/ioc/infrastructure.py b/src/app/main/ioc/infrastructure.py new file mode 100644 index 00000000..0dcb0c76 --- /dev/null +++ b/src/app/main/ioc/infrastructure.py @@ -0,0 +1,177 @@ +import asyncio +import logging +from collections.abc import AsyncIterator, Iterator +from concurrent.futures import ThreadPoolExecutor +from typing import cast + +from dishka import Provider, Scope, from_context, provide +from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker, create_async_engine +from starlette.requests import Request + +from app.config.settings import ( + CookieSettings, + JwtSettings, + PasswordHasherSettings, + PostgresSettings, + SessionSettings, + SqlaSettings, +) +from app.infrastructure.adapters.bcrypt_password_hasher import HasherSemaphore, HasherThreadPoolExecutor +from app.infrastructure.auth_ctx.cookie_manager import CookieManager, CookieName +from app.infrastructure.auth_ctx.handlers.change_password import ChangePassword +from app.infrastructure.auth_ctx.handlers.log_in import LogIn +from app.infrastructure.auth_ctx.handlers.log_out import LogOut +from app.infrastructure.auth_ctx.handlers.sign_up import SignUp +from app.infrastructure.auth_ctx.jwt_processor import JwtProcessor +from app.infrastructure.auth_ctx.service import AuthService +from app.infrastructure.auth_ctx.sqla_transaction_manager import AuthSqlaTransactionManager +from app.infrastructure.auth_ctx.sqla_tx_storage import AuthSessionSqlaTxStorage +from app.infrastructure.auth_ctx.sqla_user_tx_storage import AuthSqlaUserTxStorage +from app.infrastructure.auth_ctx.types_ import AuthAsyncSession +from app.infrastructure.auth_ctx.utc_timer import AuthSessionUtcTimer + +logger = logging.getLogger(__name__) + + +class HasherThreadPoolProvider(Provider): + scope = Scope.APP + + @provide + def provide_hasher_threadpool_executor( + self, + settings: PasswordHasherSettings, + ) -> Iterator[HasherThreadPoolExecutor]: + executor = HasherThreadPoolExecutor( + ThreadPoolExecutor( + max_workers=settings.MAX_THREADS, + thread_name_prefix="bcrypt", + ) + ) + yield executor + logger.debug("Disposing hasher threadpool executor...") + executor.shutdown(wait=True, cancel_futures=True) + logger.debug("Hasher threadpool executor is disposed.") + + @provide + def provide_hasher_semaphore(self, settings: PasswordHasherSettings) -> HasherSemaphore: + return HasherSemaphore(asyncio.Semaphore(settings.MAX_THREADS)) + + +class PersistenceSqlaProvider(Provider): + @provide(scope=Scope.APP) + async def provide_async_engine( + self, + postgres: PostgresSettings, + sqla: SqlaSettings, + ) -> AsyncIterator[AsyncEngine]: + async_engine = create_async_engine( + url=postgres.dsn, + echo=sqla.ECHO, + echo_pool=sqla.ECHO_POOL, + pool_size=sqla.POOL_SIZE, + max_overflow=sqla.MAX_OVERFLOW, + connect_args={"connect_timeout": 5}, + pool_pre_ping=True, + ) + logger.debug("Async engine created with DSN: %s", postgres.dsn) + yield async_engine + logger.debug("Disposing async engine...") + await async_engine.dispose() + logger.debug("Engine is disposed.") + + @provide(scope=Scope.APP) + def provide_async_session_factory( + self, + engine: AsyncEngine, + ) -> async_sessionmaker[AsyncSession]: + async_session_factory = async_sessionmaker( + bind=engine, + class_=AsyncSession, + autoflush=False, + expire_on_commit=False, + ) + logger.debug("Async session maker initialized.") + return async_session_factory + + @provide(scope=Scope.REQUEST) + async def provide_primary_async_session( + self, + async_session_factory: async_sessionmaker[AsyncSession], + ) -> AsyncIterator[AsyncSession]: + """Provides UoW (AsyncSession) for the primary context""" + logger.debug("Starting primary async session...") + async with async_session_factory() as session: + logger.debug("Primary async session started.") + yield session + logger.debug("Closing primary async session...") + logger.debug("Primary async session closed.") + + @provide(scope=Scope.REQUEST) + async def provide_auth_async_session( + self, + async_session_factory: async_sessionmaker[AsyncSession], + ) -> AsyncIterator[AuthAsyncSession]: + """Provides UoW (AsyncSession) for the auth context.""" + logger.debug("Starting auth async session...") + async with async_session_factory() as session: + logger.debug("Auth async session started.") + yield cast(AuthAsyncSession, session) + logger.debug("Closing auth async session...") + logger.debug("Auth async session closed.") + + +class AuthProvider(Provider): + scope = Scope.REQUEST + + # Auth context + auth_service = provide(AuthService) + + @provide(scope=Scope.APP) + def provide_utc_auth_session_timer( + self, + settings: SessionSettings, + ) -> AuthSessionUtcTimer: + return AuthSessionUtcTimer( + ttl=settings.ttl, + refresh_threshold_ratio=settings.REFRESH_THRESHOLD_RATIO, + ) + + auth_session_tx_storage = provide(AuthSessionSqlaTxStorage) + auth_tx_manager = provide(AuthSqlaTransactionManager) + + @provide(scope=Scope.APP) + def provide_jwt_processor( + self, + settings: JwtSettings, + ) -> JwtProcessor: + return JwtProcessor( + secret=settings.SECRET, + algorithm=settings.ALGORITHM, + ) + + @provide(scope=Scope.APP) + def provide_cookie_name(self, settings: CookieSettings) -> CookieName: + return CookieName(settings.NAME) + + cookie_manager = provide(CookieManager) + + auth_sqla_user_tx_storage = provide(AuthSqlaUserTxStorage) + + # Account handlers + sign_up = provide(SignUp) + log_in = provide(LogIn) + change_password = provide(ChangePassword) + log_out = provide(LogOut) + + +class RequestProvider(Provider): + request = from_context(provides=Request, scope=Scope.REQUEST) + + +def infrastructure_providers() -> tuple[Provider, ...]: + return ( + HasherThreadPoolProvider(), + PersistenceSqlaProvider(), + AuthProvider(), + RequestProvider(), + ) diff --git a/src/app/main/ioc/provider_registry.py b/src/app/main/ioc/provider_registry.py new file mode 100644 index 00000000..8ca138b9 --- /dev/null +++ b/src/app/main/ioc/provider_registry.py @@ -0,0 +1,13 @@ +from collections.abc import Iterable + +from dishka import Provider + +from app.main.ioc.core import CoreProvider +from app.main.ioc.infrastructure import infrastructure_providers + + +def get_providers() -> Iterable[Provider]: + return ( + CoreProvider(), + *infrastructure_providers(), + ) diff --git a/src/app/main/run.py b/src/app/main/run.py new file mode 100644 index 00000000..b1e9252f --- /dev/null +++ b/src/app/main/run.py @@ -0,0 +1,135 @@ +from collections.abc import AsyncIterator, Callable +from contextlib import AbstractAsyncContextManager, asynccontextmanager + +from dishka import AsyncContainer, Provider, make_async_container +from dishka.integrations.fastapi import setup_dishka +from fastapi import FastAPI + +from app.config.loader import ( + load_app_settings, + load_cookie_settings, + load_jwt_settings, + load_password_hasher_settings, + load_postgres_settings, + load_session_settings, + load_sqla_settings, +) +from app.config.settings import ( + AppSettings, + CookieSettings, + JwtSettings, + PasswordHasherSettings, + PostgresSettings, + SessionSettings, + SqlaSettings, +) +from app.infrastructure.persistence_sqla.mappings.all import map_tables +from app.main.ioc.provider_registry import get_providers +from app.main.setup import setup_global_exception_handlers, setup_logging, setup_middlewares +from app.presentation.http.root_router import make_fastapi_root_router + + +def make_lifespan() -> Callable[[FastAPI], AbstractAsyncContextManager[None]]: + @asynccontextmanager + async def lifespan(app: FastAPI) -> AsyncIterator[None]: + """Here one can bind APP-scoped dependencies to `app.state` and close them if needed""" + # https://dishka.readthedocs.io/en/stable/integrations/fastapi.html + container = app.state.dishka_container + try: + map_tables() + yield + finally: + await container.close() + + return lifespan + + +def make_ioc_container( + *di_providers: Provider, + app_settings: AppSettings, + postgres_settings: PostgresSettings, + sqla_settings: SqlaSettings, + password_hasher_settings: PasswordHasherSettings, + jwt_settings: JwtSettings, + session_settings: SessionSettings, + cookie_settings: CookieSettings, +) -> AsyncContainer: + return make_async_container( + *get_providers(), + *di_providers, + context={ + AppSettings: app_settings, + PostgresSettings: postgres_settings, + SqlaSettings: sqla_settings, + PasswordHasherSettings: password_hasher_settings, + JwtSettings: jwt_settings, + SessionSettings: session_settings, + CookieSettings: cookie_settings, + }, + ) + + +def make_app( + *di_providers: Provider, + app_settings: AppSettings | None = None, + postgres_settings: PostgresSettings | None = None, + sqla_settings: SqlaSettings | None = None, + password_hasher_settings: PasswordHasherSettings | None = None, + jwt_settings: JwtSettings | None = None, + session_settings: SessionSettings | None = None, + cookie_settings: CookieSettings | None = None, +) -> FastAPI: + """Pass providers to override existing ones for testing.""" + if app_settings is None: + app_settings = load_app_settings() + + setup_logging(level=app_settings.LOGGING_LEVEL) + + if postgres_settings is None: + postgres_settings = load_postgres_settings() + if sqla_settings is None: + sqla_settings = load_sqla_settings() + if password_hasher_settings is None: + password_hasher_settings = load_password_hasher_settings() + if jwt_settings is None: + jwt_settings = load_jwt_settings() + if session_settings is None: + session_settings = load_session_settings() + if cookie_settings is None: + cookie_settings = load_cookie_settings() + + app = FastAPI( + debug=app_settings.DEBUG_MODE, + title=app_settings.SERVICE_NAME, + version=app_settings.VERSION, + summary=f"OpenAPI schema for {app_settings.SERVICE_NAME}", + lifespan=make_lifespan(), + root_path=app_settings.ROOT_PATH.rstrip("/"), + ) + container = make_ioc_container( + *di_providers, + app_settings=app_settings, + postgres_settings=postgres_settings, + sqla_settings=sqla_settings, + password_hasher_settings=password_hasher_settings, + jwt_settings=jwt_settings, + session_settings=session_settings, + cookie_settings=cookie_settings, + ) + setup_dishka(container, app) + setup_middlewares(app, cookie_settings) + setup_global_exception_handlers(app) + app.include_router( + make_fastapi_root_router( + debug_mode=app_settings.DEBUG_MODE, + cookie_name=cookie_settings.NAME, + ) + ) + return app + + +if __name__ == "__main__": + """See clck.ru/3RUG2j if debug in PyCharm is broken""" + import uvicorn + + uvicorn.run(app=make_app()) diff --git a/src/app/main/setup.py b/src/app/main/setup.py new file mode 100644 index 00000000..66fb1bc5 --- /dev/null +++ b/src/app/main/setup.py @@ -0,0 +1,36 @@ +import logging + +from fastapi import FastAPI + +from app.config.logging_ import DATEFMT, FMT, LoggingLevel +from app.config.settings import CookieSettings +from app.presentation.http.auth_cookie_middleware import AuthCookieMiddleware + +logger = logging.getLogger(__name__) + + +def setup_logging(*, level: LoggingLevel = LoggingLevel.INFO) -> None: + logging.basicConfig( + level=level, + datefmt=DATEFMT, + format=FMT, + force=True, + ) + logger.info("Logging is set up") + + +def setup_middlewares(app: FastAPI, cookie_settings: CookieSettings) -> None: + app.add_middleware( + AuthCookieMiddleware, + cookie_name=cookie_settings.NAME, + cookie_path=cookie_settings.PATH, + cookie_httponly=cookie_settings.HTTPONLY, + cookie_secure=cookie_settings.SECURE, + cookie_samesite=cookie_settings.SAMESITE, + ) + logger.info("Middlewares are set up") + + +def setup_global_exception_handlers(_app: FastAPI) -> None: + # A place to register global exception handlers + logger.info("Global exception handlers are set up") diff --git a/src/app/infrastructure/auth/handlers/__init__.py b/src/app/presentation/http/account/__init__.py similarity index 100% rename from src/app/infrastructure/auth/handlers/__init__.py rename to src/app/presentation/http/account/__init__.py diff --git a/src/app/presentation/http/account/change_password.py b/src/app/presentation/http/account/change_password.py new file mode 100644 index 00000000..15c3de5e --- /dev/null +++ b/src/app/presentation/http/account/change_password.py @@ -0,0 +1,62 @@ +from inspect import getdoc + +from dishka import FromDishka +from dishka.integrations.fastapi import inject +from fastapi import APIRouter, Depends, status +from fastapi.security import APIKeyCookie +from fastapi_error_map import ErrorAwareRouter +from pydantic import BaseModel, ConfigDict + +from app.core.common.authorization.exceptions import AuthorizationError +from app.core.common.exceptions import BusinessTypeError +from app.infrastructure.adapters.exceptions import PasswordHasherBusyError +from app.infrastructure.auth_ctx.exceptions import AuthenticationChangeError, AuthenticationError, ReAuthenticationError +from app.infrastructure.auth_ctx.handlers.change_password import ChangePassword, ChangePasswordRequest +from app.infrastructure.exceptions import StorageError +from app.presentation.http.errors.callbacks import log_info +from app.presentation.http.errors.rules import HTTP_503_SERVICE_UNAVAILABLE_RULE + + +class ChangePasswordRequestSchema(BaseModel): + """ + Using Pydantic model here is generally unnecessary. + It's only implemented to render specific Swagger UI. + """ + + model_config = ConfigDict(frozen=True) + + current_password: str + new_password: str + + +def make_change_password_router(*, cookie_name: str) -> APIRouter: + router = ErrorAwareRouter() + + @router.put( + "/password/", + error_map={ + AuthenticationError: status.HTTP_401_UNAUTHORIZED, + StorageError: HTTP_503_SERVICE_UNAVAILABLE_RULE, + AuthorizationError: status.HTTP_403_FORBIDDEN, + BusinessTypeError: status.HTTP_400_BAD_REQUEST, + AuthenticationChangeError: status.HTTP_400_BAD_REQUEST, + ReAuthenticationError: status.HTTP_403_FORBIDDEN, + PasswordHasherBusyError: HTTP_503_SERVICE_UNAVAILABLE_RULE, + }, + default_on_error=log_info, + status_code=status.HTTP_204_NO_CONTENT, + dependencies=[Depends(APIKeyCookie(name=cookie_name))], + description=getdoc(ChangePassword), + ) + @inject + async def change_password( + request_schema: ChangePasswordRequestSchema, + handler: FromDishka[ChangePassword], + ) -> None: + request = ChangePasswordRequest( + current_password=request_schema.current_password, + new_password=request_schema.new_password, + ) + await handler.execute(request) + + return router diff --git a/src/app/presentation/http/account/log_in.py b/src/app/presentation/http/account/log_in.py new file mode 100644 index 00000000..e5be4f1c --- /dev/null +++ b/src/app/presentation/http/account/log_in.py @@ -0,0 +1,44 @@ +from inspect import getdoc + +from dishka import FromDishka +from dishka.integrations.fastapi import inject +from fastapi import APIRouter, status +from fastapi_error_map import ErrorAwareRouter + +from app.core.commands.exceptions import UserNotFoundError +from app.core.common.authorization.exceptions import AuthorizationError +from app.core.common.exceptions import BusinessTypeError +from app.infrastructure.adapters.exceptions import PasswordHasherBusyError +from app.infrastructure.auth_ctx.exceptions import AlreadyAuthenticatedError, AuthenticationError +from app.infrastructure.auth_ctx.handlers.log_in import LogIn, LogInRequest +from app.infrastructure.exceptions import StorageError +from app.presentation.http.errors.callbacks import log_info +from app.presentation.http.errors.rules import HTTP_503_SERVICE_UNAVAILABLE_RULE + + +def make_log_in_router() -> APIRouter: + router = ErrorAwareRouter() + + @router.post( + "/login/", + error_map={ + StorageError: HTTP_503_SERVICE_UNAVAILABLE_RULE, + AuthorizationError: status.HTTP_403_FORBIDDEN, + AlreadyAuthenticatedError: status.HTTP_403_FORBIDDEN, + BusinessTypeError: status.HTTP_400_BAD_REQUEST, + UserNotFoundError: status.HTTP_404_NOT_FOUND, + AuthenticationError: status.HTTP_401_UNAUTHORIZED, + PasswordHasherBusyError: HTTP_503_SERVICE_UNAVAILABLE_RULE, + }, + default_on_error=log_info, + status_code=status.HTTP_204_NO_CONTENT, + description=getdoc(LogIn), + ) + @inject + async def log_in( + request: LogInRequest, + handler: FromDishka[LogIn], + ) -> None: + await handler.execute(request) + + return router diff --git a/src/app/presentation/http/account/log_out.py b/src/app/presentation/http/account/log_out.py new file mode 100644 index 00000000..ddfd8924 --- /dev/null +++ b/src/app/presentation/http/account/log_out.py @@ -0,0 +1,36 @@ +from inspect import getdoc + +from dishka import FromDishka +from dishka.integrations.fastapi import inject +from fastapi import APIRouter, Depends, status +from fastapi.security import APIKeyCookie +from fastapi_error_map import ErrorAwareRouter + +from app.core.common.authorization.exceptions import AuthorizationError +from app.infrastructure.auth_ctx.exceptions import AuthenticationError +from app.infrastructure.auth_ctx.handlers.log_out import LogOut +from app.infrastructure.exceptions import StorageError +from app.presentation.http.errors.callbacks import log_info +from app.presentation.http.errors.rules import HTTP_503_SERVICE_UNAVAILABLE_RULE + + +def make_log_out_router(*, cookie_name: str) -> APIRouter: + router = ErrorAwareRouter() + + @router.delete( + "/logout/", + error_map={ + AuthenticationError: status.HTTP_401_UNAUTHORIZED, + StorageError: HTTP_503_SERVICE_UNAVAILABLE_RULE, + AuthorizationError: status.HTTP_403_FORBIDDEN, + }, + default_on_error=log_info, + status_code=status.HTTP_204_NO_CONTENT, + dependencies=[Depends(APIKeyCookie(name=cookie_name))], + description=getdoc(LogOut), + ) + @inject + async def log_out(handler: FromDishka[LogOut]) -> None: + await handler.execute() + + return router diff --git a/src/app/presentation/http/account/router.py b/src/app/presentation/http/account/router.py new file mode 100644 index 00000000..b39cf37e --- /dev/null +++ b/src/app/presentation/http/account/router.py @@ -0,0 +1,15 @@ +from fastapi import APIRouter + +from app.presentation.http.account.change_password import make_change_password_router +from app.presentation.http.account.log_in import make_log_in_router +from app.presentation.http.account.log_out import make_log_out_router +from app.presentation.http.account.sign_up import make_sign_up_router + + +def make_account_router(*, cookie_name: str) -> APIRouter: + router = APIRouter(prefix="/account", tags=["Account"]) + router.include_router(make_sign_up_router()) + router.include_router(make_log_in_router()) + router.include_router(make_change_password_router(cookie_name=cookie_name)) + router.include_router(make_log_out_router(cookie_name=cookie_name)) + return router diff --git a/src/app/presentation/http/account/sign_up.py b/src/app/presentation/http/account/sign_up.py new file mode 100644 index 00000000..42c02a16 --- /dev/null +++ b/src/app/presentation/http/account/sign_up.py @@ -0,0 +1,43 @@ +from inspect import getdoc + +from dishka import FromDishka +from dishka.integrations.fastapi import inject +from fastapi import APIRouter, status +from fastapi_error_map import ErrorAwareRouter + +from app.core.commands.exceptions import UsernameAlreadyExistsError +from app.core.common.authorization.exceptions import AuthorizationError +from app.core.common.exceptions import BusinessTypeError +from app.infrastructure.adapters.exceptions import PasswordHasherBusyError +from app.infrastructure.auth_ctx.exceptions import AlreadyAuthenticatedError +from app.infrastructure.auth_ctx.handlers.sign_up import SignUp, SignUpRequest +from app.infrastructure.exceptions import StorageError +from app.presentation.http.errors.callbacks import log_info +from app.presentation.http.errors.rules import HTTP_503_SERVICE_UNAVAILABLE_RULE + + +def make_sign_up_router() -> APIRouter: + router = ErrorAwareRouter() + + @router.post( + "/signup/", + error_map={ + StorageError: HTTP_503_SERVICE_UNAVAILABLE_RULE, + AuthorizationError: status.HTTP_403_FORBIDDEN, + AlreadyAuthenticatedError: status.HTTP_403_FORBIDDEN, + BusinessTypeError: status.HTTP_400_BAD_REQUEST, + PasswordHasherBusyError: HTTP_503_SERVICE_UNAVAILABLE_RULE, + UsernameAlreadyExistsError: status.HTTP_409_CONFLICT, + }, + default_on_error=log_info, + status_code=status.HTTP_204_NO_CONTENT, + description=getdoc(SignUp), + ) + @inject + async def sign_up( + request: SignUpRequest, + handler: FromDishka[SignUp], + ) -> None: + await handler.execute(request) + + return router diff --git a/src/app/presentation/http/api_v1_router.py b/src/app/presentation/http/api_v1_router.py new file mode 100644 index 00000000..07ee3f96 --- /dev/null +++ b/src/app/presentation/http/api_v1_router.py @@ -0,0 +1,11 @@ +from fastapi import APIRouter + +from app.presentation.http.account.router import make_account_router +from app.presentation.http.users.router import make_users_router + + +def make_v1_router(*, cookie_name: str) -> APIRouter: + router = APIRouter(prefix="/api/v1") + router.include_router(make_account_router(cookie_name=cookie_name)) + router.include_router(make_users_router(cookie_name=cookie_name)) + return router diff --git a/src/app/presentation/http/auth/access_token_processor_jwt.py b/src/app/presentation/http/auth/access_token_processor_jwt.py deleted file mode 100644 index ee0b1cf3..00000000 --- a/src/app/presentation/http/auth/access_token_processor_jwt.py +++ /dev/null @@ -1,69 +0,0 @@ -import logging -from typing import Any, Literal, TypedDict, cast - -import jwt - -from app.infrastructure.auth.session.model import AuthSession -from app.presentation.http.auth.constants import ( - ACCESS_TOKEN_INVALID_OR_EXPIRED, - ACCESS_TOKEN_PAYLOAD_MISSING, - ACCESS_TOKEN_PAYLOAD_OF_INTEREST, -) - -log = logging.getLogger(__name__) - - -class JwtPayload(TypedDict): - auth_session_id: str - exp: int - - -class JwtAccessTokenProcessor: - def __init__( - self, - secret: str, - algorithm: Literal[ - "HS256", - "HS384", - "HS512", - "RS256", - "RS384", - "RS512", - ], - ) -> None: - self._secret = secret - self._algorithm = algorithm - - def encode(self, auth_session: AuthSession) -> str: - payload = JwtPayload( - auth_session_id=auth_session.id_, - exp=int(auth_session.expiration.timestamp()), - ) - return jwt.encode( - cast(dict[str, Any], payload), - key=self._secret, - algorithm=self._algorithm, - ) - - def decode_auth_session_id(self, token: str) -> str | None: - try: - payload = jwt.decode( - token, - key=self._secret, - algorithms=[self._algorithm], - ) - - except jwt.PyJWTError as err: - log.debug("%s %s", ACCESS_TOKEN_INVALID_OR_EXPIRED, err) - return None - - auth_session_id: str | None = payload.get(ACCESS_TOKEN_PAYLOAD_OF_INTEREST) - if auth_session_id is None: - log.debug( - "%s '%s'", - ACCESS_TOKEN_PAYLOAD_MISSING, - ACCESS_TOKEN_PAYLOAD_OF_INTEREST, - ) - return None - - return auth_session_id diff --git a/src/app/presentation/http/auth/adapters/session_transport_jwt_cookie.py b/src/app/presentation/http/auth/adapters/session_transport_jwt_cookie.py deleted file mode 100644 index cfdd4a06..00000000 --- a/src/app/presentation/http/auth/adapters/session_transport_jwt_cookie.py +++ /dev/null @@ -1,61 +0,0 @@ -import logging - -from starlette.requests import Request - -from app.infrastructure.auth.session.model import AuthSession -from app.infrastructure.auth.session.ports.transport import AuthSessionTransport -from app.presentation.http.auth.access_token_processor_jwt import ( - JwtAccessTokenProcessor, -) -from app.presentation.http.auth.constants import ( - ACCESS_TOKEN_DELIVERED_VIA_COOKIE, - ACCESS_TOKEN_MARKED_FOR_REMOVAL, - ACCESS_TOKEN_NOT_FOUND_IN_COOKIE, - COOKIE_ACCESS_TOKEN_NAME, - REQUEST_STATE_COOKIE_PARAMS_KEY, - REQUEST_STATE_DELETE_ACCESS_TOKEN_KEY, - REQUEST_STATE_NEW_ACCESS_TOKEN_KEY, -) -from app.presentation.http.auth.cookie_params import CookieParams - -log = logging.getLogger(__name__) - - -class JwtCookieAuthSessionTransport(AuthSessionTransport): - def __init__( - self, - request: Request, - access_token_processor: JwtAccessTokenProcessor, - cookie_params: CookieParams, - ) -> None: - self._request = request - self._access_token_processor = access_token_processor - self._cookie_params = cookie_params - - def deliver(self, auth_session: AuthSession) -> None: - access_token = self._access_token_processor.encode(auth_session) - setattr(self._request.state, REQUEST_STATE_NEW_ACCESS_TOKEN_KEY, access_token) - setattr( - self._request.state, - REQUEST_STATE_COOKIE_PARAMS_KEY, - self._cookie_params, - ) - - log.debug( - "%s Session ID: %s", - ACCESS_TOKEN_DELIVERED_VIA_COOKIE, - auth_session.id_, - ) - - def extract_id(self) -> str | None: - access_token = self._request.cookies.get(COOKIE_ACCESS_TOKEN_NAME) - if access_token is None: - log.debug("%s", ACCESS_TOKEN_NOT_FOUND_IN_COOKIE) - return None - - return self._access_token_processor.decode_auth_session_id(access_token) - - def remove_current(self) -> None: - setattr(self._request.state, REQUEST_STATE_DELETE_ACCESS_TOKEN_KEY, True) - - log.debug("%s", ACCESS_TOKEN_MARKED_FOR_REMOVAL) diff --git a/src/app/presentation/http/auth/asgi_middleware.py b/src/app/presentation/http/auth/asgi_middleware.py deleted file mode 100644 index 9cffcc01..00000000 --- a/src/app/presentation/http/auth/asgi_middleware.py +++ /dev/null @@ -1,101 +0,0 @@ -import logging -from http.cookies import SimpleCookie -from typing import Literal - -from starlette.datastructures import MutableHeaders -from starlette.requests import Request -from starlette.types import ASGIApp, Message, Receive, Scope, Send - -from app.presentation.http.auth.constants import ( - COOKIE_ACCESS_TOKEN_NAME, - REQUEST_STATE_COOKIE_PARAMS_KEY, - REQUEST_STATE_DELETE_ACCESS_TOKEN_KEY, - REQUEST_STATE_NEW_ACCESS_TOKEN_KEY, -) -from app.presentation.http.auth.cookie_params import ( - CookieParams, -) - -log = logging.getLogger(__name__) - - -class ASGIAuthMiddleware: - def __init__(self, app: ASGIApp) -> None: - self.app = app - - async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: - if scope["type"] != "http": - return await self.app(scope, receive, send) - - request = Request(scope) - - async def send_wrapper(message: Message) -> None: - if message["type"] == "http.response.start": - headers = MutableHeaders(scope=message) - self._maybe_set_cookie(request, headers) - self._maybe_delete_cookie(request, headers) - await send(message) - - return await self.app(scope, receive, send_wrapper) - - def _maybe_set_cookie(self, request: Request, headers: MutableHeaders) -> None: - new_access_token: str | None = getattr( - request.state, - REQUEST_STATE_NEW_ACCESS_TOKEN_KEY, - None, - ) - if new_access_token is None: - return - - cookie_params: CookieParams = getattr( - request.state, - REQUEST_STATE_COOKIE_PARAMS_KEY, - CookieParams(secure=False), - ) - cookie_header = self._make_cookie_header( - value=new_access_token, - is_secure=cookie_params.secure, - samesite=cookie_params.samesite, - ) - headers.append("Set-Cookie", cookie_header) - log.debug("Cookie with access token '%s' was set.", new_access_token) - - def _maybe_delete_cookie( - self, - request: Request, - headers: MutableHeaders, - ) -> None: - if not getattr(request.state, REQUEST_STATE_DELETE_ACCESS_TOKEN_KEY, False): - return - - current_access_token = request.cookies.get(COOKIE_ACCESS_TOKEN_NAME) - log.debug( - "Deleting cookie with access token: '%s'.", - current_access_token if current_access_token else "already deleted", - ) - - cookie_header = self._make_cookie_header(value="", max_age=0) - headers.append("Set-Cookie", cookie_header) - log.debug("Cookie was deleted.") - - def _make_cookie_header( - self, - *, - value: str, - is_secure: bool = False, - samesite: Literal["strict"] | None = None, - max_age: int | None = None, - ) -> str: - cookie = SimpleCookie() - cookie["access_token"] = value - cookie["access_token"]["path"] = "/" - cookie["access_token"]["httponly"] = True - - if is_secure: - cookie["access_token"]["secure"] = True - if samesite: - cookie["access_token"]["samesite"] = samesite - if max_age is not None: - cookie["access_token"]["max-age"] = max_age - - return cookie.output(header="").strip() diff --git a/src/app/presentation/http/auth/constants.py b/src/app/presentation/http/auth/constants.py deleted file mode 100644 index 6764cd37..00000000 --- a/src/app/presentation/http/auth/constants.py +++ /dev/null @@ -1,18 +0,0 @@ -from typing import Final - -ACCESS_TOKEN_DELIVERED_VIA_COOKIE: Final[str] = ( - "Delivered auth session token via cookie." -) -ACCESS_TOKEN_INVALID_OR_EXPIRED: Final[str] = "Invalid or expired JWT." -ACCESS_TOKEN_MARKED_FOR_REMOVAL: Final[str] = ( - "Marked access token for removal in response." -) -ACCESS_TOKEN_NOT_FOUND_IN_COOKIE: Final[str] = "No access token found in cookie." -ACCESS_TOKEN_PAYLOAD_OF_INTEREST: Final[str] = "auth_session_id" -ACCESS_TOKEN_PAYLOAD_MISSING: Final[str] = "JWT payload missing." - -COOKIE_ACCESS_TOKEN_NAME: Final[str] = "access_token" - -REQUEST_STATE_COOKIE_PARAMS_KEY: Final[str] = "cookie_params" -REQUEST_STATE_DELETE_ACCESS_TOKEN_KEY: Final[str] = "delete_access_token" -REQUEST_STATE_NEW_ACCESS_TOKEN_KEY: Final[str] = "new_access_token" diff --git a/src/app/presentation/http/auth/cookie_params.py b/src/app/presentation/http/auth/cookie_params.py deleted file mode 100644 index 6db57de3..00000000 --- a/src/app/presentation/http/auth/cookie_params.py +++ /dev/null @@ -1,12 +0,0 @@ -from dataclasses import dataclass -from typing import Literal - - -@dataclass(eq=False, slots=True, kw_only=True) -class CookieParams: - secure: bool - samesite: Literal["strict"] | None = None - - def __post_init__(self) -> None: - if self.secure and self.samesite is None: - self.samesite = "strict" diff --git a/src/app/presentation/http/auth/openapi_marker.py b/src/app/presentation/http/auth/openapi_marker.py deleted file mode 100644 index 36878313..00000000 --- a/src/app/presentation/http/auth/openapi_marker.py +++ /dev/null @@ -1,9 +0,0 @@ -from fastapi.security import APIKeyCookie - -from app.presentation.http.auth.constants import ( - COOKIE_ACCESS_TOKEN_NAME, -) - -# Cookie extraction marker for Swagger UI (OpenAPI). -# The actual cookie processing is handled behind the Identity Provider. -cookie_scheme = APIKeyCookie(name=COOKIE_ACCESS_TOKEN_NAME) diff --git a/src/app/presentation/http/auth_cookie_middleware.py b/src/app/presentation/http/auth_cookie_middleware.py new file mode 100644 index 00000000..33d3cee0 --- /dev/null +++ b/src/app/presentation/http/auth_cookie_middleware.py @@ -0,0 +1,58 @@ +from typing import ClassVar, Literal, cast + +from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint +from starlette.requests import Request +from starlette.responses import Response +from starlette.types import ASGIApp + +from app.infrastructure.auth_ctx.cookie_manager import STAGED_COOKIE + + +class AuthCookieMiddleware(BaseHTTPMiddleware): + MISSING: ClassVar[object] = object() + + def __init__( + self, + app: ASGIApp, + *, + cookie_name: str, + cookie_path: str, + cookie_httponly: bool, + cookie_secure: bool, + cookie_samesite: Literal["lax", "strict", "none"], + ) -> None: + super().__init__(app) + self._cookie_name = cookie_name + self._cookie_path = cookie_path + self._cookie_httponly = cookie_httponly + self._cookie_secure = cookie_secure + self._cookie_samesite = cookie_samesite + + async def dispatch( + self, + request: Request, + call_next: RequestResponseEndpoint, + ) -> Response: + response = await call_next(request) + + staged = getattr(request.state, STAGED_COOKIE, self.MISSING) + if staged is self.MISSING: + return response + + value = cast(str | None, staged) + if value is None: + response.delete_cookie( + key=self._cookie_name, + path=self._cookie_path, + ) + return response + + response.set_cookie( + key=self._cookie_name, + value=value, + path=self._cookie_path, + httponly=self._cookie_httponly, + secure=self._cookie_secure, + samesite=self._cookie_samesite, + ) + return response diff --git a/src/app/presentation/http/controllers/account/change_password.py b/src/app/presentation/http/controllers/account/change_password.py deleted file mode 100644 index c19c8cc5..00000000 --- a/src/app/presentation/http/controllers/account/change_password.py +++ /dev/null @@ -1,68 +0,0 @@ -from inspect import getdoc -from typing import Annotated - -from dishka import FromDishka -from dishka.integrations.fastapi import inject -from fastapi import APIRouter, Body, Security, status -from fastapi_error_map import ErrorAwareRouter, rule - -from app.application.common.exceptions.authorization import AuthorizationError -from app.domain.exceptions.base import DomainTypeError -from app.infrastructure.auth.exceptions import ( - AuthenticationChangeError, - AuthenticationError, - ReAuthenticationError, -) -from app.infrastructure.auth.handlers.change_password import ( - ChangePasswordHandler, - ChangePasswordRequest, -) -from app.infrastructure.exceptions.gateway import DataMapperError -from app.infrastructure.exceptions.password_hasher import PasswordHasherBusyError -from app.presentation.http.auth.openapi_marker import cookie_scheme -from app.presentation.http.errors.callbacks import log_error, log_info -from app.presentation.http.errors.translators import ( - ServiceUnavailableTranslator, -) - - -def create_change_password_router() -> APIRouter: - router = ErrorAwareRouter() - - @router.put( - "/password", - description=getdoc(ChangePasswordHandler), - error_map={ - AuthenticationError: status.HTTP_401_UNAUTHORIZED, - DataMapperError: rule( - status=status.HTTP_503_SERVICE_UNAVAILABLE, - translator=ServiceUnavailableTranslator(), - on_error=log_error, - ), - AuthorizationError: status.HTTP_403_FORBIDDEN, - DomainTypeError: status.HTTP_400_BAD_REQUEST, - AuthenticationChangeError: status.HTTP_400_BAD_REQUEST, - ReAuthenticationError: status.HTTP_403_FORBIDDEN, - PasswordHasherBusyError: rule( - status=status.HTTP_503_SERVICE_UNAVAILABLE, - translator=ServiceUnavailableTranslator(), - on_error=log_error, - ), - }, - default_on_error=log_info, - status_code=status.HTTP_204_NO_CONTENT, - dependencies=[Security(cookie_scheme)], - ) - @inject - async def change_password( - current_password: Annotated[str, Body()], - new_password: Annotated[str, Body()], - handler: FromDishka[ChangePasswordHandler], - ) -> None: - request_data = ChangePasswordRequest( - current_password=current_password, - new_password=new_password, - ) - await handler.execute(request_data) - - return router diff --git a/src/app/presentation/http/controllers/account/log_in.py b/src/app/presentation/http/controllers/account/log_in.py deleted file mode 100644 index 1febf5be..00000000 --- a/src/app/presentation/http/controllers/account/log_in.py +++ /dev/null @@ -1,57 +0,0 @@ -from inspect import getdoc - -from dishka import FromDishka -from dishka.integrations.fastapi import inject -from fastapi import APIRouter, status -from fastapi_error_map import ErrorAwareRouter, rule - -from app.application.common.exceptions.authorization import AuthorizationError -from app.domain.exceptions.base import DomainTypeError -from app.domain.exceptions.user import UserNotFoundByUsernameError -from app.infrastructure.auth.exceptions import ( - AlreadyAuthenticatedError, - AuthenticationError, -) -from app.infrastructure.auth.handlers.log_in import LogInHandler, LogInRequest -from app.infrastructure.exceptions.gateway import DataMapperError -from app.infrastructure.exceptions.password_hasher import PasswordHasherBusyError -from app.presentation.http.errors.callbacks import log_error, log_info -from app.presentation.http.errors.translators import ( - ServiceUnavailableTranslator, -) - - -def create_log_in_router() -> APIRouter: - router = ErrorAwareRouter() - - @router.post( - "/login", - description=getdoc(LogInHandler), - error_map={ - AlreadyAuthenticatedError: status.HTTP_403_FORBIDDEN, - AuthorizationError: status.HTTP_403_FORBIDDEN, - DataMapperError: rule( - status=status.HTTP_503_SERVICE_UNAVAILABLE, - translator=ServiceUnavailableTranslator(), - on_error=log_error, - ), - DomainTypeError: status.HTTP_400_BAD_REQUEST, - UserNotFoundByUsernameError: status.HTTP_404_NOT_FOUND, - PasswordHasherBusyError: rule( - status=status.HTTP_503_SERVICE_UNAVAILABLE, - translator=ServiceUnavailableTranslator(), - on_error=log_error, - ), - AuthenticationError: status.HTTP_401_UNAUTHORIZED, - }, - default_on_error=log_info, - status_code=status.HTTP_204_NO_CONTENT, - ) - @inject - async def login( - request_data: LogInRequest, - handler: FromDishka[LogInHandler], - ) -> None: - await handler.execute(request_data) - - return router diff --git a/src/app/presentation/http/controllers/account/log_out.py b/src/app/presentation/http/controllers/account/log_out.py deleted file mode 100644 index be701712..00000000 --- a/src/app/presentation/http/controllers/account/log_out.py +++ /dev/null @@ -1,47 +0,0 @@ -from inspect import getdoc - -from dishka import FromDishka -from dishka.integrations.fastapi import inject -from fastapi import APIRouter, Security, status -from fastapi_error_map import ErrorAwareRouter, rule - -from app.application.common.exceptions.authorization import AuthorizationError -from app.infrastructure.auth.exceptions import AuthenticationError -from app.infrastructure.auth.handlers.log_out import LogOutHandler -from app.infrastructure.exceptions.gateway import DataMapperError -from app.presentation.http.auth.openapi_marker import cookie_scheme -from app.presentation.http.errors.callbacks import ( - log_error, - log_info, -) -from app.presentation.http.errors.translators import ( - ServiceUnavailableTranslator, -) - - -def create_log_out_router() -> APIRouter: - router = ErrorAwareRouter() - - @router.delete( - "/logout", - description=getdoc(LogOutHandler), - error_map={ - AuthenticationError: status.HTTP_401_UNAUTHORIZED, - AuthorizationError: status.HTTP_403_FORBIDDEN, - DataMapperError: rule( - status=status.HTTP_503_SERVICE_UNAVAILABLE, - translator=ServiceUnavailableTranslator(), - on_error=log_error, - ), - }, - default_on_error=log_info, - status_code=status.HTTP_204_NO_CONTENT, - dependencies=[Security(cookie_scheme)], - ) - @inject - async def logout( - handler: FromDishka[LogOutHandler], - ) -> None: - await handler.execute() - - return router diff --git a/src/app/presentation/http/controllers/account/router.py b/src/app/presentation/http/controllers/account/router.py deleted file mode 100644 index 45d9829e..00000000 --- a/src/app/presentation/http/controllers/account/router.py +++ /dev/null @@ -1,24 +0,0 @@ -from fastapi import APIRouter - -from app.presentation.http.controllers.account.change_password import ( - create_change_password_router, -) -from app.presentation.http.controllers.account.log_in import create_log_in_router -from app.presentation.http.controllers.account.log_out import ( - create_log_out_router, -) -from app.presentation.http.controllers.account.sign_up import ( - create_sign_up_router, -) - - -def create_account_router() -> APIRouter: - router = APIRouter( - prefix="/account", - tags=["Account"], - ) - router.include_router(create_sign_up_router()) - router.include_router(create_log_in_router()) - router.include_router(create_change_password_router()) - router.include_router(create_log_out_router()) - return router diff --git a/src/app/presentation/http/controllers/account/sign_up.py b/src/app/presentation/http/controllers/account/sign_up.py deleted file mode 100644 index d793a9bf..00000000 --- a/src/app/presentation/http/controllers/account/sign_up.py +++ /dev/null @@ -1,64 +0,0 @@ -from inspect import getdoc - -from dishka import FromDishka -from dishka.integrations.fastapi import inject -from fastapi import APIRouter, status -from fastapi_error_map import ErrorAwareRouter, rule - -from app.application.common.exceptions.authorization import AuthorizationError -from app.domain.exceptions.base import DomainTypeError -from app.domain.exceptions.user import ( - RoleAssignmentNotPermittedError, - UsernameAlreadyExistsError, -) -from app.infrastructure.auth.exceptions import AlreadyAuthenticatedError -from app.infrastructure.auth.handlers.sign_up import ( - SignUpHandler, - SignUpRequest, - SignUpResponse, -) -from app.infrastructure.exceptions.gateway import DataMapperError -from app.infrastructure.exceptions.password_hasher import PasswordHasherBusyError -from app.presentation.http.errors.callbacks import ( - log_error, - log_info, -) -from app.presentation.http.errors.translators import ( - ServiceUnavailableTranslator, -) - - -def create_sign_up_router() -> APIRouter: - router = ErrorAwareRouter() - - @router.post( - "/signup", - description=getdoc(SignUpHandler), - error_map={ - AlreadyAuthenticatedError: status.HTTP_403_FORBIDDEN, - AuthorizationError: status.HTTP_403_FORBIDDEN, - DataMapperError: rule( - status=status.HTTP_503_SERVICE_UNAVAILABLE, - translator=ServiceUnavailableTranslator(), - on_error=log_error, - ), - DomainTypeError: status.HTTP_400_BAD_REQUEST, - PasswordHasherBusyError: rule( - status=status.HTTP_503_SERVICE_UNAVAILABLE, - translator=ServiceUnavailableTranslator(), - on_error=log_error, - ), - RoleAssignmentNotPermittedError: status.HTTP_422_UNPROCESSABLE_ENTITY, - UsernameAlreadyExistsError: status.HTTP_409_CONFLICT, - }, - default_on_error=log_info, - status_code=status.HTTP_201_CREATED, - ) - @inject - async def sign_up( - request_data: SignUpRequest, - handler: FromDishka[SignUpHandler], - ) -> SignUpResponse: - return await handler.execute(request_data) - - return router diff --git a/src/app/presentation/http/controllers/api_v1_router.py b/src/app/presentation/http/controllers/api_v1_router.py deleted file mode 100644 index 98d87761..00000000 --- a/src/app/presentation/http/controllers/api_v1_router.py +++ /dev/null @@ -1,13 +0,0 @@ -from fastapi import APIRouter - -from app.presentation.http.controllers.account.router import create_account_router -from app.presentation.http.controllers.general.router import create_general_router -from app.presentation.http.controllers.users.router import create_users_router - - -def create_api_v1_router() -> APIRouter: - router = APIRouter(prefix="/api/v1") - router.include_router(create_account_router()) - router.include_router(create_general_router()) - router.include_router(create_users_router()) - return router diff --git a/src/app/presentation/http/controllers/general/health.py b/src/app/presentation/http/controllers/general/health.py deleted file mode 100644 index eb6ba991..00000000 --- a/src/app/presentation/http/controllers/general/health.py +++ /dev/null @@ -1,16 +0,0 @@ -from fastapi import APIRouter -from starlette.requests import Request - - -def create_health_router() -> APIRouter: - router = APIRouter() - - @router.get("/health") - async def health(_: Request) -> dict[str, str]: - """ - - Open to everyone. - - Returns `200 OK` if the API is alive. - """ - return {"status": "ok"} - - return router diff --git a/src/app/presentation/http/controllers/general/router.py b/src/app/presentation/http/controllers/general/router.py deleted file mode 100644 index d18534f4..00000000 --- a/src/app/presentation/http/controllers/general/router.py +++ /dev/null @@ -1,11 +0,0 @@ -from fastapi import APIRouter - -from app.presentation.http.controllers.general.health import ( - create_health_router, -) - - -def create_general_router() -> APIRouter: - router = APIRouter(tags=["General"]) - router.include_router(create_health_router()) - return router diff --git a/src/app/presentation/http/controllers/root_router.py b/src/app/presentation/http/controllers/root_router.py deleted file mode 100644 index 41f0ac49..00000000 --- a/src/app/presentation/http/controllers/root_router.py +++ /dev/null @@ -1,19 +0,0 @@ -from fastapi import APIRouter -from fastapi.responses import RedirectResponse - -from app.presentation.http.controllers.api_v1_router import create_api_v1_router - - -def create_root_router() -> APIRouter: - router = APIRouter() - - @router.get("/", tags=["General"]) - async def redirect_to_docs() -> RedirectResponse: - """ - - Open to everyone. - - Redirects to Swagger documentation. - """ - return RedirectResponse(url="docs/") - - router.include_router(create_api_v1_router()) - return router diff --git a/src/app/presentation/http/controllers/users/activate_user.py b/src/app/presentation/http/controllers/users/activate_user.py deleted file mode 100644 index 320e6f28..00000000 --- a/src/app/presentation/http/controllers/users/activate_user.py +++ /dev/null @@ -1,57 +0,0 @@ -from inspect import getdoc -from typing import Annotated -from uuid import UUID - -from dishka import FromDishka -from dishka.integrations.fastapi import inject -from fastapi import APIRouter, Path, Security, status -from fastapi_error_map import ErrorAwareRouter, rule - -from app.application.commands.activate_user import ( - ActivateUserInteractor, - ActivateUserRequest, -) -from app.application.common.exceptions.authorization import AuthorizationError -from app.domain.exceptions.user import ( - ActivationChangeNotPermittedError, - UserNotFoundByIdError, -) -from app.infrastructure.auth.exceptions import AuthenticationError -from app.infrastructure.exceptions.gateway import DataMapperError -from app.presentation.http.auth.openapi_marker import cookie_scheme -from app.presentation.http.errors.callbacks import log_error, log_info -from app.presentation.http.errors.translators import ( - ServiceUnavailableTranslator, -) - - -def create_activate_user_router() -> APIRouter: - router = ErrorAwareRouter() - - @router.put( - "/{user_id}/activation", - description=getdoc(ActivateUserInteractor), - error_map={ - AuthenticationError: status.HTTP_401_UNAUTHORIZED, - DataMapperError: rule( - status=status.HTTP_503_SERVICE_UNAVAILABLE, - translator=ServiceUnavailableTranslator(), - on_error=log_error, - ), - AuthorizationError: status.HTTP_403_FORBIDDEN, - UserNotFoundByIdError: status.HTTP_404_NOT_FOUND, - ActivationChangeNotPermittedError: status.HTTP_403_FORBIDDEN, - }, - default_on_error=log_info, - status_code=status.HTTP_204_NO_CONTENT, - dependencies=[Security(cookie_scheme)], - ) - @inject - async def activate_user( - user_id: Annotated[UUID, Path()], - interactor: FromDishka[ActivateUserInteractor], - ) -> None: - request_data = ActivateUserRequest(user_id) - await interactor.execute(request_data) - - return router diff --git a/src/app/presentation/http/controllers/users/create_user.py b/src/app/presentation/http/controllers/users/create_user.py deleted file mode 100644 index c8fe6043..00000000 --- a/src/app/presentation/http/controllers/users/create_user.py +++ /dev/null @@ -1,83 +0,0 @@ -from inspect import getdoc - -from dishka import FromDishka -from dishka.integrations.fastapi import inject -from fastapi import APIRouter, Security, status -from fastapi_error_map import ErrorAwareRouter, rule -from pydantic import BaseModel, ConfigDict, Field - -from app.application.commands.create_user import ( - CreateUserInteractor, - CreateUserRequest, - CreateUserResponse, -) -from app.application.common.exceptions.authorization import AuthorizationError -from app.domain.enums.user_role import UserRole -from app.domain.exceptions.base import DomainTypeError -from app.domain.exceptions.user import ( - RoleAssignmentNotPermittedError, - UsernameAlreadyExistsError, -) -from app.infrastructure.auth.exceptions import AuthenticationError -from app.infrastructure.exceptions.gateway import DataMapperError -from app.infrastructure.exceptions.password_hasher import PasswordHasherBusyError -from app.presentation.http.auth.openapi_marker import cookie_scheme -from app.presentation.http.errors.callbacks import log_error, log_info -from app.presentation.http.errors.translators import ( - ServiceUnavailableTranslator, -) - - -class CreateUserRequestPydantic(BaseModel): - """ - Using a Pydantic model here is generally unnecessary. - It's only implemented to render a specific Swagger UI (OpenAPI) schema. - """ - - model_config = ConfigDict(frozen=True) - - username: str - password: str - role: UserRole = Field(default=UserRole.USER) - - -def create_create_user_router() -> APIRouter: - router = ErrorAwareRouter() - - @router.post( - "/", - description=getdoc(CreateUserInteractor), - error_map={ - AuthenticationError: status.HTTP_401_UNAUTHORIZED, - DataMapperError: rule( - status=status.HTTP_503_SERVICE_UNAVAILABLE, - translator=ServiceUnavailableTranslator(), - on_error=log_error, - ), - AuthorizationError: status.HTTP_403_FORBIDDEN, - DomainTypeError: status.HTTP_400_BAD_REQUEST, - PasswordHasherBusyError: rule( - status=status.HTTP_503_SERVICE_UNAVAILABLE, - translator=ServiceUnavailableTranslator(), - on_error=log_error, - ), - RoleAssignmentNotPermittedError: status.HTTP_422_UNPROCESSABLE_ENTITY, - UsernameAlreadyExistsError: status.HTTP_409_CONFLICT, - }, - default_on_error=log_info, - status_code=status.HTTP_201_CREATED, - dependencies=[Security(cookie_scheme)], - ) - @inject - async def create_user( - request_data_pydantic: CreateUserRequestPydantic, - interactor: FromDishka[CreateUserInteractor], - ) -> CreateUserResponse: - request_data = CreateUserRequest( - username=request_data_pydantic.username, - password=request_data_pydantic.password, - role=request_data_pydantic.role, - ) - return await interactor.execute(request_data) - - return router diff --git a/src/app/presentation/http/controllers/users/deactivate_user.py b/src/app/presentation/http/controllers/users/deactivate_user.py deleted file mode 100644 index 5214d18d..00000000 --- a/src/app/presentation/http/controllers/users/deactivate_user.py +++ /dev/null @@ -1,57 +0,0 @@ -from inspect import getdoc -from typing import Annotated -from uuid import UUID - -from dishka import FromDishka -from dishka.integrations.fastapi import inject -from fastapi import APIRouter, Path, Security, status -from fastapi_error_map import ErrorAwareRouter, rule - -from app.application.commands.deactivate_user import ( - DeactivateUserInteractor, - DeactivateUserRequest, -) -from app.application.common.exceptions.authorization import AuthorizationError -from app.domain.exceptions.user import ( - ActivationChangeNotPermittedError, - UserNotFoundByIdError, -) -from app.infrastructure.auth.exceptions import AuthenticationError -from app.infrastructure.exceptions.gateway import DataMapperError -from app.presentation.http.auth.openapi_marker import cookie_scheme -from app.presentation.http.errors.callbacks import log_error, log_info -from app.presentation.http.errors.translators import ( - ServiceUnavailableTranslator, -) - - -def create_deactivate_user_router() -> APIRouter: - router = ErrorAwareRouter() - - @router.delete( - "/{user_id}/activation", - description=getdoc(DeactivateUserInteractor), - error_map={ - AuthenticationError: status.HTTP_401_UNAUTHORIZED, - DataMapperError: rule( - status=status.HTTP_503_SERVICE_UNAVAILABLE, - translator=ServiceUnavailableTranslator(), - on_error=log_error, - ), - AuthorizationError: status.HTTP_403_FORBIDDEN, - UserNotFoundByIdError: status.HTTP_404_NOT_FOUND, - ActivationChangeNotPermittedError: status.HTTP_403_FORBIDDEN, - }, - default_on_error=log_info, - status_code=status.HTTP_204_NO_CONTENT, - dependencies=[Security(cookie_scheme)], - ) - @inject - async def deactivate_user( - user_id: Annotated[UUID, Path()], - interactor: FromDishka[DeactivateUserInteractor], - ) -> None: - request_data = DeactivateUserRequest(user_id) - await interactor.execute(request_data) - - return router diff --git a/src/app/presentation/http/controllers/users/grant_admin.py b/src/app/presentation/http/controllers/users/grant_admin.py deleted file mode 100644 index 66839575..00000000 --- a/src/app/presentation/http/controllers/users/grant_admin.py +++ /dev/null @@ -1,57 +0,0 @@ -from inspect import getdoc -from typing import Annotated -from uuid import UUID - -from dishka import FromDishka -from dishka.integrations.fastapi import inject -from fastapi import APIRouter, Path, Security, status -from fastapi_error_map import ErrorAwareRouter, rule - -from app.application.commands.grant_admin import ( - GrantAdminInteractor, - GrantAdminRequest, -) -from app.application.common.exceptions.authorization import AuthorizationError -from app.domain.exceptions.user import ( - RoleChangeNotPermittedError, - UserNotFoundByIdError, -) -from app.infrastructure.auth.exceptions import AuthenticationError -from app.infrastructure.exceptions.gateway import DataMapperError -from app.presentation.http.auth.openapi_marker import cookie_scheme -from app.presentation.http.errors.callbacks import log_error, log_info -from app.presentation.http.errors.translators import ( - ServiceUnavailableTranslator, -) - - -def create_grant_admin_router() -> APIRouter: - router = ErrorAwareRouter() - - @router.put( - "/{user_id}/roles/admin", - description=getdoc(GrantAdminInteractor), - error_map={ - AuthenticationError: status.HTTP_401_UNAUTHORIZED, - DataMapperError: rule( - status=status.HTTP_503_SERVICE_UNAVAILABLE, - translator=ServiceUnavailableTranslator(), - on_error=log_error, - ), - AuthorizationError: status.HTTP_403_FORBIDDEN, - UserNotFoundByIdError: status.HTTP_404_NOT_FOUND, - RoleChangeNotPermittedError: status.HTTP_403_FORBIDDEN, - }, - default_on_error=log_info, - status_code=status.HTTP_204_NO_CONTENT, - dependencies=[Security(cookie_scheme)], - ) - @inject - async def grant_admin( - user_id: Annotated[UUID, Path()], - interactor: FromDishka[GrantAdminInteractor], - ) -> None: - request_data = GrantAdminRequest(user_id) - await interactor.execute(request_data) - - return router diff --git a/src/app/presentation/http/controllers/users/list_users.py b/src/app/presentation/http/controllers/users/list_users.py deleted file mode 100644 index 65e5403f..00000000 --- a/src/app/presentation/http/controllers/users/list_users.py +++ /dev/null @@ -1,80 +0,0 @@ -from inspect import getdoc -from typing import Annotated - -from dishka import FromDishka -from dishka.integrations.fastapi import inject -from fastapi import APIRouter, Depends, Security, status -from fastapi_error_map import ErrorAwareRouter, rule -from pydantic import BaseModel, ConfigDict, Field - -from app.application.common.exceptions.authorization import AuthorizationError -from app.application.common.exceptions.query import PaginationError, SortingError -from app.application.common.ports.user_query_gateway import ListUsersQM -from app.application.common.query_params.sorting import SortingOrder -from app.application.queries.list_users import ( - ListUsersQueryService, - ListUsersRequest, -) -from app.infrastructure.auth.exceptions import AuthenticationError -from app.infrastructure.exceptions.gateway import DataMapperError, ReaderError -from app.presentation.http.auth.openapi_marker import cookie_scheme -from app.presentation.http.errors.callbacks import log_error, log_info -from app.presentation.http.errors.translators import ( - ServiceUnavailableTranslator, -) - - -class ListUsersRequestPydantic(BaseModel): - """ - Using a Pydantic model here is generally unnecessary. - It's only implemented to render a specific Swagger UI (OpenAPI) schema. - """ - - model_config = ConfigDict(frozen=True) - - limit: Annotated[int, Field(ge=1)] = 20 - offset: Annotated[int, Field(ge=0)] = 0 - sorting_field: Annotated[str, Field()] = "username" - sorting_order: Annotated[SortingOrder, Field()] = SortingOrder.ASC - - -def create_list_users_router() -> APIRouter: - router = ErrorAwareRouter() - - @router.get( - "/", - description=getdoc(ListUsersQueryService), - error_map={ - AuthenticationError: status.HTTP_401_UNAUTHORIZED, - DataMapperError: rule( - status=status.HTTP_503_SERVICE_UNAVAILABLE, - translator=ServiceUnavailableTranslator(), - on_error=log_error, - ), - AuthorizationError: status.HTTP_403_FORBIDDEN, - PaginationError: status.HTTP_400_BAD_REQUEST, - SortingError: status.HTTP_400_BAD_REQUEST, - ReaderError: rule( - status=status.HTTP_503_SERVICE_UNAVAILABLE, - translator=ServiceUnavailableTranslator(), - on_error=log_error, - ), - }, - default_on_error=log_info, - status_code=status.HTTP_200_OK, - dependencies=[Security(cookie_scheme)], - ) - @inject - async def list_users( - request_data_pydantic: Annotated[ListUsersRequestPydantic, Depends()], - interactor: FromDishka[ListUsersQueryService], - ) -> ListUsersQM: - request_data = ListUsersRequest( - limit=request_data_pydantic.limit, - offset=request_data_pydantic.offset, - sorting_field=request_data_pydantic.sorting_field, - sorting_order=request_data_pydantic.sorting_order, - ) - return await interactor.execute(request_data) - - return router diff --git a/src/app/presentation/http/controllers/users/revoke_admin.py b/src/app/presentation/http/controllers/users/revoke_admin.py deleted file mode 100644 index a394d01e..00000000 --- a/src/app/presentation/http/controllers/users/revoke_admin.py +++ /dev/null @@ -1,57 +0,0 @@ -from inspect import getdoc -from typing import Annotated -from uuid import UUID - -from dishka import FromDishka -from dishka.integrations.fastapi import inject -from fastapi import APIRouter, Path, Security, status -from fastapi_error_map import ErrorAwareRouter, rule - -from app.application.commands.revoke_admin import ( - RevokeAdminInteractor, - RevokeAdminRequest, -) -from app.application.common.exceptions.authorization import AuthorizationError -from app.domain.exceptions.user import ( - RoleChangeNotPermittedError, - UserNotFoundByIdError, -) -from app.infrastructure.auth.exceptions import AuthenticationError -from app.infrastructure.exceptions.gateway import DataMapperError -from app.presentation.http.auth.openapi_marker import cookie_scheme -from app.presentation.http.errors.callbacks import log_error, log_info -from app.presentation.http.errors.translators import ( - ServiceUnavailableTranslator, -) - - -def create_revoke_admin_router() -> APIRouter: - router = ErrorAwareRouter() - - @router.delete( - "/{user_id}/roles/admin", - description=getdoc(RevokeAdminInteractor), - error_map={ - AuthenticationError: status.HTTP_401_UNAUTHORIZED, - DataMapperError: rule( - status=status.HTTP_503_SERVICE_UNAVAILABLE, - translator=ServiceUnavailableTranslator(), - on_error=log_error, - ), - AuthorizationError: status.HTTP_403_FORBIDDEN, - UserNotFoundByIdError: status.HTTP_404_NOT_FOUND, - RoleChangeNotPermittedError: status.HTTP_403_FORBIDDEN, - }, - default_on_error=log_info, - status_code=status.HTTP_204_NO_CONTENT, - dependencies=[Security(cookie_scheme)], - ) - @inject - async def revoke_admin( - user_id: Annotated[UUID, Path()], - interactor: FromDishka[RevokeAdminInteractor], - ) -> None: - request_data = RevokeAdminRequest(user_id) - await interactor.execute(request_data) - - return router diff --git a/src/app/presentation/http/controllers/users/router.py b/src/app/presentation/http/controllers/users/router.py deleted file mode 100644 index 6cbf3b27..00000000 --- a/src/app/presentation/http/controllers/users/router.py +++ /dev/null @@ -1,36 +0,0 @@ -from fastapi import APIRouter - -from app.presentation.http.controllers.users.activate_user import ( - create_activate_user_router, -) -from app.presentation.http.controllers.users.create_user import ( - create_create_user_router, -) -from app.presentation.http.controllers.users.deactivate_user import ( - create_deactivate_user_router, -) -from app.presentation.http.controllers.users.grant_admin import ( - create_grant_admin_router, -) -from app.presentation.http.controllers.users.list_users import create_list_users_router -from app.presentation.http.controllers.users.revoke_admin import ( - create_revoke_admin_router, -) -from app.presentation.http.controllers.users.set_user_password import ( - create_set_user_password_router, -) - - -def create_users_router() -> APIRouter: - router = APIRouter( - prefix="/users", - tags=["Users"], - ) - router.include_router(create_create_user_router()) - router.include_router(create_list_users_router()) - router.include_router(create_set_user_password_router()) - router.include_router(create_grant_admin_router()) - router.include_router(create_revoke_admin_router()) - router.include_router(create_activate_user_router()) - router.include_router(create_deactivate_user_router()) - return router diff --git a/src/app/presentation/http/controllers/users/set_user_password.py b/src/app/presentation/http/controllers/users/set_user_password.py deleted file mode 100644 index 5030af78..00000000 --- a/src/app/presentation/http/controllers/users/set_user_password.py +++ /dev/null @@ -1,67 +0,0 @@ -from inspect import getdoc -from typing import Annotated -from uuid import UUID - -from dishka import FromDishka -from dishka.integrations.fastapi import inject -from fastapi import APIRouter, Body, Path, Security, status -from fastapi_error_map import ErrorAwareRouter, rule - -from app.application.commands.set_user_password import ( - SetUserPasswordInteractor, - SetUserPasswordRequest, -) -from app.application.common.exceptions.authorization import AuthorizationError -from app.domain.exceptions.base import DomainTypeError -from app.domain.exceptions.user import ( - UserNotFoundByIdError, -) -from app.infrastructure.auth.exceptions import AuthenticationError -from app.infrastructure.exceptions.gateway import DataMapperError -from app.infrastructure.exceptions.password_hasher import PasswordHasherBusyError -from app.presentation.http.auth.openapi_marker import cookie_scheme -from app.presentation.http.errors.callbacks import log_error, log_info -from app.presentation.http.errors.translators import ( - ServiceUnavailableTranslator, -) - - -def create_set_user_password_router() -> APIRouter: - router = ErrorAwareRouter() - - @router.put( - "/{user_id}/password", - description=getdoc(SetUserPasswordInteractor), - error_map={ - AuthenticationError: status.HTTP_401_UNAUTHORIZED, - DataMapperError: rule( - status=status.HTTP_503_SERVICE_UNAVAILABLE, - translator=ServiceUnavailableTranslator(), - on_error=log_error, - ), - AuthorizationError: status.HTTP_403_FORBIDDEN, - DomainTypeError: status.HTTP_400_BAD_REQUEST, - UserNotFoundByIdError: status.HTTP_404_NOT_FOUND, - PasswordHasherBusyError: rule( - status=status.HTTP_503_SERVICE_UNAVAILABLE, - translator=ServiceUnavailableTranslator(), - on_error=log_error, - ), - }, - default_on_error=log_info, - status_code=status.HTTP_204_NO_CONTENT, - dependencies=[Security(cookie_scheme)], - ) - @inject - async def set_user_password( - user_id: Annotated[UUID, Path()], - password: Annotated[str, Body()], - interactor: FromDishka[SetUserPasswordInteractor], - ) -> None: - request_data = SetUserPasswordRequest( - user_id=user_id, - password=password, - ) - await interactor.execute(request_data) - - return router diff --git a/src/app/presentation/http/errors/callbacks.py b/src/app/presentation/http/errors/callbacks.py index 7e3e40f6..a9c3e9eb 100644 --- a/src/app/presentation/http/errors/callbacks.py +++ b/src/app/presentation/http/errors/callbacks.py @@ -1,11 +1,7 @@ import logging -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) def log_info(err: Exception) -> None: - log.info(f"Handled exception: {type(err).__name__} β€” {err}") - - -def log_error(err: Exception) -> None: - log.error(f"Handled exception: {type(err).__name__} β€” {err}") + logger.info("Handled exception: %s β€” %s", type(err).__name__, err) diff --git a/src/app/presentation/http/errors/rules.py b/src/app/presentation/http/errors/rules.py new file mode 100644 index 00000000..7667f565 --- /dev/null +++ b/src/app/presentation/http/errors/rules.py @@ -0,0 +1,11 @@ +from typing import Final + +from fastapi_error_map.rules import Rule, rule +from starlette import status + +from app.presentation.http.errors.translators import ServiceUnavailableTranslator + +HTTP_503_SERVICE_UNAVAILABLE_RULE: Final[Rule] = rule( + status=status.HTTP_503_SERVICE_UNAVAILABLE, + translator=ServiceUnavailableTranslator(), +) diff --git a/src/app/presentation/http/errors/translators.py b/src/app/presentation/http/errors/translators.py index e26deb87..221b0b53 100644 --- a/src/app/presentation/http/errors/translators.py +++ b/src/app/presentation/http/errors/translators.py @@ -7,6 +7,4 @@ def error_response_model_cls(self) -> type[SimpleErrorResponseModel]: return SimpleErrorResponseModel def from_error(self, err: Exception) -> SimpleErrorResponseModel: - return SimpleErrorResponseModel( - error="Service temporarily unavailable. Please try again later." - ) + return SimpleErrorResponseModel(error="Service temporarily unavailable. Please try again later.") diff --git a/src/app/infrastructure/auth/session/__init__.py b/src/app/presentation/http/health/__init__.py similarity index 100% rename from src/app/infrastructure/auth/session/__init__.py rename to src/app/presentation/http/health/__init__.py diff --git a/src/app/presentation/http/health/checks.py b/src/app/presentation/http/health/checks.py new file mode 100644 index 00000000..05609069 --- /dev/null +++ b/src/app/presentation/http/health/checks.py @@ -0,0 +1,13 @@ +from sqlalchemy import text +from sqlalchemy.ext.asyncio import AsyncSession + + +class ProbeError(Exception): + pass + + +async def db_check(session: AsyncSession) -> None: + try: + await session.scalar(text("SELECT 1")) + except Exception as e: + raise ProbeError from e diff --git a/src/app/presentation/http/health/router.py b/src/app/presentation/http/health/router.py new file mode 100644 index 00000000..7c9b1e24 --- /dev/null +++ b/src/app/presentation/http/health/router.py @@ -0,0 +1,43 @@ +from dishka import FromDishka +from dishka.integrations.fastapi import inject +from fastapi import APIRouter +from sqlalchemy.ext.asyncio import AsyncSession + +from app.presentation.http.health.checks import db_check + + +class InternalServerError(Exception): + pass + + +def make_health_router(*, debug_mode: bool) -> APIRouter: + router = APIRouter() + + @router.get( + "/livez/", + include_in_schema=False, + ) + async def liveness_probe() -> str: + return "OK" + + @router.get( + "/healthz/", + include_in_schema=False, + ) + @inject + async def readiness_probe( + session: FromDishka[AsyncSession], + ) -> str: + await db_check(session) + return "OK" + + if debug_mode: + + @router.get( + "/http_error/", + include_in_schema=False, + ) + async def generate_http_error() -> None: + raise InternalServerError("Internal Server Error") + + return router diff --git a/src/app/presentation/http/root_router.py b/src/app/presentation/http/root_router.py new file mode 100644 index 00000000..345b9db7 --- /dev/null +++ b/src/app/presentation/http/root_router.py @@ -0,0 +1,20 @@ +from fastapi import APIRouter +from starlette.responses import RedirectResponse + +from app.presentation.http.api_v1_router import make_v1_router +from app.presentation.http.health.router import make_health_router + + +def make_fastapi_root_router(*, debug_mode: bool, cookie_name: str) -> APIRouter: + router = APIRouter() + + @router.get( + "/", + include_in_schema=False, + ) + async def redirect_to_docs() -> RedirectResponse: + return RedirectResponse(url="/docs") + + router.include_router(make_health_router(debug_mode=debug_mode)) + router.include_router(make_v1_router(cookie_name=cookie_name)) + return router diff --git a/src/app/infrastructure/auth/session/ports/__init__.py b/src/app/presentation/http/users/__init__.py similarity index 100% rename from src/app/infrastructure/auth/session/ports/__init__.py rename to src/app/presentation/http/users/__init__.py diff --git a/src/app/presentation/http/users/activate_user.py b/src/app/presentation/http/users/activate_user.py new file mode 100644 index 00000000..7a7e2747 --- /dev/null +++ b/src/app/presentation/http/users/activate_user.py @@ -0,0 +1,42 @@ +from inspect import getdoc +from typing import Annotated +from uuid import UUID + +from dishka import FromDishka +from dishka.integrations.fastapi import inject +from fastapi import APIRouter, Path, status +from fastapi_error_map import ErrorAwareRouter + +from app.core.commands.activate_user import ActivateUser, ActivateUserRequest +from app.core.commands.exceptions import UserNotFoundError +from app.core.common.authorization.exceptions import AuthorizationError +from app.infrastructure.auth_ctx.exceptions import AuthenticationError +from app.infrastructure.exceptions import StorageError +from app.presentation.http.errors.callbacks import log_info +from app.presentation.http.errors.rules import HTTP_503_SERVICE_UNAVAILABLE_RULE + + +def make_activate_user_router() -> APIRouter: + router = ErrorAwareRouter() + + @router.put( + "/{user_id}/activation/", + error_map={ + AuthenticationError: status.HTTP_401_UNAUTHORIZED, + StorageError: HTTP_503_SERVICE_UNAVAILABLE_RULE, + AuthorizationError: status.HTTP_403_FORBIDDEN, + UserNotFoundError: status.HTTP_404_NOT_FOUND, + }, + default_on_error=log_info, + status_code=status.HTTP_204_NO_CONTENT, + description=getdoc(ActivateUser), + ) + @inject + async def activate_user( + user_id: Annotated[UUID, Path()], + interactor: FromDishka[ActivateUser], + ) -> None: + request = ActivateUserRequest(user_id) + await interactor.execute(request) + + return router diff --git a/src/app/presentation/http/users/create_user.py b/src/app/presentation/http/users/create_user.py new file mode 100644 index 00000000..c20752cf --- /dev/null +++ b/src/app/presentation/http/users/create_user.py @@ -0,0 +1,46 @@ +from inspect import getdoc + +from dishka import FromDishka +from dishka.integrations.fastapi import inject +from fastapi import APIRouter +from fastapi_error_map import ErrorAwareRouter +from starlette import status + +from app.core.commands.create_user import CreateUser, CreateUserRequest, CreateUserResponse +from app.core.commands.exceptions import ( + UsernameAlreadyExistsError, +) +from app.core.common.authorization.exceptions import AuthorizationError +from app.core.common.exceptions import BusinessTypeError +from app.infrastructure.adapters.exceptions import PasswordHasherBusyError +from app.infrastructure.auth_ctx.exceptions import AuthenticationError +from app.infrastructure.exceptions import StorageError +from app.presentation.http.errors.callbacks import log_info +from app.presentation.http.errors.rules import HTTP_503_SERVICE_UNAVAILABLE_RULE + + +def make_create_user_router() -> APIRouter: + router = ErrorAwareRouter() + + @router.post( + "/", + error_map={ + AuthenticationError: status.HTTP_401_UNAUTHORIZED, + StorageError: HTTP_503_SERVICE_UNAVAILABLE_RULE, + AuthorizationError: status.HTTP_403_FORBIDDEN, + BusinessTypeError: status.HTTP_400_BAD_REQUEST, + PasswordHasherBusyError: HTTP_503_SERVICE_UNAVAILABLE_RULE, + UsernameAlreadyExistsError: status.HTTP_409_CONFLICT, + }, + default_on_error=log_info, + status_code=status.HTTP_201_CREATED, + description=getdoc(CreateUser), + ) + @inject + async def create_user( + request: CreateUserRequest, + interactor: FromDishka[CreateUser], + ) -> CreateUserResponse: + return await interactor.execute(request) + + return router diff --git a/src/app/presentation/http/users/deactivate_user.py b/src/app/presentation/http/users/deactivate_user.py new file mode 100644 index 00000000..4c9a95c2 --- /dev/null +++ b/src/app/presentation/http/users/deactivate_user.py @@ -0,0 +1,42 @@ +from inspect import getdoc +from typing import Annotated +from uuid import UUID + +from dishka import FromDishka +from dishka.integrations.fastapi import inject +from fastapi import APIRouter, Path, status +from fastapi_error_map import ErrorAwareRouter + +from app.core.commands.deactivate_user import DeactivateUser, DeactivateUserRequest +from app.core.commands.exceptions import UserNotFoundError +from app.core.common.authorization.exceptions import AuthorizationError +from app.infrastructure.auth_ctx.exceptions import AuthenticationError +from app.infrastructure.exceptions import StorageError +from app.presentation.http.errors.callbacks import log_info +from app.presentation.http.errors.rules import HTTP_503_SERVICE_UNAVAILABLE_RULE + + +def make_deactivate_user_router() -> APIRouter: + router = ErrorAwareRouter() + + @router.delete( + "/{user_id}/activation/", + error_map={ + AuthenticationError: status.HTTP_401_UNAUTHORIZED, + StorageError: HTTP_503_SERVICE_UNAVAILABLE_RULE, + AuthorizationError: status.HTTP_403_FORBIDDEN, + UserNotFoundError: status.HTTP_404_NOT_FOUND, + }, + default_on_error=log_info, + status_code=status.HTTP_204_NO_CONTENT, + description=getdoc(DeactivateUser), + ) + @inject + async def deactivate_user( + user_id: Annotated[UUID, Path()], + interactor: FromDishka[DeactivateUser], + ) -> None: + request = DeactivateUserRequest(user_id) + await interactor.execute(request) + + return router diff --git a/src/app/presentation/http/users/grant_admin.py b/src/app/presentation/http/users/grant_admin.py new file mode 100644 index 00000000..7428f03c --- /dev/null +++ b/src/app/presentation/http/users/grant_admin.py @@ -0,0 +1,42 @@ +from inspect import getdoc +from typing import Annotated +from uuid import UUID + +from dishka import FromDishka +from dishka.integrations.fastapi import inject +from fastapi import APIRouter, Path, status +from fastapi_error_map import ErrorAwareRouter + +from app.core.commands.exceptions import UserNotFoundError +from app.core.commands.grant_admin import GrantAdmin, GrantAdminRequest +from app.core.common.authorization.exceptions import AuthorizationError +from app.infrastructure.auth_ctx.exceptions import AuthenticationError +from app.infrastructure.exceptions import StorageError +from app.presentation.http.errors.callbacks import log_info +from app.presentation.http.errors.rules import HTTP_503_SERVICE_UNAVAILABLE_RULE + + +def make_grant_admin_router() -> APIRouter: + router = ErrorAwareRouter() + + @router.put( + "/{user_id}/roles/admin/", + error_map={ + AuthenticationError: status.HTTP_401_UNAUTHORIZED, + StorageError: HTTP_503_SERVICE_UNAVAILABLE_RULE, + AuthorizationError: status.HTTP_403_FORBIDDEN, + UserNotFoundError: status.HTTP_404_NOT_FOUND, + }, + default_on_error=log_info, + status_code=status.HTTP_204_NO_CONTENT, + description=getdoc(GrantAdmin), + ) + @inject + async def grant_admin( + user_id: Annotated[UUID, Path()], + interactor: FromDishka[GrantAdmin], + ) -> None: + request = GrantAdminRequest(user_id) + await interactor.execute(request) + + return router diff --git a/src/app/presentation/http/users/list_users.py b/src/app/presentation/http/users/list_users.py new file mode 100644 index 00000000..6e461b47 --- /dev/null +++ b/src/app/presentation/http/users/list_users.py @@ -0,0 +1,66 @@ +from inspect import getdoc +from typing import Annotated + +from dishka import FromDishka +from dishka.integrations.fastapi import inject +from fastapi import APIRouter, Depends +from fastapi_error_map import ErrorAwareRouter +from pydantic import BaseModel, ConfigDict, Field +from starlette import status + +from app.core.common.authorization.exceptions import AuthorizationError +from app.core.queries.list_users import ListUsers, ListUsersRequest, UserSortingField +from app.core.queries.ports.user_reader import ListUsersQm +from app.core.queries.query_support.exceptions import PaginationError +from app.core.queries.query_support.offset_pagination import OffsetPaginationParams +from app.core.queries.query_support.sorting import SortingOrder +from app.infrastructure.auth_ctx.exceptions import AuthenticationError +from app.infrastructure.exceptions import ReaderError, StorageError +from app.presentation.http.errors.callbacks import log_info +from app.presentation.http.errors.rules import HTTP_503_SERVICE_UNAVAILABLE_RULE + + +class ListUsersRequestSchema(BaseModel): + """ + Using Pydantic model here is generally unnecessary. + It's only implemented to render specific Swagger UI. + """ + + model_config = ConfigDict(frozen=True) + + limit: Annotated[int, Field(ge=1, le=OffsetPaginationParams.MAX_INT32)] = 20 + offset: Annotated[int, Field(ge=0, le=OffsetPaginationParams.MAX_INT32)] = 0 + sorting_field: Annotated[UserSortingField, Field()] = UserSortingField.UPDATED_AT + sorting_order: Annotated[SortingOrder, Field()] = SortingOrder.DESC + + +def make_list_users_router() -> APIRouter: + router = ErrorAwareRouter() + + @router.get( + "/", + error_map={ + AuthenticationError: status.HTTP_401_UNAUTHORIZED, + StorageError: HTTP_503_SERVICE_UNAVAILABLE_RULE, + AuthorizationError: status.HTTP_403_FORBIDDEN, + PaginationError: status.HTTP_400_BAD_REQUEST, + ReaderError: HTTP_503_SERVICE_UNAVAILABLE_RULE, + }, + default_on_error=log_info, + status_code=status.HTTP_200_OK, + description=getdoc(ListUsers), + ) + @inject + async def list_users( + request_schema: Annotated[ListUsersRequestSchema, Depends()], + interactor: FromDishka[ListUsers], + ) -> ListUsersQm: + request = ListUsersRequest( + limit=request_schema.limit, + offset=request_schema.offset, + sorting_field=request_schema.sorting_field, + sorting_order=request_schema.sorting_order, + ) + return await interactor.execute(request) + + return router diff --git a/src/app/presentation/http/users/revoke_admin.py b/src/app/presentation/http/users/revoke_admin.py new file mode 100644 index 00000000..46f0df6b --- /dev/null +++ b/src/app/presentation/http/users/revoke_admin.py @@ -0,0 +1,42 @@ +from inspect import getdoc +from typing import Annotated +from uuid import UUID + +from dishka import FromDishka +from dishka.integrations.fastapi import inject +from fastapi import APIRouter, Path, status +from fastapi_error_map import ErrorAwareRouter + +from app.core.commands.exceptions import UserNotFoundError +from app.core.commands.revoke_admin import RevokeAdmin, RevokeAdminRequest +from app.core.common.authorization.exceptions import AuthorizationError +from app.infrastructure.auth_ctx.exceptions import AuthenticationError +from app.infrastructure.exceptions import StorageError +from app.presentation.http.errors.callbacks import log_info +from app.presentation.http.errors.rules import HTTP_503_SERVICE_UNAVAILABLE_RULE + + +def make_revoke_admin_router() -> APIRouter: + router = ErrorAwareRouter() + + @router.delete( + "/{user_id}/roles/admin/", + error_map={ + AuthenticationError: status.HTTP_401_UNAUTHORIZED, + StorageError: HTTP_503_SERVICE_UNAVAILABLE_RULE, + AuthorizationError: status.HTTP_403_FORBIDDEN, + UserNotFoundError: status.HTTP_404_NOT_FOUND, + }, + default_on_error=log_info, + status_code=status.HTTP_204_NO_CONTENT, + description=getdoc(RevokeAdmin), + ) + @inject + async def revoke_admin( + user_id: Annotated[UUID, Path()], + interactor: FromDishka[RevokeAdmin], + ) -> None: + request = RevokeAdminRequest(user_id) + await interactor.execute(request) + + return router diff --git a/src/app/presentation/http/users/router.py b/src/app/presentation/http/users/router.py new file mode 100644 index 00000000..ae2f39a2 --- /dev/null +++ b/src/app/presentation/http/users/router.py @@ -0,0 +1,26 @@ +from fastapi import APIRouter, Depends +from fastapi.security import APIKeyCookie + +from app.presentation.http.users.activate_user import make_activate_user_router +from app.presentation.http.users.create_user import make_create_user_router +from app.presentation.http.users.deactivate_user import make_deactivate_user_router +from app.presentation.http.users.grant_admin import make_grant_admin_router +from app.presentation.http.users.list_users import make_list_users_router +from app.presentation.http.users.revoke_admin import make_revoke_admin_router +from app.presentation.http.users.set_user_password import make_set_user_password_router + + +def make_users_router(*, cookie_name: str) -> APIRouter: + router = APIRouter( + prefix="/users", + tags=["Users"], + dependencies=[Depends(APIKeyCookie(name=cookie_name))], + ) + router.include_router(make_create_user_router()) + router.include_router(make_list_users_router()) + router.include_router(make_set_user_password_router()) + router.include_router(make_grant_admin_router()) + router.include_router(make_revoke_admin_router()) + router.include_router(make_activate_user_router()) + router.include_router(make_deactivate_user_router()) + return router diff --git a/src/app/presentation/http/users/set_user_password.py b/src/app/presentation/http/users/set_user_password.py new file mode 100644 index 00000000..1a5e6dd5 --- /dev/null +++ b/src/app/presentation/http/users/set_user_password.py @@ -0,0 +1,63 @@ +from inspect import getdoc +from typing import Annotated +from uuid import UUID + +from dishka import FromDishka +from dishka.integrations.fastapi import inject +from fastapi import APIRouter, Path +from fastapi_error_map import ErrorAwareRouter +from pydantic import BaseModel, ConfigDict +from starlette import status + +from app.core.commands.exceptions import UserNotFoundError +from app.core.commands.set_user_password import SetUserPassword, SetUserPasswordRequest +from app.core.common.authorization.exceptions import AuthorizationError +from app.core.common.exceptions import BusinessTypeError +from app.infrastructure.adapters.exceptions import PasswordHasherBusyError +from app.infrastructure.auth_ctx.exceptions import AuthenticationError +from app.infrastructure.exceptions import StorageError +from app.presentation.http.errors.callbacks import log_info +from app.presentation.http.errors.rules import HTTP_503_SERVICE_UNAVAILABLE_RULE + + +class SetUserPasswordRequestSchema(BaseModel): + """ + Using Pydantic model here is generally unnecessary. + It's only implemented to render specific Swagger UI. + """ + + model_config = ConfigDict(frozen=True) + + password: str + + +def make_set_user_password_router() -> APIRouter: + router = ErrorAwareRouter() + + @router.put( + "/{user_id}/password/", + error_map={ + AuthenticationError: status.HTTP_401_UNAUTHORIZED, + StorageError: HTTP_503_SERVICE_UNAVAILABLE_RULE, + AuthorizationError: status.HTTP_403_FORBIDDEN, + BusinessTypeError: status.HTTP_400_BAD_REQUEST, + UserNotFoundError: status.HTTP_404_NOT_FOUND, + PasswordHasherBusyError: HTTP_503_SERVICE_UNAVAILABLE_RULE, + }, + default_on_error=log_info, + status_code=status.HTTP_204_NO_CONTENT, + description=getdoc(SetUserPassword), + ) + @inject + async def set_user_password( + user_id: Annotated[UUID, Path()], + request_schema: SetUserPasswordRequestSchema, + interactor: FromDishka[SetUserPassword], + ) -> None: + request = SetUserPasswordRequest( + user_id=user_id, + password=request_schema.password, + ) + await interactor.execute(request) + + return router diff --git a/src/app/run.py b/src/app/run.py deleted file mode 100644 index 3eb7c01b..00000000 --- a/src/app/run.py +++ /dev/null @@ -1,36 +0,0 @@ -from dishka import Provider -from dishka.integrations.fastapi import setup_dishka -from fastapi import FastAPI - -from app.setup.app_factory import create_ioc_container, create_web_app -from app.setup.config.logs import configure_logging -from app.setup.config.settings import AppSettings, load_settings - - -def make_app( - *di_providers: Provider, - settings: AppSettings | None = None, -) -> FastAPI: - """Pass providers to override existing ones for testing.""" - if settings is None: - configure_logging() - settings = load_settings() - - configure_logging(level=settings.logs.level) - - app: FastAPI = create_web_app() - container = create_ioc_container(settings, *di_providers) - setup_dishka(container, app) - - return app - - -if __name__ == "__main__": - import uvicorn - - uvicorn.run( - app=make_app(), - port=8000, - reload=False, - loop="uvloop", - ) diff --git a/src/app/setup/app_factory.py b/src/app/setup/app_factory.py deleted file mode 100644 index 976c74e2..00000000 --- a/src/app/setup/app_factory.py +++ /dev/null @@ -1,48 +0,0 @@ -from collections.abc import AsyncIterator -from contextlib import asynccontextmanager - -from dishka import AsyncContainer, Provider, make_async_container -from fastapi import FastAPI -from fastapi.responses import ORJSONResponse - -from app.infrastructure.persistence_sqla.mappings.all import map_tables -from app.presentation.http.auth.asgi_middleware import ( - ASGIAuthMiddleware, -) -from app.presentation.http.controllers.root_router import create_root_router -from app.setup.config.settings import AppSettings -from app.setup.ioc.provider_registry import get_providers - - -def create_ioc_container( - settings: AppSettings, - *di_providers: Provider, -) -> AsyncContainer: - return make_async_container( - *get_providers(), - *di_providers, - context={AppSettings: settings}, - ) - - -def create_web_app() -> FastAPI: - app = FastAPI( - lifespan=lifespan, - default_response_class=ORJSONResponse, - ) - # https://github.com/encode/starlette/discussions/2451 - app.add_middleware(ASGIAuthMiddleware) - # Good place to register global exception handlers - app.include_router(create_root_router()) - return app - - -@asynccontextmanager -async def lifespan(app: FastAPI) -> AsyncIterator[None]: - # https://dishka.readthedocs.io/en/stable/integrations/fastapi.html - container = app.state.dishka_container - try: - map_tables() - yield - finally: - await container.close() diff --git a/src/app/setup/config/database.py b/src/app/setup/config/database.py deleted file mode 100644 index 7014d2c8..00000000 --- a/src/app/setup/config/database.py +++ /dev/null @@ -1,51 +0,0 @@ -import os -from typing import Final - -from pydantic import BaseModel, Field, PostgresDsn, field_validator - -PORT_MIN: Final[int] = 1 -PORT_MAX: Final[int] = 65535 - - -class PostgresSettings(BaseModel): - user: str = Field(alias="USER") - password: str = Field(alias="PASSWORD") - db: str = Field(alias="DB") - host: str = Field(alias="HOST") - port: int = Field(alias="PORT") - driver: str = Field(alias="DRIVER") - - @field_validator("host") - @classmethod - def override_host_from_env(cls, v: str) -> str: - postgres_host_env = os.environ.get("POSTGRES_HOST") - if postgres_host_env: - return postgres_host_env - return v - - @field_validator("port") - @classmethod - def validate_port_range(cls, v: int) -> int: - if not PORT_MIN <= v <= PORT_MAX: - raise ValueError(f"Port must be between {PORT_MIN} and {PORT_MAX}") - return v - - @property - def dsn(self) -> str: - return str( - PostgresDsn.build( - scheme=f"postgresql+{self.driver}", - username=self.user, - password=self.password, - host=self.host, - port=self.port, - path=self.db, - ), - ) - - -class SqlaEngineSettings(BaseModel): - echo: bool = Field(alias="ECHO") - echo_pool: bool = Field(alias="ECHO_POOL") - pool_size: int = Field(alias="POOL_SIZE") - max_overflow: int = Field(alias="MAX_OVERFLOW") diff --git a/src/app/setup/config/loader.py b/src/app/setup/config/loader.py deleted file mode 100644 index d0349b1d..00000000 --- a/src/app/setup/config/loader.py +++ /dev/null @@ -1,104 +0,0 @@ -import logging -import os -import tomllib -from collections.abc import Mapping -from enum import StrEnum -from pathlib import Path -from types import MappingProxyType -from typing import Any, Final - -ConfigDict = dict[str, Any] - -log = logging.getLogger(__name__) - -ENV_VAR_NAME: Final[str] = "APP_ENV" - - -class ValidEnvs(StrEnum): - """ - Values should reflect actual directory names. - """ - - LOCAL = "local" - DEV = "dev" - PROD = "prod" - - -class DirContents(StrEnum): - """ - Values should reflect actual file names. - """ - - CONFIG_NAME = "config.toml" - SECRETS_NAME = ".secrets.toml" - EXPORT_NAME = "export.toml" - DOTENV_NAME = ".env" - - -BASE_DIR_PATH: Final[Path] = Path(__file__).resolve().parents[4] -CONFIG_PATH: Final[Path] = BASE_DIR_PATH / "config" - -ENV_TO_DIR_PATHS: Final[Mapping[ValidEnvs, Path]] = MappingProxyType({ - ValidEnvs.LOCAL: CONFIG_PATH / ValidEnvs.LOCAL, - ValidEnvs.DEV: CONFIG_PATH / ValidEnvs.DEV, - ValidEnvs.PROD: CONFIG_PATH / ValidEnvs.PROD, -}) - - -def validate_env(env: str | None) -> ValidEnvs: - if env is None: - raise ValueError(f"{ENV_VAR_NAME} is not set.") - try: - return ValidEnvs(env) - except ValueError as err: - valid_values = ", ".join(f"'{e}'" for e in ValidEnvs) - raise ValueError( - f"Invalid {ENV_VAR_NAME}: '{env}'. Must be one of: {valid_values}.", - ) from err - - -def get_current_env() -> ValidEnvs: - return validate_env(os.getenv(ENV_VAR_NAME)) - - -def load_full_config( - env: ValidEnvs, - dir_paths: Mapping[ValidEnvs, Path] = ENV_TO_DIR_PATHS, - main_config: DirContents = DirContents.CONFIG_NAME, - secrets_config: DirContents = DirContents.SECRETS_NAME, -) -> ConfigDict: - log.info("Reading config for environment: '%s'", env) - config = read_config(env=env, config=main_config, dir_paths=dir_paths) - try: - secrets = read_config(env=env, config=secrets_config, dir_paths=dir_paths) - except FileNotFoundError: - log.warning("Secrets file not found. Full config will not contain secrets.") - return config - return merge_dicts(dict1=config, dict2=secrets) - - -def read_config( - env: ValidEnvs, - dir_paths: Mapping[ValidEnvs, Path], - config: DirContents, -) -> ConfigDict: - dir_path = dir_paths.get(env) - if dir_path is None: - raise FileNotFoundError(f"No directory path configured for environment: {env}") - file_path = dir_path / config - if not file_path.is_file(): - raise FileNotFoundError( - f"The file does not exist at the specified path: {file_path}", - ) - with file_path.open(mode="rb") as f: - return tomllib.load(f) - - -def merge_dicts(*, dict1: ConfigDict, dict2: ConfigDict) -> ConfigDict: - result = dict1.copy() - for key, value in dict2.items(): - if key in result and isinstance(result[key], dict) and isinstance(value, dict): - result[key] = merge_dicts(dict1=result[key], dict2=value) - else: - result[key] = value - return result diff --git a/src/app/setup/config/logs.py b/src/app/setup/config/logs.py deleted file mode 100644 index 90cfcc3e..00000000 --- a/src/app/setup/config/logs.py +++ /dev/null @@ -1,42 +0,0 @@ -import logging -from enum import StrEnum -from typing import Final - -from pydantic import BaseModel, Field - - -class LoggingLevel(StrEnum): - DEBUG = "DEBUG" - INFO = "INFO" - WARNING = "WARNING" - ERROR = "ERROR" - CRITICAL = "CRITICAL" - - -DEFAULT_LOG_LEVEL: Final[LoggingLevel] = LoggingLevel.INFO - -FMT: Final[str] = ( - "[%(asctime)s.%(msecs)03d] " - "[%(threadName)s] " - "%(funcName)20s " - "%(module)s:%(lineno)d " - "%(levelname)-8s - " - "%(message)s" -) -DATEFMT: Final[str] = "%Y-%m-%d %H:%M:%S" - - -def configure_logging( - *, - level: LoggingLevel = DEFAULT_LOG_LEVEL, -) -> None: - logging.basicConfig( - level=level, - datefmt=DATEFMT, - format=FMT, - force=True, - ) - - -class LoggingSettings(BaseModel): - level: LoggingLevel = Field(alias="LEVEL") diff --git a/src/app/setup/config/security.py b/src/app/setup/config/security.py deleted file mode 100644 index 21ae40e0..00000000 --- a/src/app/setup/config/security.py +++ /dev/null @@ -1,50 +0,0 @@ -from datetime import timedelta -from typing import Any, Literal - -from pydantic import BaseModel, Field, field_validator - - -class AuthSettings(BaseModel): - jwt_secret: str = Field(alias="JWT_SECRET", min_length=32) - jwt_algorithm: Literal[ - "HS256", - "HS384", - "HS512", - "RS256", - "RS384", - "RS512", - ] = Field(alias="JWT_ALGORITHM") - session_ttl_min: timedelta = Field(alias="SESSION_TTL_MIN") - session_refresh_threshold: float = Field( - gt=0, - lt=1, - alias="SESSION_REFRESH_THRESHOLD", - ) - - @field_validator("session_ttl_min", mode="before") - @classmethod - def convert_session_ttl_min(cls, v: Any) -> timedelta: - if not isinstance(v, (int, float)): - raise ValueError("SESSION_TTL_MIN must be a number (n of minutes, n >= 1).") - if v < 1: - raise ValueError("SESSION_TTL_MIN must be at least 1 (n of minutes).") - return timedelta(minutes=v) - - -class CookiesSettings(BaseModel): - secure: bool = Field(alias="SECURE") - - -class PasswordSettings(BaseModel): - pepper: str = Field(alias="PEPPER", min_length=32) - hasher_work_factor: int = Field(alias="HASHER_WORK_FACTOR", ge=10) - hasher_max_threads: int = Field(alias="HASHER_MAX_THREADS", ge=1) - hasher_semaphore_wait_timeout_s: float = Field( - alias="HASHER_SEMAPHORE_WAIT_TIMEOUT_S", gt=0 - ) - - -class SecuritySettings(BaseModel): - auth: AuthSettings - cookies: CookiesSettings - password: PasswordSettings diff --git a/src/app/setup/config/settings.py b/src/app/setup/config/settings.py deleted file mode 100644 index e3bcd2fa..00000000 --- a/src/app/setup/config/settings.py +++ /dev/null @@ -1,22 +0,0 @@ -from pydantic import ( - BaseModel, -) - -from app.setup.config.database import PostgresSettings, SqlaEngineSettings -from app.setup.config.loader import ValidEnvs, get_current_env, load_full_config -from app.setup.config.logs import LoggingSettings -from app.setup.config.security import SecuritySettings - - -class AppSettings(BaseModel): - postgres: PostgresSettings - sqla: SqlaEngineSettings - security: SecuritySettings - logs: LoggingSettings - - -def load_settings(env: ValidEnvs | None = None) -> AppSettings: - if env is None: - env = get_current_env() - raw_config = load_full_config(env=env) - return AppSettings.model_validate(raw_config) diff --git a/src/app/setup/ioc/application.py b/src/app/setup/ioc/application.py deleted file mode 100644 index 24f0f084..00000000 --- a/src/app/setup/ioc/application.py +++ /dev/null @@ -1,66 +0,0 @@ -from dishka import Provider, Scope, provide, provide_all - -from app.application.commands.activate_user import ActivateUserInteractor -from app.application.commands.create_user import CreateUserInteractor -from app.application.commands.deactivate_user import DeactivateUserInteractor -from app.application.commands.grant_admin import GrantAdminInteractor -from app.application.commands.revoke_admin import RevokeAdminInteractor -from app.application.commands.set_user_password import SetUserPasswordInteractor -from app.application.common.ports.access_revoker import AccessRevoker -from app.application.common.ports.flusher import Flusher -from app.application.common.ports.identity_provider import IdentityProvider -from app.application.common.ports.transaction_manager import ( - TransactionManager, -) -from app.application.common.ports.user_command_gateway import UserCommandGateway -from app.application.common.ports.user_query_gateway import UserQueryGateway -from app.application.common.services.current_user import CurrentUserService -from app.application.queries.list_users import ListUsersQueryService -from app.infrastructure.adapters.main_flusher_sqla import SqlaMainFlusher -from app.infrastructure.adapters.main_transaction_manager_sqla import ( - SqlaMainTransactionManager, -) -from app.infrastructure.adapters.user_data_mapper_sqla import ( - SqlaUserDataMapper, -) -from app.infrastructure.adapters.user_reader_sqla import SqlaUserReader -from app.infrastructure.auth.adapters.access_revoker import ( - AuthSessionAccessRevoker, -) -from app.infrastructure.auth.adapters.identity_provider import ( - AuthSessionIdentityProvider, -) - - -class ApplicationProvider(Provider): - scope = Scope.REQUEST - - # Services - services = provide_all( - CurrentUserService, - ) - - # Ports Persistence - tx_manager = provide(SqlaMainTransactionManager, provides=TransactionManager) - flusher = provide(SqlaMainFlusher, provides=Flusher) - user_command_gateway = provide(SqlaUserDataMapper, provides=UserCommandGateway) - user_query_gateway = provide(SqlaUserReader, provides=UserQueryGateway) - - # Ports Auth - access_revoker = provide(AuthSessionAccessRevoker, provides=AccessRevoker) - identity_provider = provide(AuthSessionIdentityProvider, provides=IdentityProvider) - - # Commands - commands = provide_all( - ActivateUserInteractor, - SetUserPasswordInteractor, - CreateUserInteractor, - DeactivateUserInteractor, - GrantAdminInteractor, - RevokeAdminInteractor, - ) - - # Queries - query_services = provide_all( - ListUsersQueryService, - ) diff --git a/src/app/setup/ioc/domain.py b/src/app/setup/ioc/domain.py deleted file mode 100644 index 8042fe72..00000000 --- a/src/app/setup/ioc/domain.py +++ /dev/null @@ -1,40 +0,0 @@ -from dishka import Provider, Scope, provide, provide_all - -from app.domain.ports.password_hasher import PasswordHasher -from app.domain.ports.user_id_generator import UserIdGenerator -from app.domain.services.user import UserService -from app.infrastructure.adapters.password_hasher_bcrypt import ( - BcryptPasswordHasher, -) -from app.infrastructure.adapters.types import HasherSemaphore, HasherThreadPoolExecutor -from app.infrastructure.adapters.user_id_generator_uuid import ( - UuidUserIdGenerator, -) -from app.setup.config.security import SecuritySettings - - -class DomainProvider(Provider): - scope = Scope.APP - - # Services - user_service = provide_all( - UserService, - ) - - # Ports - user_id_generator = provide(UuidUserIdGenerator, provides=UserIdGenerator) - - @provide - def provide_password_hasher( - self, - security: SecuritySettings, - executor: HasherThreadPoolExecutor, - semaphore: HasherSemaphore, - ) -> PasswordHasher: - return BcryptPasswordHasher( - pepper=security.password.pepper.encode(), - work_factor=security.password.hasher_work_factor, - executor=executor, - semaphore=semaphore, - semaphore_wait_timeout_s=security.password.hasher_semaphore_wait_timeout_s, - ) diff --git a/src/app/setup/ioc/infrastructure.py b/src/app/setup/ioc/infrastructure.py deleted file mode 100644 index 96ace964..00000000 --- a/src/app/setup/ioc/infrastructure.py +++ /dev/null @@ -1,182 +0,0 @@ -import asyncio -import logging -from collections.abc import AsyncIterator, Iterator -from concurrent.futures import ThreadPoolExecutor -from typing import cast - -from dishka import Provider, Scope, provide, provide_all -from sqlalchemy.ext.asyncio import ( - AsyncEngine, - AsyncSession, - async_sessionmaker, - create_async_engine, -) - -from app.infrastructure.adapters.types import ( - HasherSemaphore, - HasherThreadPoolExecutor, - MainAsyncSession, -) -from app.infrastructure.auth.adapters.data_mapper_sqla import ( - SqlaAuthSessionDataMapper, -) -from app.infrastructure.auth.adapters.transaction_manager_sqla import ( - SqlaAuthSessionTransactionManager, -) -from app.infrastructure.auth.adapters.types import AuthAsyncSession -from app.infrastructure.auth.handlers.change_password import ( - ChangePasswordHandler, -) -from app.infrastructure.auth.handlers.log_in import LogInHandler -from app.infrastructure.auth.handlers.log_out import LogOutHandler -from app.infrastructure.auth.handlers.sign_up import SignUpHandler -from app.infrastructure.auth.session.id_generator_str import ( - StrAuthSessionIdGenerator, -) -from app.infrastructure.auth.session.ports.gateway import AuthSessionGateway -from app.infrastructure.auth.session.ports.transaction_manager import ( - AuthSessionTransactionManager, -) -from app.infrastructure.auth.session.ports.transport import AuthSessionTransport -from app.infrastructure.auth.session.service import AuthSessionService -from app.infrastructure.auth.session.timer_utc import UtcAuthSessionTimer -from app.presentation.http.auth.adapters.session_transport_jwt_cookie import ( - JwtCookieAuthSessionTransport, -) -from app.setup.config.database import PostgresSettings, SqlaEngineSettings -from app.setup.config.security import SecuritySettings - -log = logging.getLogger(__name__) - - -class MainAdaptersProvider(Provider): - scope = Scope.APP - - @provide - def provide_hasher_threadpool_executor( - self, - security: SecuritySettings, - ) -> Iterator[HasherThreadPoolExecutor]: - executor = HasherThreadPoolExecutor( - ThreadPoolExecutor( - max_workers=security.password.hasher_max_threads, - thread_name_prefix="bcrypt", - ) - ) - yield executor - log.debug("Disposing hasher threadpool executor...") - executor.shutdown(wait=True, cancel_futures=True) - log.debug("Hasher threadpool executor is disposed.") - - @provide - def provide_hasher_semaphore(self, security: SecuritySettings) -> HasherSemaphore: - return HasherSemaphore(asyncio.Semaphore(security.password.hasher_max_threads)) - - -class PersistenceSqlaProvider(Provider): - @provide(scope=Scope.APP) - async def provide_async_engine( - self, - postgres: PostgresSettings, - sqla_engine: SqlaEngineSettings, - ) -> AsyncIterator[AsyncEngine]: - async_engine = create_async_engine( - url=postgres.dsn, - echo=sqla_engine.echo, - echo_pool=sqla_engine.echo_pool, - pool_size=sqla_engine.pool_size, - max_overflow=sqla_engine.max_overflow, - connect_args={"connect_timeout": 5}, - pool_pre_ping=True, - ) - log.debug("Async engine created with DSN: %s", postgres.dsn) - yield async_engine - log.debug("Disposing async engine...") - await async_engine.dispose() - log.debug("Engine is disposed.") - - @provide(scope=Scope.APP) - def provide_async_session_factory( - self, - engine: AsyncEngine, - ) -> async_sessionmaker[AsyncSession]: - async_session_factory = async_sessionmaker( - bind=engine, - class_=AsyncSession, - autoflush=False, - expire_on_commit=False, - ) - log.debug("Async session maker initialized.") - return async_session_factory - - @provide(scope=Scope.REQUEST) - async def provide_main_async_session( - self, - async_session_factory: async_sessionmaker[AsyncSession], - ) -> AsyncIterator[MainAsyncSession]: - """Provides UoW (AsyncSession) for the main context.""" - log.debug("Starting Main async session...") - async with async_session_factory() as session: - log.debug("Main async session started.") - yield cast(MainAsyncSession, session) - log.debug("Closing Main async session.") - log.debug("Main async session closed.") - - @provide(scope=Scope.REQUEST) - async def provide_auth_async_session( - self, - async_session_factory: async_sessionmaker[AsyncSession], - ) -> AsyncIterator[AuthAsyncSession]: - """Provides UoW (AsyncSession) for the auth context.""" - log.debug("Starting Auth async session...") - async with async_session_factory() as session: - log.debug("Auth async session started.") - yield cast(AuthAsyncSession, session) - log.debug("Closing Auth async session.") - log.debug("Auth async session closed.") - - -class AuthSessionProvider(Provider): - scope = Scope.REQUEST - - service = provide(AuthSessionService) - - # Ports - id_generator = provide(StrAuthSessionIdGenerator, scope=Scope.APP) - - @provide(scope=Scope.APP) - def provide_utc_auth_session_timer( - self, - security: SecuritySettings, - ) -> UtcAuthSessionTimer: - return UtcAuthSessionTimer( - ttl_min=security.auth.session_ttl_min, - refresh_threshold=security.auth.session_refresh_threshold, - ) - - gateway = provide(SqlaAuthSessionDataMapper, provides=AuthSessionGateway) - transport = provide(JwtCookieAuthSessionTransport, provides=AuthSessionTransport) - tx_manager = provide( - SqlaAuthSessionTransactionManager, - provides=AuthSessionTransactionManager, - ) - - -class AuthHandlersProvider(Provider): - scope = Scope.REQUEST - - handlers = provide_all( - SignUpHandler, - LogInHandler, - ChangePasswordHandler, - LogOutHandler, - ) - - -def infrastructure_providers() -> tuple[Provider, ...]: - return ( - MainAdaptersProvider(), - PersistenceSqlaProvider(), - AuthSessionProvider(), - AuthHandlersProvider(), - ) diff --git a/src/app/setup/ioc/presentation.py b/src/app/setup/ioc/presentation.py deleted file mode 100644 index fff2d4b9..00000000 --- a/src/app/setup/ioc/presentation.py +++ /dev/null @@ -1,28 +0,0 @@ -from dishka import Provider, Scope, from_context, provide -from starlette.requests import Request - -from app.presentation.http.auth.access_token_processor_jwt import ( - JwtAccessTokenProcessor, -) -from app.presentation.http.auth.cookie_params import CookieParams -from app.setup.config.security import SecuritySettings - - -class PresentationProvider(Provider): - scope = Scope.REQUEST - - request = from_context(provides=Request) - - @provide - def provide_access_token_processor( - self, - security: SecuritySettings, - ) -> JwtAccessTokenProcessor: - return JwtAccessTokenProcessor( - secret=security.auth.jwt_secret, - algorithm=security.auth.jwt_algorithm, - ) - - @provide - def provide_cookie_params(self, security: SecuritySettings) -> CookieParams: - return CookieParams(secure=security.cookies.secure) diff --git a/src/app/setup/ioc/provider_registry.py b/src/app/setup/ioc/provider_registry.py deleted file mode 100644 index 9d88a47e..00000000 --- a/src/app/setup/ioc/provider_registry.py +++ /dev/null @@ -1,19 +0,0 @@ -from collections.abc import Iterable - -from dishka import Provider - -from app.setup.ioc.application import ApplicationProvider -from app.setup.ioc.domain import DomainProvider -from app.setup.ioc.infrastructure import infrastructure_providers -from app.setup.ioc.presentation import PresentationProvider -from app.setup.ioc.settings import SettingsProvider - - -def get_providers() -> Iterable[Provider]: - return ( - DomainProvider(), - ApplicationProvider(), - *infrastructure_providers(), - PresentationProvider(), - SettingsProvider(), - ) diff --git a/src/app/setup/ioc/settings.py b/src/app/setup/ioc/settings.py deleted file mode 100644 index 617e7394..00000000 --- a/src/app/setup/ioc/settings.py +++ /dev/null @@ -1,28 +0,0 @@ -from dishka import Provider, Scope, from_context, provide - -from app.setup.config.database import PostgresSettings, SqlaEngineSettings -from app.setup.config.logs import LoggingSettings -from app.setup.config.security import SecuritySettings -from app.setup.config.settings import AppSettings - - -class SettingsProvider(Provider): - scope = Scope.APP - - settings = from_context(AppSettings) - - @provide - def postgres(self, settings: AppSettings) -> PostgresSettings: - return settings.postgres - - @provide - def sqla_engine(self, settings: AppSettings) -> SqlaEngineSettings: - return settings.sqla - - @provide - def security(self, settings: AppSettings) -> SecuritySettings: - return settings.security - - @provide - def logs(self, settings: AppSettings) -> LoggingSettings: - return settings.logs diff --git a/tests/app/integration/setup/test_cfg_loader.py b/tests/app/integration/setup/test_cfg_loader.py deleted file mode 100644 index d282a425..00000000 --- a/tests/app/integration/setup/test_cfg_loader.py +++ /dev/null @@ -1,5 +0,0 @@ -from app.setup.config.loader import BASE_DIR_PATH - - -def test_base_dir_points_to_root() -> None: - assert (BASE_DIR_PATH / "pyproject.toml").exists() diff --git a/tests/app/unit/application/authz_service/test_permissions.py b/tests/app/unit/application/authz_service/test_permissions.py deleted file mode 100644 index 3ada833b..00000000 --- a/tests/app/unit/application/authz_service/test_permissions.py +++ /dev/null @@ -1,113 +0,0 @@ -import pytest - -from app.application.common.services.authorization.permissions import ( - CanManageRole, - CanManageSelf, - CanManageSubordinate, - RoleManagementContext, - UserManagementContext, -) -from app.domain.enums.user_role import UserRole -from tests.app.unit.factories.user_entity import create_user -from tests.app.unit.factories.value_objects import create_user_id - - -def test_can_manage_self() -> None: - user_id = create_user_id() - subject = create_user(user_id=user_id) - target = create_user(user_id=user_id) - context = UserManagementContext(subject=subject, target=target) - sut = CanManageSelf() - - assert sut.is_satisfied_by(context) - - -def test_cannot_manage_another_user() -> None: - subject_id = create_user_id() - subject = create_user(user_id=subject_id) - target_id = create_user_id() - target = create_user(user_id=target_id) - context = UserManagementContext(subject=subject, target=target) - sut = CanManageSelf() - - assert not sut.is_satisfied_by(context) - - -@pytest.mark.parametrize( - ("subject_role", "target_role"), - [ - (UserRole.SUPER_ADMIN, UserRole.ADMIN), - (UserRole.SUPER_ADMIN, UserRole.USER), - (UserRole.ADMIN, UserRole.USER), - ], -) -def test_can_manage_subordinate( - subject_role: UserRole, - target_role: UserRole, -) -> None: - subject = create_user(role=subject_role) - target = create_user(role=target_role) - context = UserManagementContext(subject=subject, target=target) - sut = CanManageSubordinate() - - assert sut.is_satisfied_by(context) - - -@pytest.mark.parametrize( - ("subject_role", "target_role"), - [ - (UserRole.SUPER_ADMIN, UserRole.SUPER_ADMIN), - (UserRole.ADMIN, UserRole.SUPER_ADMIN), - (UserRole.ADMIN, UserRole.ADMIN), - (UserRole.USER, UserRole.ADMIN), - ], -) -def test_cannot_manage_non_subordinate( - subject_role: UserRole, - target_role: UserRole, -) -> None: - subject = create_user(role=subject_role) - target = create_user(role=target_role) - context = UserManagementContext(subject=subject, target=target) - sut = CanManageSubordinate() - - assert not sut.is_satisfied_by(context) - - -@pytest.mark.parametrize( - ("subject_role", "target_role"), - [ - (UserRole.SUPER_ADMIN, UserRole.ADMIN), - (UserRole.SUPER_ADMIN, UserRole.USER), - (UserRole.ADMIN, UserRole.USER), - ], -) -def test_can_manage_role( - subject_role: UserRole, - target_role: UserRole, -) -> None: - subject = create_user(role=subject_role) - context = RoleManagementContext(subject=subject, target_role=target_role) - sut = CanManageRole() - - assert sut.is_satisfied_by(context) - - -@pytest.mark.parametrize( - ("subject_role", "target_role"), - [ - (UserRole.SUPER_ADMIN, UserRole.SUPER_ADMIN), - (UserRole.ADMIN, UserRole.SUPER_ADMIN), - (UserRole.ADMIN, UserRole.ADMIN), - (UserRole.USER, UserRole.ADMIN), - ], -) -def test_cannot_manage_role( - subject_role: UserRole, - target_role: UserRole, -) -> None: - subject = create_user(role=subject_role) - context = RoleManagementContext(subject=subject, target_role=target_role) - sut = CanManageRole() - - assert not sut.is_satisfied_by(context) diff --git a/tests/app/unit/domain/entities/__init__.py b/tests/app/unit/domain/entities/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/app/unit/domain/enums/__init__.py b/tests/app/unit/domain/enums/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/app/unit/domain/enums/test_user_role.py b/tests/app/unit/domain/enums/test_user_role.py deleted file mode 100644 index 1300db3a..00000000 --- a/tests/app/unit/domain/enums/test_user_role.py +++ /dev/null @@ -1,27 +0,0 @@ -import pytest - -from app.domain.enums.user_role import UserRole - - -@pytest.mark.parametrize( - ("role", "expected"), - [ - (UserRole.USER, True), - (UserRole.ADMIN, True), - (UserRole.SUPER_ADMIN, False), - ], -) -def test_assignability(role: UserRole, expected: bool) -> None: - assert role.is_assignable is expected - - -@pytest.mark.parametrize( - ("role", "expected"), - [ - (UserRole.USER, True), - (UserRole.ADMIN, True), - (UserRole.SUPER_ADMIN, False), - ], -) -def test_changeability(role: UserRole, expected: bool) -> None: - assert role.is_changeable is expected diff --git a/tests/app/unit/domain/services/__init__.py b/tests/app/unit/domain/services/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/app/unit/domain/services/conftest.py b/tests/app/unit/domain/services/conftest.py deleted file mode 100644 index 0e6e7e12..00000000 --- a/tests/app/unit/domain/services/conftest.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import cast -from unittest.mock import create_autospec - -import pytest - -from app.domain.ports.password_hasher import PasswordHasher -from app.domain.ports.user_id_generator import UserIdGenerator -from tests.app.unit.domain.services.mock_types import ( - PasswordHasherMock, - UserIdGeneratorMock, -) - - -@pytest.fixture -def user_id_generator() -> UserIdGeneratorMock: - return cast(UserIdGeneratorMock, create_autospec(UserIdGenerator, instance=True)) - - -@pytest.fixture -def password_hasher() -> PasswordHasherMock: - return cast(PasswordHasherMock, create_autospec(PasswordHasher, instance=True)) diff --git a/tests/app/unit/domain/services/test_user.py b/tests/app/unit/domain/services/test_user.py deleted file mode 100644 index 7caa3787..00000000 --- a/tests/app/unit/domain/services/test_user.py +++ /dev/null @@ -1,217 +0,0 @@ -import pytest - -from app.domain.entities.user import User -from app.domain.enums.user_role import UserRole -from app.domain.exceptions.user import ( - ActivationChangeNotPermittedError, - RoleAssignmentNotPermittedError, - RoleChangeNotPermittedError, -) -from app.domain.services.user import UserService -from tests.app.unit.domain.services.mock_types import ( - PasswordHasherMock, - UserIdGeneratorMock, -) -from tests.app.unit.factories.user_entity import create_user -from tests.app.unit.factories.value_objects import ( - create_password_hash, - create_raw_password, - create_user_id, - create_username, -) - - -@pytest.mark.asyncio -@pytest.mark.parametrize( - "role", - [UserRole.USER, UserRole.ADMIN], -) -async def test_creates_active_user_with_hashed_password( - role: UserRole, - user_id_generator: UserIdGeneratorMock, - password_hasher: PasswordHasherMock, -) -> None: - # Arrange - username = create_username() - raw_password = create_raw_password() - - expected_id = create_user_id() - expected_hash = create_password_hash() - - user_id_generator.generate.return_value = expected_id - password_hasher.hash.return_value = expected_hash - sut = UserService(user_id_generator, password_hasher) # type: ignore[arg-type] - - # Act - result = await sut.create_user(username, raw_password, role) - - # Assert - assert isinstance(result, User) - assert result.id_ == expected_id - assert result.username == username - assert result.password_hash == expected_hash - assert result.role == role - assert result.is_active is True - - -@pytest.mark.asyncio -async def test_creates_inactive_user_if_specified( - user_id_generator: UserIdGeneratorMock, - password_hasher: PasswordHasherMock, -) -> None: - username = create_username() - raw_password = create_raw_password() - sut = UserService(user_id_generator, password_hasher) # type: ignore[arg-type] - - result = await sut.create_user(username, raw_password, is_active=False) - - assert not result.is_active - - -@pytest.mark.asyncio -async def test_fails_to_create_user_with_unassignable_role( - user_id_generator: UserIdGeneratorMock, - password_hasher: PasswordHasherMock, -) -> None: - username = create_username() - raw_password = create_raw_password() - sut = UserService(user_id_generator, password_hasher) # type: ignore[arg-type] - - with pytest.raises(RoleAssignmentNotPermittedError): - await sut.create_user( - username=username, - raw_password=raw_password, - role=UserRole.SUPER_ADMIN, - ) - - -@pytest.mark.asyncio -@pytest.mark.parametrize( - "is_valid", - [True, False], -) -async def test_checks_password_authenticity( - is_valid: bool, - user_id_generator: UserIdGeneratorMock, - password_hasher: PasswordHasherMock, -) -> None: - # Arrange - user = create_user() - raw_password = create_raw_password() - - password_hasher.verify.return_value = is_valid - sut = UserService(user_id_generator, password_hasher) # type: ignore[arg-type] - - # Act - result = await sut.is_password_valid(user, raw_password) - - # Assert - assert result is is_valid - - -@pytest.mark.asyncio -async def test_changes_password( - user_id_generator: UserIdGeneratorMock, - password_hasher: PasswordHasherMock, -) -> None: - # Arrange - initial_hash = create_password_hash(b"old") - user = create_user(password_hash=initial_hash) - raw_password = create_raw_password() - - expected_hash = create_password_hash(b"new") - password_hasher.hash.return_value = expected_hash - sut = UserService(user_id_generator, password_hasher) # type: ignore[arg-type] - - # Act - await sut.change_password(user, raw_password) - - # Assert - assert user.password_hash == expected_hash - - -@pytest.mark.parametrize( - ("initial_state", "target_state", "expected_result"), - [ - pytest.param(True, False, True, id="active_to_inactive"), - pytest.param(False, True, True, id="inactive_to_active"), - pytest.param(True, True, False, id="already_active"), - pytest.param(False, False, False, id="already_inactive"), - ], -) -def test_toggles_activation_state( - initial_state: bool, - target_state: bool, - expected_result: bool, - user_id_generator: UserIdGeneratorMock, - password_hasher: PasswordHasherMock, -) -> None: - user = create_user(is_active=initial_state) - sut = UserService(user_id_generator, password_hasher) # type: ignore[arg-type] - - result = sut.toggle_user_activation(user, is_active=target_state) - - assert result is expected_result - assert user.is_active is target_state - - -@pytest.mark.parametrize( - "is_active", - [True, False], -) -def test_preserves_super_admin_activation_state( - is_active: bool, - user_id_generator: UserIdGeneratorMock, - password_hasher: PasswordHasherMock, -) -> None: - user = create_user(role=UserRole.SUPER_ADMIN, is_active=not is_active) - sut = UserService(user_id_generator, password_hasher) # type: ignore[arg-type] - - with pytest.raises(ActivationChangeNotPermittedError): - sut.toggle_user_activation(user, is_active=is_active) - - assert user.is_active is not is_active - - -@pytest.mark.parametrize( - ("initial_role", "target_is_admin", "expected_role", "expected_result"), - [ - pytest.param(UserRole.USER, True, UserRole.ADMIN, True, id="user_to_admin"), - pytest.param(UserRole.ADMIN, False, UserRole.USER, True, id="admin_to_user"), - pytest.param(UserRole.USER, False, UserRole.USER, False, id="already_user"), - pytest.param(UserRole.ADMIN, True, UserRole.ADMIN, False, id="already_admin"), - ], -) -def test_toggles_role( - initial_role: UserRole, - target_is_admin: bool, - expected_role: UserRole, - expected_result: bool, - user_id_generator: UserIdGeneratorMock, - password_hasher: PasswordHasherMock, -) -> None: - user = create_user(role=initial_role) - sut = UserService(user_id_generator, password_hasher) # type: ignore[arg-type] - - result = sut.toggle_user_admin_role(user, is_admin=target_is_admin) - - assert result is expected_result - assert user.role == expected_role - - -@pytest.mark.parametrize( - "is_admin", - [True, False], -) -def test_preserves_super_admin_role( - is_admin: bool, - user_id_generator: UserIdGeneratorMock, - password_hasher: PasswordHasherMock, -) -> None: - user = create_user(role=UserRole.SUPER_ADMIN) - sut = UserService(user_id_generator, password_hasher) # type: ignore[arg-type] - - with pytest.raises(RoleChangeNotPermittedError): - sut.toggle_user_admin_role(user, is_admin=is_admin) - - assert user.role == UserRole.SUPER_ADMIN diff --git a/tests/app/unit/domain/value_objects/__init__.py b/tests/app/unit/domain/value_objects/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/app/unit/factories/__init__.py b/tests/app/unit/factories/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/app/unit/factories/named_entity.py b/tests/app/unit/factories/named_entity.py deleted file mode 100644 index b89544b0..00000000 --- a/tests/app/unit/factories/named_entity.py +++ /dev/null @@ -1,42 +0,0 @@ -from dataclasses import dataclass - -from app.domain.entities.base import Entity -from app.domain.value_objects.base import ValueObject - - -@dataclass(frozen=True, slots=True, repr=False) -class NamedEntityId(ValueObject): - value: int - - -class NamedEntity(Entity[NamedEntityId]): - def __init__(self, *, id_: NamedEntityId, name: str) -> None: - super().__init__(id_=id_) - self.name = name - - -class NamedEntitySubclass(NamedEntity): - def __init__(self, *, id_: NamedEntityId, name: str, value: int) -> None: - super().__init__(id_=id_, name=name) - self.value = value - - -def create_named_entity_id( - id_: int = 42, -) -> NamedEntityId: - return NamedEntityId(id_) - - -def create_named_entity( - id_: int = 42, - name: str = "name", -) -> NamedEntity: - return NamedEntity(id_=NamedEntityId(id_), name=name) - - -def create_named_entity_subclass( - id_: int = 42, - name: str = "name", - value: int = 314, -) -> NamedEntitySubclass: - return NamedEntitySubclass(id_=NamedEntityId(id_), name=name, value=value) diff --git a/tests/app/unit/factories/settings_data.py b/tests/app/unit/factories/settings_data.py deleted file mode 100644 index ae09ec4c..00000000 --- a/tests/app/unit/factories/settings_data.py +++ /dev/null @@ -1,63 +0,0 @@ -from typing import Literal, TypedDict - - -class AuthSettingsData(TypedDict): - JWT_SECRET: str - JWT_ALGORITHM: Literal[ - "HS256", - "HS384", - "HS512", - "RS256", - "RS384", - "RS512", - ] - SESSION_TTL_MIN: int | float - SESSION_REFRESH_THRESHOLD: int | float - - -class PostgresSettingsData(TypedDict): - USER: str - PASSWORD: str - DB: str - HOST: str - PORT: int - DRIVER: str - - -def create_auth_settings_data( - jwt_secret: str = "jwt_secret" + "0" * 32, - jwt_algorithm: Literal[ - "HS256", - "HS384", - "HS512", - "RS256", - "RS384", - "RS512", - ] = "RS256", - session_ttl_min: int | float = 2, - session_refresh_threshold: int | float = 0.5, -) -> AuthSettingsData: - return AuthSettingsData( - JWT_SECRET=jwt_secret, - JWT_ALGORITHM=jwt_algorithm, - SESSION_TTL_MIN=session_ttl_min, - SESSION_REFRESH_THRESHOLD=session_refresh_threshold, - ) - - -def create_postgres_settings_data( - user: str = "user", - password: str = "password", - db: str = "db", - host: str = "localhost", - port: int = 5432, - driver: str = "asyncpg", -) -> PostgresSettingsData: - return PostgresSettingsData( - USER=user, - PASSWORD=password, - DB=db, - HOST=host, - PORT=port, - DRIVER=driver, - ) diff --git a/tests/app/unit/factories/tagged_entity.py b/tests/app/unit/factories/tagged_entity.py deleted file mode 100644 index 7580e6d2..00000000 --- a/tests/app/unit/factories/tagged_entity.py +++ /dev/null @@ -1,23 +0,0 @@ -from dataclasses import dataclass - -from app.domain.entities.base import Entity -from app.domain.value_objects.base import ValueObject - - -@dataclass(frozen=True, slots=True, repr=False) -class TaggedEntityId(ValueObject): - value: int - - -class TaggedEntity(Entity[TaggedEntityId]): - def __init__(self, *, id_: TaggedEntityId, tag: str) -> None: - super().__init__(id_=id_) - self.tag = tag - - -def create_tagged_entity_id(id_: int = 54) -> TaggedEntityId: - return TaggedEntityId(id_) - - -def create_tagged_entity(id_: int = 54, tag: str = "tag") -> TaggedEntity: - return TaggedEntity(id_=TaggedEntityId(id_), tag=tag) diff --git a/tests/app/unit/factories/user_entity.py b/tests/app/unit/factories/user_entity.py deleted file mode 100644 index 75d6a667..00000000 --- a/tests/app/unit/factories/user_entity.py +++ /dev/null @@ -1,26 +0,0 @@ -from app.domain.entities.user import User -from app.domain.enums.user_role import UserRole -from app.domain.value_objects.user_id import UserId -from app.domain.value_objects.user_password_hash import UserPasswordHash -from app.domain.value_objects.username import Username -from tests.app.unit.factories.value_objects import ( - create_password_hash, - create_user_id, - create_username, -) - - -def create_user( - user_id: UserId | None = None, - username: Username | None = None, - password_hash: UserPasswordHash | None = None, - role: UserRole = UserRole.USER, - is_active: bool = True, -) -> User: - return User( - id_=user_id or create_user_id(), - username=username or create_username(), - password_hash=password_hash or create_password_hash(), - role=role, - is_active=is_active, - ) diff --git a/tests/app/unit/factories/value_objects.py b/tests/app/unit/factories/value_objects.py deleted file mode 100644 index 5683e335..00000000 --- a/tests/app/unit/factories/value_objects.py +++ /dev/null @@ -1,44 +0,0 @@ -import uuid -from dataclasses import dataclass -from uuid import UUID - -from app.domain.value_objects.base import ValueObject -from app.domain.value_objects.raw_password import RawPassword -from app.domain.value_objects.user_id import UserId -from app.domain.value_objects.user_password_hash import UserPasswordHash -from app.domain.value_objects.username import Username - - -@dataclass(frozen=True, slots=True, repr=False) -class SingleFieldVO(ValueObject): - value: int - - -@dataclass(frozen=True, slots=True, repr=False) -class MultiFieldVO(ValueObject): - value1: int - value2: str - - -def create_single_field_vo(value: int = 1) -> SingleFieldVO: - return SingleFieldVO(value) - - -def create_multi_field_vo(value1: int = 1, value2: str = "Alice") -> MultiFieldVO: - return MultiFieldVO(value1, value2) - - -def create_user_id(value: UUID | None = None) -> UserId: - return UserId(value if value else uuid.uuid4()) - - -def create_username(value: str = "Alice") -> Username: - return Username(value) - - -def create_raw_password(value: str = "Good Password") -> RawPassword: - return RawPassword(value) - - -def create_password_hash(value: bytes = b"password_hash") -> UserPasswordHash: - return UserPasswordHash(value) diff --git a/tests/app/unit/infrastructure/__init__.py b/tests/app/unit/infrastructure/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/app/unit/setup/__init__.py b/tests/app/unit/setup/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/app/unit/setup/test_cfg_database.py b/tests/app/unit/setup/test_cfg_database.py deleted file mode 100644 index ac9eadfb..00000000 --- a/tests/app/unit/setup/test_cfg_database.py +++ /dev/null @@ -1,60 +0,0 @@ -import pytest -from pydantic import PostgresDsn, ValidationError - -from app.setup.config.database import PORT_MAX, PORT_MIN, PostgresSettings -from tests.app.unit.factories.settings_data import create_postgres_settings_data - - -def test_postgres_host_overridden_by_env_variable( - monkeypatch: pytest.MonkeyPatch, -) -> None: - env_host = "changed" - monkeypatch.setenv("POSTGRES_HOST", env_host) - data = create_postgres_settings_data(host="initial") - - sut = PostgresSettings.model_validate(data) - - assert sut.host == env_host - - -@pytest.mark.parametrize( - "port", - [ - pytest.param(PORT_MIN, id="lower_bound"), - pytest.param(PORT_MAX, id="upper_bound"), - ], -) -def test_postgres_port_accepts_correct_value(port: int) -> None: - data = create_postgres_settings_data(port=port) - - PostgresSettings.model_validate(data) - - -@pytest.mark.parametrize( - "port", - [ - pytest.param(PORT_MIN - 1, id="too_small"), - pytest.param(PORT_MAX + 1, id="too_big"), - ], -) -def test_postgres_port_rejects_incorrect_value(port: int) -> None: - data = create_postgres_settings_data(port=port) - - with pytest.raises(ValidationError): - PostgresSettings.model_validate(data) - - -def test_postgres_dsn_builds_valid_uri_from_fields() -> None: - data = create_postgres_settings_data( - user="alice", - password="secret", - db="my_db", - host="localhost", - port=5678, - driver="psycopg2", - ) - - sut = PostgresSettings.model_validate(data) - - assert sut.dsn == "postgresql+psycopg2://alice:secret@localhost:5678/my_db" - assert PostgresDsn(sut.dsn) diff --git a/tests/app/unit/setup/test_cfg_loader.py b/tests/app/unit/setup/test_cfg_loader.py deleted file mode 100644 index 41817378..00000000 --- a/tests/app/unit/setup/test_cfg_loader.py +++ /dev/null @@ -1,163 +0,0 @@ -import textwrap -from copy import deepcopy -from pathlib import Path - -import pytest - -from app.setup.config.loader import ( - ENV_VAR_NAME, - DirContents, - ValidEnvs, - get_current_env, - load_full_config, - merge_dicts, - read_config, - validate_env, -) - - -@pytest.mark.parametrize("env", list(ValidEnvs)) -def test_returns_enum_for_correct_env_string(env: ValidEnvs) -> None: - assert validate_env(env) == env - - -@pytest.mark.parametrize( - "env", - ["Incorrect", None], -) -def test_raises_for_incorrect_env_string_or_none(env: str | None) -> None: - with pytest.raises(ValueError): - validate_env(env) - - -@pytest.mark.parametrize("env_str", list(ValidEnvs)) -def test_reads_and_validates_env_var( - env_str: ValidEnvs, - monkeypatch: pytest.MonkeyPatch, -) -> None: - monkeypatch.setenv(ENV_VAR_NAME, str(env_str)) - - assert get_current_env() == env_str - - -def test_reader_returns_dict_for_valid_toml(tmp_path: Path) -> None: - cfg_file = tmp_path / "config.toml" - fake_cfg_text = textwrap.dedent("""\ - [database] - USER = "test_postgres" - PORT = 1234 - """) - cfg_file.write_text(fake_cfg_text, encoding="utf-8") - - result = read_config( - env=ValidEnvs.DEV, - config=DirContents.CONFIG_NAME, - dir_paths={ValidEnvs.DEV: tmp_path}, - ) - - assert result == {"database": {"USER": "test_postgres", "PORT": 1234}} - - -def test_reader_raises_for_missing_dir() -> None: - with pytest.raises(FileNotFoundError): - read_config( - env=ValidEnvs.DEV, - config=DirContents.CONFIG_NAME, - dir_paths={ValidEnvs.DEV: Path("wrong_path")}, - ) - - -def test_reader_raises_for_missing_env_path() -> None: - with pytest.raises(FileNotFoundError): - read_config( - env=ValidEnvs.PROD, - config=DirContents.CONFIG_NAME, - dir_paths={}, - ) - - -def test_merges_flat_dicts() -> None: - assert merge_dicts(dict1={"a": 1}, dict2={"b": 2}) == {"a": 1, "b": 2} - - -def test_merges_nested_dicts() -> None: - d1 = {"db": {"host": "localhost"}} - d2 = {"db": {"port": 5432}} - - assert merge_dicts(dict1=d1, dict2=d2) == { - "db": {"host": "localhost", "port": 5432} - } - - -def test_merger_overwrites_values_to_latest() -> None: - d1 = {"a": 1} - d2 = {"a": {"nested": True}} - - assert merge_dicts(dict1=d1, dict2=d2) == {"a": {"nested": True}} - - -def test_merger_does_not_mutate_inputs() -> None: - dict1 = {"a": {"x": 1}, "b": {"z": 3}} - dict2 = {"a": {"y": 2}, "c": {"w": 4}} - dict1_copy = deepcopy(dict1) - dict2_copy = deepcopy(dict2) - - merge_dicts(dict1=dict1, dict2=dict2) - - assert dict1 == dict1_copy - assert dict2 == dict2_copy - - -def test_full_loader_merges_config_and_secrets(tmp_path: Path) -> None: - # Arrange - config_file = tmp_path / "config.toml" - config_text = textwrap.dedent("""\ - [db] - USER = "admin" - PORT = 5432 - """) - config_file.write_text(config_text, encoding="utf-8") - - secrets_file = tmp_path / ".secrets.toml" - secrets_text = textwrap.dedent("""\ - [db] - PASSWORD = "secret" - """) - secrets_file.write_text(secrets_text, encoding="utf-8") - - # Act - result = load_full_config( - env=ValidEnvs.DEV, - dir_paths={ValidEnvs.DEV: tmp_path}, - ) - - # Assert - assert result == { - "db": { - "USER": "admin", - "PORT": 5432, - "PASSWORD": "secret", - } - } - - -def test_full_loader_skips_missing_secrets(tmp_path: Path) -> None: - config_file = tmp_path / "config.toml" - config_text = textwrap.dedent("""\ - [db] - USER = "admin" - PORT = 5432 - """) - config_file.write_text(config_text, encoding="utf-8") - - result = load_full_config( - env=ValidEnvs.DEV, - dir_paths={ValidEnvs.DEV: tmp_path}, - ) - - assert result == { - "db": { - "USER": "admin", - "PORT": 5432, - } - } diff --git a/tests/app/unit/setup/test_cfg_logs.py b/tests/app/unit/setup/test_cfg_logs.py deleted file mode 100644 index 6212a8d7..00000000 --- a/tests/app/unit/setup/test_cfg_logs.py +++ /dev/null @@ -1,36 +0,0 @@ -import logging -from collections.abc import Iterator - -import pytest - -from app.setup.config.logs import LoggingLevel, configure_logging - - -@pytest.fixture -def clean_logging() -> Iterator[None]: - try: - yield - finally: - logging.getLogger().handlers.clear() - - -@pytest.mark.parametrize( - ("lvl_given", "lvl_expected"), - [ - (LoggingLevel.DEBUG, logging.DEBUG), - (LoggingLevel.INFO, logging.INFO), - (LoggingLevel.WARNING, logging.WARNING), - (LoggingLevel.ERROR, logging.ERROR), - (LoggingLevel.CRITICAL, logging.CRITICAL), - ], -) -@pytest.mark.usefixtures("clean_logging") -def test_logger_uses_given_level( - lvl_given: LoggingLevel, - lvl_expected: int, -) -> None: - logger = logging.getLogger() - - configure_logging(level=lvl_given) - - assert logger.level == lvl_expected diff --git a/tests/app/unit/setup/test_cfg_security.py b/tests/app/unit/setup/test_cfg_security.py deleted file mode 100644 index c00080d0..00000000 --- a/tests/app/unit/setup/test_cfg_security.py +++ /dev/null @@ -1,36 +0,0 @@ -from datetime import timedelta - -import pytest -from pydantic import ValidationError - -from app.setup.config.security import AuthSettings -from tests.app.unit.factories.settings_data import create_auth_settings_data - - -@pytest.mark.parametrize( - ("ttl", "expected"), - [ - pytest.param(1, timedelta(minutes=1), id="boundary"), - pytest.param(2.5, timedelta(minutes=2.5), id="ordinary"), - ], -) -def test_auth_converts_ttl_to_timedelta(ttl: int, expected: timedelta) -> None: - data = create_auth_settings_data(session_ttl_min=ttl) - - sut = AuthSettings.model_validate(data) - - assert sut.session_ttl_min == expected - - -@pytest.mark.parametrize( - "ttl", - [ - pytest.param("1", id="wrong_type"), - pytest.param(0.99, id="too_small"), - ], -) -def test_auth_rejects_invalid_ttl(ttl: int | str) -> None: - data = create_auth_settings_data(session_ttl_min=ttl) # type: ignore[arg-type] - - with pytest.raises(ValidationError): - AuthSettings.model_validate(data) diff --git a/src/app/infrastructure/exceptions/__init__.py b/tests/integration/__init__.py similarity index 100% rename from src/app/infrastructure/exceptions/__init__.py rename to tests/integration/__init__.py diff --git a/src/app/presentation/http/auth/__init__.py b/tests/integration/no_infra/__init__.py similarity index 100% rename from src/app/presentation/http/auth/__init__.py rename to tests/integration/no_infra/__init__.py diff --git a/src/app/presentation/http/auth/adapters/__init__.py b/tests/integration/with_infra/__init__.py similarity index 100% rename from src/app/presentation/http/auth/adapters/__init__.py rename to tests/integration/with_infra/__init__.py diff --git a/tests/integration/with_infra/conftest.py b/tests/integration/with_infra/conftest.py new file mode 100644 index 00000000..bf0f27d4 --- /dev/null +++ b/tests/integration/with_infra/conftest.py @@ -0,0 +1,95 @@ +import os +from collections.abc import AsyncIterator, Sequence +from typing import Final, cast + +import asgi_lifespan +import httpx +import pytest +from dishka import Provider +from fastapi import FastAPI +from sqlalchemy import text +from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker + +from app.config.settings import AppSettings +from app.infrastructure.persistence_sqla.registry import mapper_registry +from app.main.run import make_app + +LIFESPAN_MANAGER_STARTUP_TIMEOUT_S: Final[int] = 30 +ALLOW_DESTRUCTIVE_TEST_CLEANUP: Final[str] = "ALLOW_DESTRUCTIVE_TEST_CLEANUP" +ALLOW_DESTRUCTIVE_TEST_CLEANUP_EXPECTED_VALUE: Final[str] = "1" + + +@pytest.fixture(scope="session") +def allow_destructive() -> None: + """Use on fixtures that require potentially dangerous cleanup.""" + if os.getenv(ALLOW_DESTRUCTIVE_TEST_CLEANUP) != ALLOW_DESTRUCTIVE_TEST_CLEANUP_EXPECTED_VALUE: + raise pytest.UsageError( + "Destructive cleanup is disabled: " + f"{ALLOW_DESTRUCTIVE_TEST_CLEANUP} must be set to {ALLOW_DESTRUCTIVE_TEST_CLEANUP_EXPECTED_VALUE}. " + "This guard prevents accidental cleanup of non-test data." + ) + + +@pytest.fixture +def it_di_overrides() -> Sequence[Provider]: + """ + Override in a test module to provide custom dependency overrides. + Keep the same fixture signature. + """ + return () + + +@pytest.fixture +def it_fastapi_app(it_di_overrides: Sequence[Provider]) -> FastAPI: + return make_app( + *it_di_overrides, + app_settings=AppSettings(DEBUG_MODE=False), + ) + + +@pytest.fixture +async def it_client(it_fastapi_app: FastAPI) -> AsyncIterator[httpx.AsyncClient]: + async with ( + asgi_lifespan.LifespanManager( + it_fastapi_app, + startup_timeout=LIFESPAN_MANAGER_STARTUP_TIMEOUT_S, + ), + httpx.AsyncClient( + transport=httpx.ASGITransport(app=it_fastapi_app), + base_url="http://test", + ) as client, + ): + yield client + + +@pytest.fixture +async def it_sessionmaker( + it_client: httpx.AsyncClient, + it_fastapi_app: FastAPI, +) -> async_sessionmaker[AsyncSession]: + container = it_fastapi_app.state.dishka_container + session_maker = await container.get(async_sessionmaker[AsyncSession]) + return cast(async_sessionmaker[AsyncSession], session_maker) + + +@pytest.fixture +async def it_db_clean( + allow_destructive: None, + it_sessionmaker: async_sessionmaker[AsyncSession], +) -> None: + table_names = [table.name for table in mapper_registry.metadata.sorted_tables if table.name != "alembic_version"] + assert table_names, "it_db_clean: no tables found in mapper_registry.metadata (fixture is a no-op)" + sql = "TRUNCATE " + ", ".join(f'"{name}"' for name in table_names) + " RESTART IDENTITY CASCADE;" + + async with it_sessionmaker() as session: + await session.execute(text(sql)) + await session.commit() + + +@pytest.fixture +async def it_session( + it_db_clean: None, + it_sessionmaker: async_sessionmaker[AsyncSession], +) -> AsyncIterator[AsyncSession]: + async with it_sessionmaker() as session: + yield session diff --git a/tests/integration/with_infra/test_stairway.py b/tests/integration/with_infra/test_stairway.py new file mode 100644 index 00000000..6b94d600 --- /dev/null +++ b/tests/integration/with_infra/test_stairway.py @@ -0,0 +1,68 @@ +""" +Test can find forgotten downgrade methods, undeleted data types in downgrade +methods, typos and many other errors. + +Does not require any maintenance - you just add it once to check 80% of typos +and mistakes in migrations forever. + +https://github.com/alvassin/alembic-quickstart +""" + +from argparse import Namespace +from typing import Final + +import pytest +from alembic.command import downgrade, upgrade +from alembic.config import Config +from alembic.script import Script, ScriptDirectory + +from app.config.loader import BASE_DIR, load_postgres_settings +from app.config.settings import PostgresSettings + +ALEMBIC_INI_PATH: Final[str] = str(BASE_DIR / "alembic.ini") + + +@pytest.fixture(scope="module") +def postgres_settings() -> PostgresSettings: + return load_postgres_settings() + + +@pytest.fixture(scope="module") +def alembic_config(postgres_settings: PostgresSettings) -> Config: + cmd_opts = Namespace( + config=ALEMBIC_INI_PATH, + name="alembic", + db_url=postgres_settings.dsn, + raiseerr=False, + x=None, + ) + config = Config(file_=cmd_opts.config, ini_section=cmd_opts.name, cmd_opts=cmd_opts) + config.set_main_option("sqlalchemy.url", f"{postgres_settings.dsn}?async_fallback=true") + return config + + +def get_revisions() -> list[Script]: + # Create Alembic configuration object + # (we don't need database for getting revisions list) + config = Config(ALEMBIC_INI_PATH) + + # Get directory object with Alembic migrations + revisions_dir = ScriptDirectory.from_config(config) + + # Get & sort migrations, from first to last + revisions = list(revisions_dir.walk_revisions("base", "heads")) + revisions.reverse() + return revisions + + +@pytest.mark.parametrize("revision", get_revisions()) +def test_migrations_stairway( + allow_destructive: None, + alembic_config: Config, + revision: Script, +) -> None: + upgrade(alembic_config, revision.revision) + + # We need -1 for downgrading first migration (its down_revision is None) + downgrade(alembic_config, str(revision.down_revision or "-1")) + upgrade(alembic_config, revision.revision) diff --git a/src/app/presentation/http/controllers/__init__.py b/tests/performance/__init__.py similarity index 100% rename from src/app/presentation/http/controllers/__init__.py rename to tests/performance/__init__.py diff --git a/tests/app/performance/profile_password_hasher_bcrypt.py b/tests/performance/profile_bcrypt_password_hasher.py similarity index 72% rename from tests/app/performance/profile_password_hasher_bcrypt.py rename to tests/performance/profile_bcrypt_password_hasher.py index bbaf22bb..234c45d3 100644 --- a/tests/app/performance/profile_password_hasher_bcrypt.py +++ b/tests/performance/profile_bcrypt_password_hasher.py @@ -2,10 +2,8 @@ from line_profiler import LineProfiler -from app.domain.value_objects.raw_password import RawPassword -from app.infrastructure.adapters.password_hasher_bcrypt import ( - BcryptPasswordHasher, -) +from app.core.common.value_objects.raw_password import RawPassword +from app.infrastructure.adapters.bcrypt_password_hasher import BcryptPasswordHasher def profile_password_hashing(hasher: BcryptPasswordHasher) -> None: @@ -22,11 +20,11 @@ def main() -> None: semaphore=Mock(), semaphore_wait_timeout_s=1, ) - profiler = LineProfiler() profiler.add_function(profile_password_hashing) - profiler.runcall(profile_password_hashing, hasher) + profiler.runcall(profile_password_hashing, hasher) # type: ignore[no-untyped-call] + profiler.print_stats() diff --git a/src/app/presentation/http/controllers/account/__init__.py b/tests/sanity/__init__.py similarity index 100% rename from src/app/presentation/http/controllers/account/__init__.py rename to tests/sanity/__init__.py diff --git a/src/app/presentation/http/controllers/general/__init__.py b/tests/sanity/config/__init__.py similarity index 100% rename from src/app/presentation/http/controllers/general/__init__.py rename to tests/sanity/config/__init__.py diff --git a/tests/sanity/config/test_loader.py b/tests/sanity/config/test_loader.py new file mode 100644 index 00000000..93bcfd6c --- /dev/null +++ b/tests/sanity/config/test_loader.py @@ -0,0 +1,5 @@ +from app.config.loader import BASE_DIR + + +def test_base_dir_points_to_root() -> None: + assert (BASE_DIR / "pyproject.toml").exists() diff --git a/src/app/presentation/http/controllers/users/__init__.py b/tests/smoke/__init__.py similarity index 100% rename from src/app/presentation/http/controllers/users/__init__.py rename to tests/smoke/__init__.py diff --git a/src/app/setup/__init__.py b/tests/unit/__init__.py similarity index 100% rename from src/app/setup/__init__.py rename to tests/unit/__init__.py diff --git a/src/app/setup/config/__init__.py b/tests/unit/config/__init__.py similarity index 100% rename from src/app/setup/config/__init__.py rename to tests/unit/config/__init__.py diff --git a/tests/unit/config/test_loader.py b/tests/unit/config/test_loader.py new file mode 100644 index 00000000..5c36e345 --- /dev/null +++ b/tests/unit/config/test_loader.py @@ -0,0 +1,118 @@ +import pytest + +from app.config.loader import ( + load_app_settings, + load_cookie_settings, + load_jwt_settings, + load_password_hasher_settings, + load_postgres_settings, + load_session_settings, + load_sqla_settings, +) +from app.config.logging_ import LoggingLevel + + +@pytest.mark.parametrize( + "logging_level", + [ + LoggingLevel.DEBUG, + LoggingLevel.INFO, + LoggingLevel.WARNING, + LoggingLevel.ERROR, + LoggingLevel.CRITICAL, + ], +) +def test_load_app_settings_reads_env_vars(monkeypatch: pytest.MonkeyPatch, logging_level: LoggingLevel) -> None: + monkeypatch.setenv("APP_SERVICE_NAME", "test-service") + monkeypatch.setenv("APP_VERSION", "test-version") + monkeypatch.setenv("APP_ROOT_PATH", "test-path") + monkeypatch.setenv("APP_DEBUG_MODE", "1") + monkeypatch.setenv("APP_LOGGING_LEVEL", logging_level) + + sut = load_app_settings() + + assert sut.SERVICE_NAME == "test-service" + assert sut.VERSION == "test-version" + assert sut.ROOT_PATH == "test-path" + assert sut.DEBUG_MODE is True + assert sut.LOGGING_LEVEL == logging_level + + +def test_load_postgres_settings_reads_env_vars(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("POSTGRES_DB", "test-db") + monkeypatch.setenv("POSTGRES_HOST", "test-host") + monkeypatch.setenv("POSTGRES_PORT", "123456789") + monkeypatch.setenv("POSTGRES_USER", "test-user") + monkeypatch.setenv("POSTGRES_PASSWORD", "test-password") + + sut = load_postgres_settings() + + assert sut.DB == "test-db" + assert sut.HOST == "test-host" + assert sut.PORT == 123456789 + assert sut.USER == "test-user" + assert sut.PASSWORD == "test-password" + + +def test_load_sqla_settings_reads_env_vars(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("SQLA_ECHO", "true") + monkeypatch.setenv("SQLA_ECHO_POOL", "true") + monkeypatch.setenv("SQLA_POOL_SIZE", "123456789") + monkeypatch.setenv("SQLA_MAX_OVERFLOW", "987654321") + + sut = load_sqla_settings() + + assert sut.ECHO is True + assert sut.ECHO_POOL is True + assert sut.POOL_SIZE == 123456789 + assert sut.MAX_OVERFLOW == 987654321 + + +def test_load_password_hasher_settings_reads_env_vars(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("PASSWORD_PEPPER", "test-pepper-test-pepper-test-pepper") + monkeypatch.setenv("PASSWORD_WORK_FACTOR", "123456789") + monkeypatch.setenv("PASSWORD_MAX_THREADS", "987654321") + monkeypatch.setenv("PASSWORD_SEMAPHORE_WAIT_TIMEOUT_S", "1.23456789") + + sut = load_password_hasher_settings() + + assert sut.PEPPER == "test-pepper-test-pepper-test-pepper" + assert sut.WORK_FACTOR == 123456789 + assert sut.MAX_THREADS == 987654321 + assert sut.SEMAPHORE_WAIT_TIMEOUT_S == 1.23456789 + + +def test_load_jwt_settings_reads_env_vars(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("JWT_SECRET", "test-secret-test-secret-test-secret") + monkeypatch.setenv("JWT_ALGORITHM", "HS384") + + sut = load_jwt_settings() + + assert sut.SECRET == "test-secret-test-secret-test-secret" + assert sut.ALGORITHM == "HS384" + + +def test_load_session_settings_reads_env_vars(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("SESSION_TTL_MIN", "123465789") + monkeypatch.setenv("SESSION_REFRESH_THRESHOLD_RATIO", "0.123456789") + + sut = load_session_settings() + + assert sut.TTL_MIN == 123465789 + assert sut.REFRESH_THRESHOLD_RATIO == 0.123456789 + + +def test_load_cookie_settings_reads_env_vars(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("COOKIE_NAME", "test-name") + monkeypatch.setenv("COOKIE_PATH", "test-path") + monkeypatch.setenv("COOKIE_HTTPONLY", "1") + monkeypatch.setenv("COOKIE_SECURE", "false") + monkeypatch.setenv("COOKIE_SAMESITE", "strict") + + sut = load_cookie_settings() + + assert sut.NAME == "test-name" + assert sut.PATH == "test-path" + assert sut.HTTPONLY is True + assert sut.SECURE is False + assert sut.SAMESITE == "strict" diff --git a/tests/unit/config/test_settings.py b/tests/unit/config/test_settings.py new file mode 100644 index 00000000..9b08a3f2 --- /dev/null +++ b/tests/unit/config/test_settings.py @@ -0,0 +1,18 @@ +from datetime import timedelta + +import pytest + +from app.config.settings import SessionSettings + + +@pytest.mark.parametrize( + ("ttl_min", "expected"), + [ + pytest.param(1, timedelta(minutes=1), id="boundary"), + pytest.param(5, timedelta(minutes=5), id="ordinary"), + ], +) +def test_ttl_property_builds_timedelta(ttl_min: int, expected: timedelta) -> None: + sut = SessionSettings(TTL_MIN=ttl_min) + + assert sut.ttl == expected diff --git a/src/app/setup/ioc/__init__.py b/tests/unit/core/__init__.py similarity index 100% rename from src/app/setup/ioc/__init__.py rename to tests/unit/core/__init__.py diff --git a/tests/app/__init__.py b/tests/unit/core/common/__init__.py similarity index 100% rename from tests/app/__init__.py rename to tests/unit/core/common/__init__.py diff --git a/tests/app/integration/__init__.py b/tests/unit/core/common/authorization/__init__.py similarity index 100% rename from tests/app/integration/__init__.py rename to tests/unit/core/common/authorization/__init__.py diff --git a/tests/unit/core/common/authorization/factories.py b/tests/unit/core/common/authorization/factories.py new file mode 100644 index 00000000..94676a7a --- /dev/null +++ b/tests/unit/core/common/authorization/factories.py @@ -0,0 +1,15 @@ +from app.core.common.entities.types_ import UserRole +from app.core.common.entities.user import User +from tests.unit.core.common.services.factories import create_super_user, create_user + + +def make_super_admin() -> User: + return create_super_user() + + +def make_admin() -> User: + return create_user(role=UserRole.ADMIN) + + +def make_user() -> User: + return create_user(role=UserRole.USER) diff --git a/tests/app/unit/application/authz_service/permission_stubs.py b/tests/unit/core/common/authorization/permission_stubs.py similarity index 67% rename from tests/app/unit/application/authz_service/permission_stubs.py rename to tests/unit/core/common/authorization/permission_stubs.py index 81e06fd9..2cad2987 100644 --- a/tests/app/unit/application/authz_service/permission_stubs.py +++ b/tests/unit/core/common/authorization/permission_stubs.py @@ -1,9 +1,9 @@ -from app.application.common.services.authorization.base import ( - Permission, - PermissionContext, -) +from dataclasses import dataclass +from app.core.common.authorization.base import Permission, PermissionContext + +@dataclass(frozen=True, slots=True) class DummyContext(PermissionContext): pass diff --git a/tests/app/unit/application/authz_service/test_authorize.py b/tests/unit/core/common/authorization/test_authorize.py similarity index 58% rename from tests/app/unit/application/authz_service/test_authorize.py rename to tests/unit/core/common/authorization/test_authorize.py index 084a7258..1a7eba21 100644 --- a/tests/app/unit/application/authz_service/test_authorize.py +++ b/tests/unit/core/common/authorization/test_authorize.py @@ -1,14 +1,8 @@ import pytest -from app.application.common.exceptions.authorization import AuthorizationError -from app.application.common.services.authorization.authorize import ( - authorize, -) -from tests.app.unit.application.authz_service.permission_stubs import ( - AlwaysAllow, - AlwaysDeny, - DummyContext, -) +from app.core.common.authorization.authorize import authorize +from app.core.common.authorization.exceptions import AuthorizationError +from tests.unit.core.common.authorization.permission_stubs import AlwaysAllow, AlwaysDeny, DummyContext def test_authorize_allows_when_permission_is_satisfied() -> None: diff --git a/tests/app/unit/application/authz_service/test_composite.py b/tests/unit/core/common/authorization/test_composite.py similarity index 68% rename from tests/app/unit/application/authz_service/test_composite.py rename to tests/unit/core/common/authorization/test_composite.py index 4e2617cf..8a26ed7f 100644 --- a/tests/app/unit/application/authz_service/test_composite.py +++ b/tests/unit/core/common/authorization/test_composite.py @@ -1,21 +1,20 @@ -from app.application.common.services.authorization.composite import AnyOf -from tests.app.unit.application.authz_service.permission_stubs import ( - AlwaysAllow, - AlwaysDeny, - DummyContext, -) +from app.core.common.authorization.composite import AnyOf +from tests.unit.core.common.authorization.permission_stubs import AlwaysAllow, AlwaysDeny, DummyContext def test_any_of_allows_if_at_least_one_allows() -> None: sut = AnyOf(AlwaysDeny(), AlwaysAllow()) + assert sut.is_satisfied_by(DummyContext()) def test_any_of_denies_if_all_deny() -> None: sut = AnyOf(AlwaysDeny(), AlwaysDeny()) + assert not sut.is_satisfied_by(DummyContext()) def test_any_of_empty_returns_false() -> None: sut: AnyOf[DummyContext] = AnyOf() + assert not sut.is_satisfied_by(DummyContext()) diff --git a/tests/unit/core/common/authorization/test_permissions.py b/tests/unit/core/common/authorization/test_permissions.py new file mode 100644 index 00000000..5bd0f5f1 --- /dev/null +++ b/tests/unit/core/common/authorization/test_permissions.py @@ -0,0 +1,107 @@ +from collections.abc import Callable + +import pytest + +from app.core.common.authorization.permissions import ( + CanManageRole, + CanManageSelf, + CanManageSubordinate, + RoleManagementContext, + UserManagementContext, +) +from app.core.common.entities.types_ import UserRole +from app.core.common.entities.user import User +from app.core.common.factories.id_factory import create_user_id +from tests.unit.core.common.authorization.factories import make_admin, make_super_admin, make_user +from tests.unit.core.common.services.factories import create_user + + +def test_can_manage_self() -> None: + subject = create_user(user_id=create_user_id()) + context = UserManagementContext(subject=subject, target=subject) + sut = CanManageSelf() + + assert sut.is_satisfied_by(context) + + +def test_cannot_manage_another_user() -> None: + subject = create_user(user_id=create_user_id()) + target = create_user(user_id=create_user_id()) + context = UserManagementContext(subject=subject, target=target) + sut = CanManageSelf() + + assert not sut.is_satisfied_by(context) + + +@pytest.mark.parametrize( + ("subject_factory", "target_factory"), + [ + pytest.param(make_super_admin, make_admin, id="super_admin_over_admin"), + pytest.param(make_super_admin, make_user, id="super_admin_over_user"), + pytest.param(make_admin, make_user, id="admin_over_user"), + ], +) +def test_can_manage_subordinate( + subject_factory: Callable[[], User], + target_factory: Callable[[], User], +) -> None: + context = UserManagementContext(subject=subject_factory(), target=target_factory()) + sut = CanManageSubordinate() + + assert sut.is_satisfied_by(context) + + +@pytest.mark.parametrize( + ("subject_factory", "target_factory"), + [ + pytest.param(make_super_admin, make_super_admin, id="super_admin_over_super_admin"), + pytest.param(make_admin, make_super_admin, id="admin_over_super_admin"), + pytest.param(make_admin, make_admin, id="admin_over_admin"), + pytest.param(make_user, make_admin, id="user_over_admin"), + ], +) +def test_cannot_manage_non_subordinate( + subject_factory: Callable[[], User], + target_factory: Callable[[], User], +) -> None: + context = UserManagementContext(subject=subject_factory(), target=target_factory()) + sut = CanManageSubordinate() + + assert not sut.is_satisfied_by(context) + + +@pytest.mark.parametrize( + ("subject_factory", "target_role"), + [ + pytest.param(make_super_admin, UserRole.ADMIN, id="super_admin_can_manage_admin_role"), + pytest.param(make_super_admin, UserRole.USER, id="super_admin_can_manage_user_role"), + pytest.param(make_admin, UserRole.USER, id="admin_can_manage_user_role"), + ], +) +def test_can_manage_role( + subject_factory: Callable[[], User], + target_role: UserRole, +) -> None: + context = RoleManagementContext(subject=subject_factory(), target_role=target_role) + sut = CanManageRole() + + assert sut.is_satisfied_by(context) + + +@pytest.mark.parametrize( + ("subject_factory", "target_role"), + [ + pytest.param(make_super_admin, UserRole.SUPER_ADMIN, id="super_admin_cannot_manage_super_admin_role"), + pytest.param(make_admin, UserRole.SUPER_ADMIN, id="admin_cannot_manage_super_admin_role"), + pytest.param(make_admin, UserRole.ADMIN, id="admin_cannot_manage_admin_role"), + pytest.param(make_user, UserRole.ADMIN, id="user_cannot_manage_admin_role"), + ], +) +def test_cannot_manage_role( + subject_factory: Callable[[], User], + target_role: UserRole, +) -> None: + context = RoleManagementContext(subject=subject_factory(), target_role=target_role) + sut = CanManageRole() + + assert not sut.is_satisfied_by(context) diff --git a/tests/app/integration/setup/__init__.py b/tests/unit/core/common/entities/__init__.py similarity index 100% rename from tests/app/integration/setup/__init__.py rename to tests/unit/core/common/entities/__init__.py diff --git a/tests/unit/core/common/entities/factories.py b/tests/unit/core/common/entities/factories.py new file mode 100644 index 00000000..714c96cb --- /dev/null +++ b/tests/unit/core/common/entities/factories.py @@ -0,0 +1,42 @@ +from tests.unit.core.common.entities.types_ import ( + NamedEntity, + NamedEntityId, + NamedEntitySubclass, + TaggedEntity, + TaggedEntityId, +) +from tests.unit.core.common.value_objects.types_ import SingleFieldVO + + +def create_single_field_vo(value: int = 1) -> SingleFieldVO: + return SingleFieldVO(value) + + +def create_named_entity_id(id_: int = 42) -> NamedEntityId: + return NamedEntityId(id_) + + +def create_named_entity( + id_: int = 42, + name: str = "name", +) -> NamedEntity: + return NamedEntity(id_=NamedEntityId(id_), name=name) + + +def create_named_entity_subclass( + id_: int = 42, + name: str = "name", + value: int = 314, +) -> NamedEntitySubclass: + return NamedEntitySubclass(id_=NamedEntityId(id_), name=name, value=value) + + +def create_tagged_entity_id(id_: int = 54) -> TaggedEntityId: + return TaggedEntityId(id_) + + +def create_tagged_entity( + id_: int = 54, + tag: str = "tag", +) -> TaggedEntity: + return TaggedEntity(id_=TaggedEntityId(id_), tag=tag) diff --git a/tests/app/unit/domain/entities/test_base.py b/tests/unit/core/common/entities/test_base.py similarity index 91% rename from tests/app/unit/domain/entities/test_base.py rename to tests/unit/core/common/entities/test_base.py index 933bd983..c20d47b6 100644 --- a/tests/app/unit/domain/entities/test_base.py +++ b/tests/unit/core/common/entities/test_base.py @@ -1,13 +1,13 @@ import pytest -from app.domain.entities.base import Entity -from tests.app.unit.factories.named_entity import ( +from app.core.common.entities.base import Entity +from tests.unit.core.common.entities.factories import ( create_named_entity, create_named_entity_id, create_named_entity_subclass, + create_single_field_vo, + create_tagged_entity, ) -from tests.app.unit.factories.tagged_entity import create_tagged_entity -from tests.app.unit.factories.value_objects import create_single_field_vo def test_cannot_init() -> None: diff --git a/tests/unit/core/common/entities/types_.py b/tests/unit/core/common/entities/types_.py new file mode 100644 index 00000000..afda9fe3 --- /dev/null +++ b/tests/unit/core/common/entities/types_.py @@ -0,0 +1,32 @@ +from dataclasses import dataclass + +from app.core.common.entities.base import Entity +from app.core.common.value_objects.base import ValueObject + + +@dataclass(frozen=True, slots=True, repr=False) +class NamedEntityId(ValueObject): + value: int + + +class NamedEntity(Entity[NamedEntityId]): + def __init__(self, *, id_: NamedEntityId, name: str) -> None: + super().__init__(id_=id_) + self.name = name + + +class NamedEntitySubclass(NamedEntity): + def __init__(self, *, id_: NamedEntityId, name: str, value: int) -> None: + super().__init__(id_=id_, name=name) + self.value = value + + +@dataclass(frozen=True, slots=True, repr=False) +class TaggedEntityId(ValueObject): + value: int + + +class TaggedEntity(Entity[TaggedEntityId]): + def __init__(self, *, id_: TaggedEntityId, tag: str) -> None: + super().__init__(id_=id_) + self.tag = tag diff --git a/tests/app/performance/__init__.py b/tests/unit/core/common/services/__init__.py similarity index 100% rename from tests/app/performance/__init__.py rename to tests/unit/core/common/services/__init__.py diff --git a/tests/unit/core/common/services/conftest.py b/tests/unit/core/common/services/conftest.py new file mode 100644 index 00000000..034b0192 --- /dev/null +++ b/tests/unit/core/common/services/conftest.py @@ -0,0 +1,12 @@ +from typing import cast +from unittest.mock import create_autospec + +import pytest + +from app.core.common.ports.password_hasher import PasswordHasher +from tests.unit.core.common.services.mock_types import PasswordHasherMock + + +@pytest.fixture +def password_hasher() -> PasswordHasherMock: + return cast(PasswordHasherMock, create_autospec(PasswordHasher, instance=True)) diff --git a/tests/unit/core/common/services/factories.py b/tests/unit/core/common/services/factories.py new file mode 100644 index 00000000..2ab1707d --- /dev/null +++ b/tests/unit/core/common/services/factories.py @@ -0,0 +1,88 @@ +import uuid +from datetime import UTC, datetime +from uuid import UUID + +from app.core.common.entities.types_ import UserId, UserPasswordHash, UserRole +from app.core.common.entities.user import User +from app.core.common.ports.password_hasher import PasswordHasher +from app.core.common.services.user import UserService +from app.core.common.value_objects.raw_password import RawPassword +from app.core.common.value_objects.username import Username +from app.core.common.value_objects.utc_datetime import UtcDatetime +from tests.unit.core.common.services.stubs import StubPasswordHasher + + +def create_user_id(value: UUID | None = None) -> UserId: + return UserId(value if value is not None else uuid.uuid4()) + + +def create_username(value: str | None = None) -> Username: + default = f"user_{uuid.uuid4().hex[:8]}" + return Username(value if value is not None else default) + + +def create_raw_password(value: str | None = None) -> RawPassword: + default = uuid.uuid4().hex + return RawPassword(value if value is not None else default) + + +def create_password_hash(value: bytes | None = None) -> UserPasswordHash: + default = uuid.uuid4().bytes + return UserPasswordHash(value if value is not None else default) + + +def create_now(value: datetime | None = None) -> UtcDatetime: + default = datetime.now(UTC) + return UtcDatetime(value if value is not None else default) + + +def create_role(value: str | None = None) -> UserRole: + return UserRole(value) if value is not None else UserRole.USER + + +def create_is_active(value: bool | None = None) -> bool: + return value if value is not None else True + + +def create_user_service(password_hasher: PasswordHasher | None = None) -> UserService: + return UserService(password_hasher=password_hasher if password_hasher is not None else StubPasswordHasher()) + + +def create_user( + *, + user_id: UserId | None = None, + username: Username | None = None, + password_hash: UserPasswordHash | None = None, + now: UtcDatetime | None = None, + role: UserRole | None = None, + is_active: bool | None = None, +) -> User: + user_service = create_user_service() + return user_service.create_user( + user_id=user_id if user_id is not None else create_user_id(), + username=username if username is not None else create_username(), + password_hash=password_hash if password_hash is not None else create_password_hash(), + now=now if now is not None else create_now(), + role=role if role is not None else create_role(), + is_active=is_active if is_active is not None else create_is_active(), + ) + + +def create_super_user( + *, + user_id: UserId | None = None, + username: Username | None = None, + password_hash: UserPasswordHash | None = None, + now: UtcDatetime | None = None, + is_active: bool | None = None, +) -> User: + now_ = now if now is not None else create_now() + return User( + id_=user_id if user_id is not None else create_user_id(), + username=username if username is not None else create_username(), + password_hash=password_hash if password_hash is not None else create_password_hash(), + role=UserRole.SUPER_ADMIN, + is_active=is_active if is_active is not None else create_is_active(), + created_at=now_, + updated_at=now_, + ) diff --git a/tests/app/unit/domain/services/mock_types.py b/tests/unit/core/common/services/mock_types.py similarity index 51% rename from tests/app/unit/domain/services/mock_types.py rename to tests/unit/core/common/services/mock_types.py index c4e7614c..049d43dd 100644 --- a/tests/app/unit/domain/services/mock_types.py +++ b/tests/unit/core/common/services/mock_types.py @@ -1,9 +1,5 @@ from typing import Protocol -from unittest.mock import AsyncMock, Mock - - -class UserIdGeneratorMock(Protocol): - generate: Mock +from unittest.mock import AsyncMock class PasswordHasherMock(Protocol): diff --git a/tests/unit/core/common/services/stubs.py b/tests/unit/core/common/services/stubs.py new file mode 100644 index 00000000..d210712c --- /dev/null +++ b/tests/unit/core/common/services/stubs.py @@ -0,0 +1,13 @@ +import hashlib + +from app.core.common.entities.types_ import UserPasswordHash +from app.core.common.ports.password_hasher import PasswordHasher +from app.core.common.value_objects.raw_password import RawPassword + + +class StubPasswordHasher(PasswordHasher): + async def hash(self, raw_password: RawPassword) -> UserPasswordHash: + return UserPasswordHash(hashlib.sha256(raw_password.value).digest()) + + async def verify(self, raw_password: RawPassword, hashed_password: UserPasswordHash) -> bool: + return await self.hash(raw_password) == hashed_password diff --git a/tests/unit/core/common/services/test_stubs.py b/tests/unit/core/common/services/test_stubs.py new file mode 100644 index 00000000..34938b07 --- /dev/null +++ b/tests/unit/core/common/services/test_stubs.py @@ -0,0 +1,30 @@ +import pytest + +from tests.unit.core.common.services.factories import create_raw_password +from tests.unit.core.common.services.stubs import StubPasswordHasher + + +@pytest.mark.asyncio +async def test_stub_password_hasher_verify_true_and_false() -> None: + raw_ok = create_raw_password("test-password") + raw_bad = create_raw_password("wrong-password") + sut = StubPasswordHasher() + + hashed_ok = await sut.hash(raw_ok) + + assert await sut.verify(raw_ok, hashed_ok) is True + assert await sut.verify(raw_bad, hashed_ok) is False + + +@pytest.mark.asyncio +async def test_stub_password_hasher_hash_is_deterministic() -> None: + raw1 = create_raw_password() + raw2 = create_raw_password() + sut = StubPasswordHasher() + + h1a = await sut.hash(raw1) + h1b = await sut.hash(raw1) + h2 = await sut.hash(raw2) + + assert h1a == h1b + assert h1a != h2 diff --git a/tests/unit/core/common/services/test_user.py b/tests/unit/core/common/services/test_user.py new file mode 100644 index 00000000..76d6297d --- /dev/null +++ b/tests/unit/core/common/services/test_user.py @@ -0,0 +1,264 @@ +import pytest + +from app.core.common.entities.types_ import UserRole +from app.core.common.entities.user import User +from app.core.common.exceptions import ( + ActivationChangeNotPermittedError, + RoleAssignmentNotPermittedError, + RoleChangeNotPermittedError, +) +from tests.unit.core.common.services.factories import ( + create_now, + create_password_hash, + create_raw_password, + create_super_user, + create_user, + create_user_id, + create_user_service, + create_username, +) +from tests.unit.core.common.services.mock_types import PasswordHasherMock + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "role", + [UserRole.USER, UserRole.ADMIN], +) +async def test_creates_active_user_with_hashed_password( + role: UserRole, + password_hasher: PasswordHasherMock, +) -> None: + sut = create_user_service(password_hasher=password_hasher) + user_id = create_user_id() + username = create_username() + raw_password = create_raw_password() + expected_hash = create_password_hash() + created_at = create_now() + password_hasher.hash.return_value = expected_hash + + user = await sut.create_user_with_raw_password( + user_id=user_id, + username=username, + raw_password=raw_password, + now=created_at, + role=role, + ) + + assert isinstance(user, User) + assert user.id_ == user_id + assert user.username == username + assert user.password_hash == expected_hash + assert user.role == role + assert user.is_active is True + assert user.created_at == created_at + assert user.updated_at == created_at + + +@pytest.mark.asyncio +async def test_creates_inactive_user_if_specified(password_hasher: PasswordHasherMock) -> None: + sut = create_user_service(password_hasher=password_hasher) + user_id = create_user_id() + username = create_username() + raw_password = create_raw_password() + created_at = create_now() + password_hasher.hash.return_value = create_password_hash() + + user = await sut.create_user_with_raw_password( + user_id=user_id, + username=username, + raw_password=raw_password, + now=created_at, + is_active=False, + ) + + assert not user.is_active + assert user.created_at == created_at + assert user.updated_at == created_at + + +@pytest.mark.asyncio +async def test_fails_to_create_user_with_unassignable_role() -> None: + sut = create_user_service() + user_id = create_user_id() + username = create_username() + raw_password = create_raw_password() + + with pytest.raises(RoleAssignmentNotPermittedError): + await sut.create_user_with_raw_password( + user_id=user_id, + username=username, + raw_password=raw_password, + now=create_now(), + role=UserRole.SUPER_ADMIN, + ) + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + ("password", "expected"), + [ + pytest.param("test-password", True, id="valid"), + pytest.param("wrong-password", False, id="invalid"), + ], +) +async def test_checks_password_authenticity(password: str, expected: bool) -> None: + sut = create_user_service() + user = await sut.create_user_with_raw_password( + user_id=create_user_id(), + username=create_username(), + raw_password=create_raw_password("test-password"), + now=create_now(), + ) + + assert await sut.is_password_valid(user, create_raw_password(password)) is expected + + +@pytest.mark.asyncio +async def test_changes_password() -> None: + sut = create_user_service() + old_raw = create_raw_password() + created_at = create_now() + user = await sut.create_user_with_raw_password( + user_id=create_user_id(), + username=create_username(), + raw_password=old_raw, + now=created_at, + ) + initial_hash = user.password_hash + new_raw = create_raw_password() + updated_at = create_now() + + await sut.change_password(user, new_raw, now=updated_at) + + assert user.password_hash != initial_hash + assert await sut.is_password_valid(user, new_raw) is True + assert await sut.is_password_valid(user, old_raw) is False + assert user.created_at == created_at + assert user.updated_at == updated_at + + +@pytest.mark.parametrize( + ("initial_role", "target_is_admin", "expected_role"), + [ + pytest.param(UserRole.USER, True, UserRole.ADMIN, id="user_to_admin"), + pytest.param(UserRole.ADMIN, False, UserRole.USER, id="admin_to_user"), + ], +) +def test_set_role_changes_role_when_needed( + initial_role: UserRole, + target_is_admin: bool, + expected_role: UserRole, +) -> None: + sut = create_user_service() + created_at = create_now() + user = create_user(role=initial_role, now=created_at) + updated_at = create_now() + + result = sut.set_role(user, now=updated_at, is_admin=target_is_admin) + + assert result is True + assert user.role == expected_role + assert user.created_at == created_at + assert user.updated_at == updated_at + + +@pytest.mark.parametrize( + ("role", "is_admin"), + [ + pytest.param(UserRole.USER, False, id="already_user"), + pytest.param(UserRole.ADMIN, True, id="already_admin"), + ], +) +def test_set_role_does_nothing_when_already_in_target_role( + role: UserRole, + is_admin: bool, +) -> None: + sut = create_user_service() + created_at = create_now() + user = create_user(role=role, now=created_at) + attempt_at = create_now() + + result = sut.set_role(user, now=attempt_at, is_admin=is_admin) + + assert result is False + assert user.role == role + assert user.created_at == created_at + assert user.updated_at == created_at + + +@pytest.mark.parametrize( + "is_admin", + [True, False], +) +def test_preserves_super_admin_role(is_admin: bool) -> None: + sut = create_user_service() + created_at = create_now() + user = create_super_user(now=created_at) + + with pytest.raises(RoleChangeNotPermittedError): + sut.set_role(user, now=create_now(), is_admin=is_admin) + + assert user.role == UserRole.SUPER_ADMIN + assert user.updated_at == created_at + + +@pytest.mark.parametrize( + ("initial_state", "target_state"), + [ + pytest.param(True, False, id="active_to_inactive"), + pytest.param(False, True, id="inactive_to_active"), + ], +) +def test_set_activation_changes_state_when_needed( + initial_state: bool, + target_state: bool, +) -> None: + sut = create_user_service() + created_at = create_now() + user = create_user(is_active=initial_state, now=created_at) + updated_at = create_now() + + result = sut.set_activation(user, now=updated_at, is_active=target_state) + + assert result is True + assert user.is_active is target_state + assert user.created_at == created_at + assert user.updated_at == updated_at + + +@pytest.mark.parametrize( + "state", + [ + pytest.param(True, id="already_active"), + pytest.param(False, id="already_inactive"), + ], +) +def test_set_activation_does_nothing_when_already_in_target_state(state: bool) -> None: + sut = create_user_service() + created_at = create_now() + user = create_user(is_active=state, now=created_at) + attempt_at = create_now() + + result = sut.set_activation(user, now=attempt_at, is_active=state) + + assert result is False + assert user.is_active is state + assert user.created_at == created_at + assert user.updated_at == created_at + + +@pytest.mark.parametrize( + "is_active", + [True, False], +) +def test_preserves_system_user_activation_state(is_active: bool) -> None: + sut = create_user_service() + created_at = create_now() + user = create_super_user(now=created_at, is_active=is_active) + + with pytest.raises(ActivationChangeNotPermittedError): + sut.set_activation(user, now=create_now(), is_active=not is_active) + + assert user.is_active is is_active + assert user.updated_at == created_at diff --git a/tests/app/unit/__init__.py b/tests/unit/core/common/value_objects/__init__.py similarity index 100% rename from tests/app/unit/__init__.py rename to tests/unit/core/common/value_objects/__init__.py diff --git a/tests/app/unit/domain/value_objects/test_base.py b/tests/unit/core/common/value_objects/test_base.py similarity index 60% rename from tests/app/unit/domain/value_objects/test_base.py rename to tests/unit/core/common/value_objects/test_base.py index 190b3ea5..b86cc2f4 100644 --- a/tests/app/unit/domain/value_objects/test_base.py +++ b/tests/unit/core/common/value_objects/test_base.py @@ -1,13 +1,16 @@ from dataclasses import FrozenInstanceError, dataclass, field, fields -from typing import ClassVar, Final +from typing import ClassVar import pytest -from app.domain.value_objects.base import ValueObject -from tests.app.unit.factories.value_objects import ( - create_multi_field_vo, - create_single_field_vo, -) +from app.core.common.value_objects.base import ValueObject +from tests.unit.core.common.value_objects.types_ import SingleFieldVO + + +@dataclass(frozen=True, slots=True, repr=False) +class MultiFieldVO(ValueObject): + value1: int + value2: str def test_cannot_init() -> None: @@ -28,7 +31,7 @@ def test_child_cannot_init_with_only_class_fields() -> None: @dataclass(frozen=True) class ClassFieldsVO(ValueObject): foo: ClassVar[int] = 0 - bar: ClassVar[Final[str]] = "baz" + bar: ClassVar[str] = "baz" with pytest.raises(TypeError): ClassFieldsVO() @@ -41,8 +44,8 @@ class MixedFieldsVO(ValueObject): bar: str sut = MixedFieldsVO(bar="baz") - sut_fields = fields(sut) + sut_fields = fields(sut) assert len(sut_fields) == 1 assert sut_fields[0].name == "bar" assert sut_fields[0].type is str @@ -62,7 +65,7 @@ def test_class_field_final_equivalence() -> None: @dataclass(frozen=True) class MixedFieldsVO: a: ClassVar[int] = 1 - b: ClassVar[Final[str]] = "bar" + b: ClassVar[str] = "bar" c: str = "baz" sut_field_names = [f.name for f in fields(MixedFieldsVO)] @@ -71,38 +74,40 @@ class MixedFieldsVO: def test_is_immutable() -> None: - vo_value = 123 - sut = create_single_field_vo(vo_value) + sut = SingleFieldVO(1) with pytest.raises(FrozenInstanceError): # noinspection PyDataclass - sut.value = vo_value + 1 # type: ignore[misc] + sut.value = sut.value + 1 # type: ignore[misc] def test_equality() -> None: - vo1 = create_multi_field_vo() - vo2 = create_multi_field_vo() + vo_value_1 = 1 + vo_value_2 = "Alice" + sut1 = MultiFieldVO(value1=vo_value_1, value2=vo_value_2) + sut2 = MultiFieldVO(value1=vo_value_1, value2=vo_value_2) - assert vo1 == vo2 + assert sut1 == sut2 def test_inequality() -> None: - vo1 = create_multi_field_vo(value2="one") - vo2 = create_multi_field_vo(value2="two") + vo_value_1 = 1 + sut1 = MultiFieldVO(value1=vo_value_1, value2="Alice") + sut2 = MultiFieldVO(value1=vo_value_1, value2="Bob") - assert vo1 != vo2 + assert sut1 != sut2 def test_single_field_vo_repr() -> None: - sut = create_single_field_vo(123) + sut = SingleFieldVO(1) - assert repr(sut) == "SingleFieldVO(123)" + assert repr(sut) == "SingleFieldVO(1)" def test_multi_field_vo_repr() -> None: - sut = create_multi_field_vo(value1=123, value2="abc") + sut = MultiFieldVO(value1=1, value2="Alice") - assert repr(sut) == "MultiFieldVO(value1=123, value2='abc')" + assert repr(sut) == "MultiFieldVO(value1=1, value2='Alice')" def test_class_field_not_in_repr() -> None: @@ -110,7 +115,7 @@ def test_class_field_not_in_repr() -> None: class MixedFieldsVO(ValueObject): baz: int foo: ClassVar[int] = 0 - bar: ClassVar[Final[str]] = "baz" + bar: ClassVar[str] = "baz" sut = MixedFieldsVO(baz=1) @@ -123,9 +128,9 @@ class HiddenFieldVO(ValueObject): visible: int hidden: int = field(repr=False) - sut = HiddenFieldVO(123, 456) + sut = HiddenFieldVO(1, 2) - assert repr(sut) == "HiddenFieldVO(123)" + assert repr(sut) == "HiddenFieldVO(1)" def test_all_fields_hidden_repr() -> None: @@ -134,6 +139,23 @@ class HiddenFieldVO(ValueObject): hidden_1: int = field(repr=False) hidden_2: int = field(repr=False) - sut = HiddenFieldVO(123, 456) + sut = HiddenFieldVO(1, 2) assert repr(sut) == "HiddenFieldVO()" + + +def test_is_hashable() -> None: + sut1 = MultiFieldVO(value1=123, value2="abc") + sut2 = MultiFieldVO(value1=123, value2="abc") + sut3 = MultiFieldVO(value1=456, value2="def") + + dict_with_sut_as_keys = { + sut1: "value1", + sut3: "value3", + } + + assert dict_with_sut_as_keys[sut1] == "value1" + assert dict_with_sut_as_keys[sut2] == "value1" + assert dict_with_sut_as_keys[sut3] == "value3" + assert len(dict_with_sut_as_keys) == 2 + assert hash(sut1) == hash(sut2) != hash(sut3) diff --git a/tests/app/unit/domain/value_objects/test_raw_password.py b/tests/unit/core/common/value_objects/test_raw_password.py similarity index 60% rename from tests/app/unit/domain/value_objects/test_raw_password.py rename to tests/unit/core/common/value_objects/test_raw_password.py index e70ff950..086c740e 100644 --- a/tests/app/unit/domain/value_objects/test_raw_password.py +++ b/tests/unit/core/common/value_objects/test_raw_password.py @@ -1,7 +1,7 @@ import pytest -from app.domain.exceptions.base import DomainTypeError -from app.domain.value_objects.raw_password import RawPassword +from app.core.common.exceptions import BusinessTypeError +from app.core.common.value_objects.raw_password import RawPassword def test_accepts_boundary_length() -> None: @@ -13,5 +13,5 @@ def test_accepts_boundary_length() -> None: def test_rejects_out_of_bounds_length() -> None: password = "a" * (RawPassword.MIN_LEN - 1) - with pytest.raises(DomainTypeError): + with pytest.raises(BusinessTypeError): RawPassword(password) diff --git a/tests/app/unit/domain/value_objects/test_username.py b/tests/unit/core/common/value_objects/test_username.py similarity index 86% rename from tests/app/unit/domain/value_objects/test_username.py rename to tests/unit/core/common/value_objects/test_username.py index 2f12625a..17d774c2 100644 --- a/tests/app/unit/domain/value_objects/test_username.py +++ b/tests/unit/core/common/value_objects/test_username.py @@ -1,7 +1,7 @@ import pytest -from app.domain.exceptions.base import DomainTypeError -from app.domain.value_objects.username import Username +from app.core.common.exceptions import BusinessTypeError +from app.core.common.value_objects.username import Username @pytest.mark.parametrize( @@ -23,7 +23,7 @@ def test_accepts_boundary_length(username: str) -> None: ], ) def test_rejects_out_of_bounds_length(username: str) -> None: - with pytest.raises(DomainTypeError): + with pytest.raises(BusinessTypeError): Username(username) @@ -63,5 +63,5 @@ def test_accepts_correct_names(username: str) -> None: ], ) def test_rejects_incorrect_names(username: str) -> None: - with pytest.raises(DomainTypeError): + with pytest.raises(BusinessTypeError): Username(username) diff --git a/tests/unit/core/common/value_objects/test_utc_datetime.py b/tests/unit/core/common/value_objects/test_utc_datetime.py new file mode 100644 index 00000000..9a3c5633 --- /dev/null +++ b/tests/unit/core/common/value_objects/test_utc_datetime.py @@ -0,0 +1,33 @@ +from datetime import UTC, datetime, timedelta +from zoneinfo import ZoneInfo + +import pytest + +from app.core.common.exceptions import BusinessTypeError +from app.core.common.value_objects.utc_datetime import UtcDatetime + + +def test_normalizes_value_to_utc_when_input_has_offset() -> None: + raw = datetime.fromisoformat("2024-01-01T03:00:00+03:00") + + sut = UtcDatetime(raw) + + assert sut.value.utcoffset() == timedelta(0) + assert sut.value == datetime(2024, 1, 1, 0, 0, tzinfo=UTC) + assert sut.value.timestamp() == raw.timestamp() + + +def test_preserves_same_timestamp_when_input_is_zoneinfo() -> None: + raw = datetime(2024, 1, 15, 6, 0, tzinfo=ZoneInfo("Asia/Almaty")) + + sut = UtcDatetime(raw) + + assert sut.value.utcoffset() == timedelta(0) + assert sut.value.timestamp() == raw.timestamp() + + +def test_raises_when_input_is_naive_datetime() -> None: + raw = datetime.fromisoformat("2024-01-01T03:00:00") + + with pytest.raises(BusinessTypeError): + UtcDatetime(raw) diff --git a/tests/unit/core/common/value_objects/types_.py b/tests/unit/core/common/value_objects/types_.py new file mode 100644 index 00000000..1f53e459 --- /dev/null +++ b/tests/unit/core/common/value_objects/types_.py @@ -0,0 +1,8 @@ +from dataclasses import dataclass + +from app.core.common.value_objects.base import ValueObject + + +@dataclass(frozen=True, slots=True, repr=False) +class SingleFieldVO(ValueObject): + value: int diff --git a/tests/app/unit/application/__init__.py b/tests/unit/core/queries/__init__.py similarity index 100% rename from tests/app/unit/application/__init__.py rename to tests/unit/core/queries/__init__.py diff --git a/tests/app/unit/application/authz_service/__init__.py b/tests/unit/core/queries/query_support/__init__.py similarity index 100% rename from tests/app/unit/application/authz_service/__init__.py rename to tests/unit/core/queries/query_support/__init__.py diff --git a/tests/unit/core/queries/query_support/test_offset_pagination.py b/tests/unit/core/queries/query_support/test_offset_pagination.py new file mode 100644 index 00000000..ccb429e8 --- /dev/null +++ b/tests/unit/core/queries/query_support/test_offset_pagination.py @@ -0,0 +1,37 @@ +import pytest + +from app.core.queries.query_support.exceptions import PaginationError +from app.core.queries.query_support.offset_pagination import OffsetPaginationParams + + +def test_accepts_valid_params() -> None: + sut = OffsetPaginationParams(limit=10, offset=0) + + assert sut.limit == 10 + assert sut.offset == 0 + + +def test_limit_must_be_greater_than_0() -> None: + with pytest.raises(PaginationError): + OffsetPaginationParams(limit=0, offset=0) + + +def test_limit_cannot_exceed_max_int32() -> None: + with pytest.raises(PaginationError): + OffsetPaginationParams( + limit=OffsetPaginationParams.MAX_INT32 + 1, + offset=0, + ) + + +def test_offset_must_be_non_negative() -> None: + with pytest.raises(PaginationError): + OffsetPaginationParams(limit=1, offset=-1) + + +def test_offset_cannot_exceed_max_int32() -> None: + with pytest.raises(PaginationError): + OffsetPaginationParams( + limit=1, + offset=OffsetPaginationParams.MAX_INT32 + 1, + ) diff --git a/tests/app/unit/domain/__init__.py b/tests/unit/infrastructure/__init__.py similarity index 100% rename from tests/app/unit/domain/__init__.py rename to tests/unit/infrastructure/__init__.py diff --git a/tests/app/unit/infrastructure/conftest.py b/tests/unit/infrastructure/conftest.py similarity index 78% rename from tests/app/unit/infrastructure/conftest.py rename to tests/unit/infrastructure/conftest.py index 0d901ba8..71735bac 100644 --- a/tests/app/unit/infrastructure/conftest.py +++ b/tests/unit/infrastructure/conftest.py @@ -5,8 +5,11 @@ import pytest -from app.infrastructure.adapters.password_hasher_bcrypt import BcryptPasswordHasher -from app.infrastructure.adapters.types import HasherSemaphore, HasherThreadPoolExecutor +from app.infrastructure.adapters.bcrypt_password_hasher import ( + BcryptPasswordHasher, + HasherSemaphore, + HasherThreadPoolExecutor, +) @pytest.fixture(scope="session") @@ -18,9 +21,7 @@ def hasher_max_threads() -> int: def hasher_threadpool_executor( hasher_max_threads: int, ) -> Iterator[HasherThreadPoolExecutor]: - executor = HasherThreadPoolExecutor( - ThreadPoolExecutor(max_workers=hasher_max_threads) - ) + executor = HasherThreadPoolExecutor(ThreadPoolExecutor(max_workers=hasher_max_threads)) yield executor executor.shutdown(wait=True, cancel_futures=True) diff --git a/tests/app/unit/infrastructure/test_password_hasher_bcrypt.py b/tests/unit/infrastructure/test_bcrypt_password_hasher.py similarity index 92% rename from tests/app/unit/infrastructure/test_password_hasher_bcrypt.py rename to tests/unit/infrastructure/test_bcrypt_password_hasher.py index 706c4300..08822758 100644 --- a/tests/app/unit/infrastructure/test_password_hasher_bcrypt.py +++ b/tests/unit/infrastructure/test_bcrypt_password_hasher.py @@ -2,10 +2,8 @@ import pytest -from app.infrastructure.adapters.password_hasher_bcrypt import ( - BcryptPasswordHasher, -) -from tests.app.unit.factories.value_objects import create_raw_password +from app.infrastructure.adapters.bcrypt_password_hasher import BcryptPasswordHasher +from tests.unit.core.common.services.factories import create_raw_password @pytest.mark.slow diff --git a/uv.lock b/uv.lock index f70d986d..657897ea 100644 --- a/uv.lock +++ b/uv.lock @@ -4,38 +4,38 @@ requires-python = "==3.13.*" [[package]] name = "alembic" -version = "1.17.1" +version = "1.18.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mako" }, { name = "sqlalchemy" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6e/b6/2a81d7724c0c124edc5ec7a167e85858b6fd31b9611c6fb8ecf617b7e2d3/alembic-1.17.1.tar.gz", hash = "sha256:8a289f6778262df31571d29cca4c7fbacd2f0f582ea0816f4c399b6da7528486", size = 1981285, upload-time = "2025-10-29T00:23:16.667Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/13/8b084e0f2efb0275a1d534838844926f798bd766566b1375174e2448cd31/alembic-1.18.4.tar.gz", hash = "sha256:cb6e1fd84b6174ab8dbb2329f86d631ba9559dd78df550b57804d607672cedbc", size = 2056725, upload-time = "2026-02-10T16:00:47.195Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/32/7df1d81ec2e50fb661944a35183d87e62d3f6c6d9f8aff64a4f245226d55/alembic-1.17.1-py3-none-any.whl", hash = "sha256:cbc2386e60f89608bb63f30d2d6cc66c7aaed1fe105bd862828600e5ad167023", size = 247848, upload-time = "2025-10-29T00:23:18.79Z" }, + { url = "https://files.pythonhosted.org/packages/d2/29/6533c317b74f707ea28f8d633734dbda2119bbadfc61b2f3640ba835d0f7/alembic-1.18.4-py3-none-any.whl", hash = "sha256:a5ed4adcf6d8a4cb575f3d759f071b03cd6e5c7618eb796cb52497be25bfe19a", size = 263893, upload-time = "2026-02-10T16:00:49.997Z" }, ] [[package]] name = "alembic-postgresql-enum" -version = "1.8.0" +version = "1.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alembic" }, { name = "sqlalchemy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/04/e465cb5c051fb056b7fadda7667b3e1fb4d32d7f19533e3bbff071c73788/alembic_postgresql_enum-1.8.0.tar.gz", hash = "sha256:132cd5fdc4a2a0b6498f3d89ea1c7b2a5ddc3281ddd84edae7259ec4c0a215a0", size = 15858, upload-time = "2025-07-20T12:25:50.626Z" } +sdist = { url = "https://files.pythonhosted.org/packages/63/cf/b5147b926441e7cdc12f306465dac488ff22364b1467a09a5d43743b8cfc/alembic_postgresql_enum-1.10.0.tar.gz", hash = "sha256:ea23481de3d6a00d68d369f92a46ff4fa27b2575e39c583145f1e23ea9c7511d", size = 18557, upload-time = "2026-02-23T16:15:04.148Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/80/4e6e841f9a0403b520b8f28650c2cdf5905e25bd4ff403b43daec580fed3/alembic_postgresql_enum-1.8.0-py3-none-any.whl", hash = "sha256:0e62833f8d1aca2c58fa09cae1d4a52472fb32d2dde32b68c84515fffcf401d5", size = 23697, upload-time = "2025-07-20T12:25:49.048Z" }, + { url = "https://files.pythonhosted.org/packages/58/8a/4db3821584824ac68df00a6ca27331d3f41f40105f22a8dc1b30b1ec83af/alembic_postgresql_enum-1.10.0-py3-none-any.whl", hash = "sha256:39297cb5dc0e71a3e08df1feeab9ffcc1b6aac0be02bf578748d74c442cd3fbf", size = 27774, upload-time = "2026-02-23T16:15:02.789Z" }, ] [[package]] name = "annotated-doc" -version = "0.0.3" +version = "0.0.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/a6/dc46877b911e40c00d395771ea710d5e77b6de7bacd5fdcd78d70cc5a48f/annotated_doc-0.0.3.tar.gz", hash = "sha256:e18370014c70187422c33e945053ff4c286f453a984eba84d0dbfa0c935adeda", size = 5535, upload-time = "2025-10-24T14:57:10.718Z" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/b7/cf592cb5de5cb3bade3357f8d2cf42bf103bbe39f459824b4939fd212911/annotated_doc-0.0.3-py3-none-any.whl", hash = "sha256:348ec6664a76f1fd3be81f43dffbee4c7e8ce931ba71ec67cc7f4ade7fbbb580", size = 5488, upload-time = "2025-10-24T14:57:09.462Z" }, + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, ] [[package]] @@ -49,15 +49,26 @@ wheels = [ [[package]] name = "anyio" -version = "4.11.0" +version = "4.12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, +] + +[[package]] +name = "asgi-lifespan" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ { name = "sniffio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/da/e7908b54e0f8043725a990bf625f2041ecf6bfe8eb7b19407f1c00b630f7/asgi-lifespan-2.1.0.tar.gz", hash = "sha256:5e2effaf0bfe39829cf2d64e7ecc47c7d86d676a6599f7afba378c31f5e3a308", size = 15627, upload-time = "2023-03-28T17:35:49.126Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, + { url = "https://files.pythonhosted.org/packages/2f/f5/c36551e93acba41a59939ae6a0fb77ddb3f2e8e8caa716410c65f7341f72/asgi_lifespan-2.1.0-py3-none-any.whl", hash = "sha256:ed840706680e28428c01e14afb3875d7d76d3206f3d5b2f2294e059b5c23804f", size = 10895, upload-time = "2023-03-28T17:35:47.772Z" }, ] [[package]] @@ -113,6 +124,42 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/27/44/d2ef5e87509158ad2187f4dd0852df80695bb1ee0cfe0a684727b01a69e0/bcrypt-5.0.0-cp39-abi3-win_arm64.whl", hash = "sha256:f2347d3534e76bf50bca5500989d6c1d05ed64b440408057a37673282c654927", size = 144953, upload-time = "2025-09-25T19:50:37.32Z" }, ] +[[package]] +name = "boolean-py" +version = "5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/cf/85379f13b76f3a69bca86b60237978af17d6aa0bc5998978c3b8cf05abb2/boolean_py-5.0.tar.gz", hash = "sha256:60cbc4bad079753721d32649545505362c754e121570ada4658b852a3a318d95", size = 37047, upload-time = "2025-04-03T10:39:49.734Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/ca/78d423b324b8d77900030fa59c4aa9054261ef0925631cd2501dd015b7b7/boolean_py-5.0-py3-none-any.whl", hash = "sha256:ef28a70bd43115208441b53a045d1549e2f0ec6e3d08a9d142cbc41c1938e8d9", size = 26577, upload-time = "2025-04-03T10:39:48.449Z" }, +] + +[[package]] +name = "cachecontrol" +version = "0.14.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "msgpack" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2d/f6/c972b32d80760fb79d6b9eeb0b3010a46b89c0b23cf6329417ff7886cd22/cachecontrol-0.14.4.tar.gz", hash = "sha256:e6220afafa4c22a47dd0badb319f84475d79108100d04e26e8542ef7d3ab05a1", size = 16150, upload-time = "2025-11-14T04:32:13.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/79/c45f2d53efe6ada1110cf6f9fca095e4ff47a0454444aefdde6ac4789179/cachecontrol-0.14.4-py3-none-any.whl", hash = "sha256:b7ac014ff72ee199b5f8af1de29d60239954f223e948196fa3d84adaffc71d2b", size = 22247, upload-time = "2025-11-14T04:32:11.733Z" }, +] + +[package.optional-dependencies] +filecache = [ + { name = "filelock" }, +] + +[[package]] +name = "certifi" +version = "2026.2.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, +] + [[package]] name = "cffi" version = "2.0.0" @@ -138,23 +185,48 @@ wheels = [ [[package]] name = "cfgv" -version = "3.4.0" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" 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, upload-time = "2023-08-12T20:38:17.776Z" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, ] [[package]] name = "click" -version = "8.3.0" +version = "8.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, ] [[package]] @@ -168,74 +240,104 @@ wheels = [ [[package]] name = "coverage" -version = "7.10.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6d/8f/6ac7fbb29e35645065f7be835bfe3e0cce567f80390de2f3db65d83cb5e3/coverage-7.10.0.tar.gz", hash = "sha256:2768885aef484b5dcde56262cbdfba559b770bfc46994fe9485dc3614c7a5867", size = 819816, upload-time = "2025-07-24T16:53:00.896Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/a7/a47f64718c2229b7860a334edd4e6ff41ec8513f3d3f4246284610344392/coverage-7.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d883fee92b9245c0120fa25b5d36de71ccd4cfc29735906a448271e935d8d86d", size = 215143, upload-time = "2025-07-24T16:51:14.105Z" }, - { url = "https://files.pythonhosted.org/packages/ea/86/14d76a409e9ffab10d5aece73ac159dbd102fc56627e203413bfc6d53b24/coverage-7.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c87e59e88268d30e33d3665ede4fbb77b513981a2df0059e7c106ca3de537586", size = 215401, upload-time = "2025-07-24T16:51:15.978Z" }, - { url = "https://files.pythonhosted.org/packages/f4/b3/fb5c28148a19035a3877fac4e40b044a4c97b24658c980bcf7dff18bfab8/coverage-7.10.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f669d969f669a11d6ceee0b733e491d9a50573eb92a71ffab13b15f3aa2665d4", size = 245949, upload-time = "2025-07-24T16:51:17.628Z" }, - { url = "https://files.pythonhosted.org/packages/6d/95/357559ecfe73970d2023845797361e6c2e6c2c05f970073fff186fe19dd7/coverage-7.10.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9582bd6c6771300a847d328c1c4204e751dbc339a9e249eecdc48cada41f72e6", size = 248295, upload-time = "2025-07-24T16:51:19.46Z" }, - { url = "https://files.pythonhosted.org/packages/7e/58/bac5bc43085712af201f76a24733895331c475e5ddda88ac36c1332a65e6/coverage-7.10.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91f97e9637dc7977842776fdb7ad142075d6fa40bc1b91cb73685265e0d31d32", size = 249733, upload-time = "2025-07-24T16:51:21.518Z" }, - { url = "https://files.pythonhosted.org/packages/b2/db/104b713b3b74752ee365346677fb104765923982ae7bd93b95ca41fe256b/coverage-7.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ae4fa92b6601a62367c6c9967ad32ad4e28a89af54b6bb37d740946b0e0534dd", size = 247943, upload-time = "2025-07-24T16:51:23.194Z" }, - { url = "https://files.pythonhosted.org/packages/32/4f/bef25c797c9496cf31ae9cfa93ce96b4414cacf13688e4a6000982772fd5/coverage-7.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3a5cc8b97473e7b3623dd17a42d2194a2b49de8afecf8d7d03c8987237a9552c", size = 245914, upload-time = "2025-07-24T16:51:24.766Z" }, - { url = "https://files.pythonhosted.org/packages/36/6b/b3efa0b506dbb9a37830d6dc862438fe3ad2833c5f889152bce24d9577cf/coverage-7.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc1cbb7f623250e047c32bd7aa1bb62ebc62608d5004d74df095e1059141ac88", size = 247296, upload-time = "2025-07-24T16:51:26.361Z" }, - { url = "https://files.pythonhosted.org/packages/1f/aa/95a845266aeacab4c57b08e0f4e0e2899b07809a18fd0c1ddef2ac2c9138/coverage-7.10.0-cp313-cp313-win32.whl", hash = "sha256:1380cc5666d778e77f1587cd88cc317158111f44d54c0dd3975f0936993284e0", size = 217566, upload-time = "2025-07-24T16:51:28.961Z" }, - { url = "https://files.pythonhosted.org/packages/a0/d1/27b6e5073a8026b9e0f4224f1ac53217ce589a4cdab1bee878f23bff64f0/coverage-7.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:bf03cf176af098ee578b754a03add4690b82bdfe070adfb5d192d0b1cd15cf82", size = 218337, upload-time = "2025-07-24T16:51:31.45Z" }, - { url = "https://files.pythonhosted.org/packages/c7/06/0e3ba498b11e2245fd96bd7e8dcdf90e1dd36d57f49f308aa650ff0561b8/coverage-7.10.0-cp313-cp313-win_arm64.whl", hash = "sha256:8041c78cd145088116db2329b2fb6e89dc338116c962fbe654b7e9f5d72ab957", size = 216740, upload-time = "2025-07-24T16:51:33.317Z" }, - { url = "https://files.pythonhosted.org/packages/44/8b/11529debbe3e6b39ef6e7c8912554724adc6dc10adbb617a855ecfd387eb/coverage-7.10.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:37cc2c06052771f48651160c080a86431884db9cd62ba622cab71049b90a95b3", size = 215866, upload-time = "2025-07-24T16:51:35.339Z" }, - { url = "https://files.pythonhosted.org/packages/9c/6d/d8981310879e395f39af66536665b75135b1bc88dd21c7764e3340e9ce69/coverage-7.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:91f37270b16178b05fa107d85713d29bf21606e37b652d38646eef5f2dfbd458", size = 216083, upload-time = "2025-07-24T16:51:36.932Z" }, - { url = "https://files.pythonhosted.org/packages/c3/84/93295402de002de8b8c953bf6a1f19687174c4db7d44c1e85ffc153a772d/coverage-7.10.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f9b0b0168864d09bcb9a3837548f75121645c4cfd0efce0eb994c221955c5b10", size = 257320, upload-time = "2025-07-24T16:51:38.734Z" }, - { url = "https://files.pythonhosted.org/packages/02/5c/d0540db4869954dac0f69ad709adcd51f3a73ab11fcc9435ee76c518944a/coverage-7.10.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:df0be435d3b616e7d3ee3f9ebbc0d784a213986fe5dff9c6f1042ee7cfd30157", size = 259182, upload-time = "2025-07-24T16:51:40.463Z" }, - { url = "https://files.pythonhosted.org/packages/59/b2/d7d57a41a15ca4b47290862efd6b596d0a185bfd26f15d04db9f238aa56c/coverage-7.10.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35e9aba1c4434b837b1d567a533feba5ce205e8e91179c97974b28a14c23d3a0", size = 261322, upload-time = "2025-07-24T16:51:42.44Z" }, - { url = "https://files.pythonhosted.org/packages/16/92/fd828ae411b3da63673305617b6fbeccc09feb7dfe397d164f55a65cd880/coverage-7.10.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a0b0c481e74dfad631bdc2c883e57d8b058e5c90ba8ef087600995daf7bbec18", size = 258914, upload-time = "2025-07-24T16:51:44.115Z" }, - { url = "https://files.pythonhosted.org/packages/28/49/4aa5f5464b2e1215640c0400c5b007e7f5cdade8bf39c55c33b02f3a8c7f/coverage-7.10.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8aec1b7c8922808a433c13cd44ace6fceac0609f4587773f6c8217a06102674b", size = 257051, upload-time = "2025-07-24T16:51:45.75Z" }, - { url = "https://files.pythonhosted.org/packages/1e/5a/ded2346098c7f48ff6e135b5005b97de4cd9daec5c39adb4ecf3a60967da/coverage-7.10.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:04ec59ceb3a594af0927f2e0d810e1221212abd9a2e6b5b917769ff48760b460", size = 257869, upload-time = "2025-07-24T16:51:47.41Z" }, - { url = "https://files.pythonhosted.org/packages/46/66/e06cedb8fc7d1c96630b2f549b8cdc084e2623dcc70c900cb3b705a36a60/coverage-7.10.0-cp313-cp313t-win32.whl", hash = "sha256:b6871e62d29646eb9b3f5f92def59e7575daea1587db21f99e2b19561187abda", size = 218243, upload-time = "2025-07-24T16:51:49.136Z" }, - { url = "https://files.pythonhosted.org/packages/e7/1e/e84dd5ff35ed066bd6150e5c26fe0061ded2c59c209fd4f18db0650766c0/coverage-7.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff99cff2be44f78920b76803f782e91ffb46ccc7fa89eccccc0da3ca94285b64", size = 219334, upload-time = "2025-07-24T16:51:50.789Z" }, - { url = "https://files.pythonhosted.org/packages/b7/e0/b7b60b5dbc4e88eac0a0e9d5b4762409a59b29bf4e772b3509c8543ccaba/coverage-7.10.0-cp313-cp313t-win_arm64.whl", hash = "sha256:3246b63501348fe47299d12c47a27cfc221cfbffa1c2d857bcc8151323a4ae4f", size = 217196, upload-time = "2025-07-24T16:51:52.599Z" }, - { url = "https://files.pythonhosted.org/packages/09/df/7c34bada8ace39f688b3bd5bc411459a20a3204ccb0984c90169a80a9366/coverage-7.10.0-py3-none-any.whl", hash = "sha256:310a786330bb0463775c21d68e26e79973839b66d29e065c5787122b8dd4489f", size = 206777, upload-time = "2025-07-24T16:52:59.009Z" }, +version = "7.13.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9", size = 219474, upload-time = "2026-02-09T12:57:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac", size = 219844, upload-time = "2026-02-09T12:57:20.66Z" }, + { url = "https://files.pythonhosted.org/packages/97/fd/7e859f8fab324cef6c4ad7cff156ca7c489fef9179d5749b0c8d321281c2/coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea", size = 250832, upload-time = "2026-02-09T12:57:22.007Z" }, + { url = "https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b", size = 253434, upload-time = "2026-02-09T12:57:23.339Z" }, + { url = "https://files.pythonhosted.org/packages/5a/88/6728a7ad17428b18d836540630487231f5470fb82454871149502f5e5aa2/coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525", size = 254676, upload-time = "2026-02-09T12:57:24.774Z" }, + { url = "https://files.pythonhosted.org/packages/7c/bc/21244b1b8cedf0dff0a2b53b208015fe798d5f2a8d5348dbfece04224fff/coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242", size = 256807, upload-time = "2026-02-09T12:57:26.125Z" }, + { url = "https://files.pythonhosted.org/packages/97/a0/ddba7ed3251cff51006737a727d84e05b61517d1784a9988a846ba508877/coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148", size = 251058, upload-time = "2026-02-09T12:57:27.614Z" }, + { url = "https://files.pythonhosted.org/packages/9b/55/e289addf7ff54d3a540526f33751951bf0878f3809b47f6dfb3def69c6f7/coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a", size = 252805, upload-time = "2026-02-09T12:57:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/13/4e/cc276b1fa4a59be56d96f1dabddbdc30f4ba22e3b1cd42504c37b3313255/coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23", size = 250766, upload-time = "2026-02-09T12:57:30.522Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/1093b8f93018f8b41a8cf29636c9292502f05e4a113d4d107d14a3acd044/coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80", size = 254923, upload-time = "2026-02-09T12:57:31.946Z" }, + { url = "https://files.pythonhosted.org/packages/8b/55/ea2796da2d42257f37dbea1aab239ba9263b31bd91d5527cdd6db5efe174/coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea", size = 250591, upload-time = "2026-02-09T12:57:33.842Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/7c4bb72aacf8af5020675aa633e59c1fbe296d22aed191b6a5b711eb2bc7/coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a", size = 252364, upload-time = "2026-02-09T12:57:35.743Z" }, + { url = "https://files.pythonhosted.org/packages/5c/38/a8d2ec0146479c20bbaa7181b5b455a0c41101eed57f10dd19a78ab44c80/coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d", size = 222010, upload-time = "2026-02-09T12:57:37.25Z" }, + { url = "https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd", size = 222818, upload-time = "2026-02-09T12:57:38.734Z" }, + { url = "https://files.pythonhosted.org/packages/04/d1/934918a138c932c90d78301f45f677fb05c39a3112b96fd2c8e60503cdc7/coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af", size = 221438, upload-time = "2026-02-09T12:57:40.223Z" }, + { url = "https://files.pythonhosted.org/packages/52/57/ee93ced533bcb3e6df961c0c6e42da2fc6addae53fb95b94a89b1e33ebd7/coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d", size = 220165, upload-time = "2026-02-09T12:57:41.639Z" }, + { url = "https://files.pythonhosted.org/packages/c5/e0/969fc285a6fbdda49d91af278488d904dcd7651b2693872f0ff94e40e84a/coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12", size = 220516, upload-time = "2026-02-09T12:57:44.215Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b8/9531944e16267e2735a30a9641ff49671f07e8138ecf1ca13db9fd2560c7/coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b", size = 261804, upload-time = "2026-02-09T12:57:45.989Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f3/e63df6d500314a2a60390d1989240d5f27318a7a68fa30ad3806e2a9323e/coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9", size = 263885, upload-time = "2026-02-09T12:57:47.42Z" }, + { url = "https://files.pythonhosted.org/packages/f3/67/7654810de580e14b37670b60a09c599fa348e48312db5b216d730857ffe6/coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092", size = 266308, upload-time = "2026-02-09T12:57:49.345Z" }, + { url = "https://files.pythonhosted.org/packages/37/6f/39d41eca0eab3cc82115953ad41c4e77935286c930e8fad15eaed1389d83/coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9", size = 267452, upload-time = "2026-02-09T12:57:50.811Z" }, + { url = "https://files.pythonhosted.org/packages/50/6d/39c0fbb8fc5cd4d2090811e553c2108cf5112e882f82505ee7495349a6bf/coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26", size = 261057, upload-time = "2026-02-09T12:57:52.447Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a2/60010c669df5fa603bb5a97fb75407e191a846510da70ac657eb696b7fce/coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2", size = 263875, upload-time = "2026-02-09T12:57:53.938Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d9/63b22a6bdbd17f1f96e9ed58604c2a6b0e72a9133e37d663bef185877cf6/coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940", size = 261500, upload-time = "2026-02-09T12:57:56.012Z" }, + { url = "https://files.pythonhosted.org/packages/70/bf/69f86ba1ad85bc3ad240e4c0e57a2e620fbc0e1645a47b5c62f0e941ad7f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c", size = 265212, upload-time = "2026-02-09T12:57:57.5Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f2/5f65a278a8c2148731831574c73e42f57204243d33bedaaf18fa79c5958f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0", size = 260398, upload-time = "2026-02-09T12:57:59.027Z" }, + { url = "https://files.pythonhosted.org/packages/ef/80/6e8280a350ee9fea92f14b8357448a242dcaa243cb2c72ab0ca591f66c8c/coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b", size = 262584, upload-time = "2026-02-09T12:58:01.129Z" }, + { url = "https://files.pythonhosted.org/packages/22/63/01ff182fc95f260b539590fb12c11ad3e21332c15f9799cb5e2386f71d9f/coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9", size = 222688, upload-time = "2026-02-09T12:58:02.736Z" }, + { url = "https://files.pythonhosted.org/packages/a9/43/89de4ef5d3cd53b886afa114065f7e9d3707bdb3e5efae13535b46ae483d/coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd", size = 223746, upload-time = "2026-02-09T12:58:05.362Z" }, + { url = "https://files.pythonhosted.org/packages/35/39/7cf0aa9a10d470a5309b38b289b9bb07ddeac5d61af9b664fe9775a4cb3e/coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997", size = 222003, upload-time = "2026-02-09T12:58:06.952Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, ] [[package]] name = "cryptography" -version = "46.0.3" +version = "46.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" }, - { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" }, - { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" }, - { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" }, - { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" }, - { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" }, - { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" }, - { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" }, - { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" }, - { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" }, - { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" }, - { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" }, - { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" }, - { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" }, - { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" }, - { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" }, - { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" }, - { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" }, - { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" }, - { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" }, - { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" }, - { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" }, - { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" }, - { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" }, - { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" }, - { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" }, - { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" }, - { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad", size = 7176289, upload-time = "2026-02-10T19:17:08.274Z" }, + { url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" }, + { url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" }, + { url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" }, + { url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" }, + { url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" }, + { url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" }, + { url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" }, + { url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" }, + { url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ed/325d2a490c5e94038cdb0117da9397ece1f11201f425c4e9c57fe5b9f08b/cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48", size = 3028230, upload-time = "2026-02-10T19:17:30.518Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5a/ac0f49e48063ab4255d9e3b79f5def51697fce1a95ea1370f03dc9db76f6/cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4", size = 3480909, upload-time = "2026-02-10T19:17:32.083Z" }, + { url = "https://files.pythonhosted.org/packages/e2/fa/a66aa722105ad6a458bebd64086ca2b72cdd361fed31763d20390f6f1389/cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31", size = 7170514, upload-time = "2026-02-10T19:17:56.267Z" }, + { url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" }, + { url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" }, + { url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" }, + { url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" }, + { url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" }, + { url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" }, + { url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" }, + { url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" }, + { url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" }, + { url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/45/2d/9c5f2926cb5300a8eefc3f4f0b3f3df39db7f7ce40c8365444c49363cbda/cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", size = 3010220, upload-time = "2026-02-10T19:18:17.361Z" }, + { url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" }, +] + +[[package]] +name = "cyclonedx-python-lib" +version = "11.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "license-expression" }, + { name = "packageurl-python" }, + { name = "py-serializable" }, + { name = "sortedcontainers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/ed/54ecfa25fc145c58bf4f98090f7b6ffe5188d0759248c57dde44427ea239/cyclonedx_python_lib-11.6.0.tar.gz", hash = "sha256:7fb85a4371fa3a203e5be577ac22b7e9a7157f8b0058b7448731474d6dea7bf0", size = 1408147, upload-time = "2025-12-02T12:28:46.446Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/1b/534ad8a5e0f9470522811a8e5a9bc5d328fb7738ba29faf357467a4ef6d0/cyclonedx_python_lib-11.6.0-py3-none-any.whl", hash = "sha256:94f4aae97db42a452134dafdddcfab9745324198201c4777ed131e64c8380759", size = 511157, upload-time = "2025-12-02T12:28:44.158Z" }, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, ] [[package]] @@ -262,11 +364,11 @@ wheels = [ [[package]] name = "dishka" -version = "1.7.2" +version = "1.9.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/40/d7/1be31f5ef32387059190353f9fa493ff4d07a1c75fa856c7566ca45e0800/dishka-1.7.2.tar.gz", hash = "sha256:47d4cb5162b28c61bf5541860e605ed5eaf5c667122299c7ef657c86fc8d5a49", size = 68132, upload-time = "2025-09-24T21:23:05.135Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/97/18d4a9bd44f6baa975cd8d54ed3a1a86b341a43c9c077e647d351c9d4573/dishka-1.9.1.tar.gz", hash = "sha256:973f19dc65160a97370181106764ae076052af4489e94b0cedb3eb4e47fe13bf", size = 274962, upload-time = "2026-03-08T09:43:47.298Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b9/89381173b4f336e986d72471198614806cd313e0f85c143ccb677c310223/dishka-1.7.2-py3-none-any.whl", hash = "sha256:f6faa6ab321903926b825b3337d77172ee693450279b314434864978d01fbad3", size = 94774, upload-time = "2025-09-24T21:23:03.246Z" }, + { url = "https://files.pythonhosted.org/packages/33/98/c8f80be83fbd92f5f9d4bdb5d619a9c9901fb1523c0b02a448b942e532e6/dishka-1.9.1-py3-none-any.whl", hash = "sha256:5080a46bf40bd403aee396aac81f999f679078655f9a6f2062111d62e94e7b18", size = 114327, upload-time = "2026-03-08T09:43:46.097Z" }, ] [[package]] @@ -280,22 +382,23 @@ wheels = [ [[package]] name = "fastapi" -version = "0.121.0" +version = "0.133.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, { name = "pydantic" }, { name = "starlette" }, { name = "typing-extensions" }, + { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8c/e3/77a2df0946703973b9905fd0cde6172c15e0781984320123b4f5079e7113/fastapi-0.121.0.tar.gz", hash = "sha256:06663356a0b1ee93e875bbf05a31fb22314f5bed455afaaad2b2dad7f26e98fa", size = 342412, upload-time = "2025-11-03T10:25:54.818Z" } +sdist = { url = "https://files.pythonhosted.org/packages/22/6f/0eafed8349eea1fa462238b54a624c8b408cd1ba2795c8e64aa6c34f8ab7/fastapi-0.133.1.tar.gz", hash = "sha256:ed152a45912f102592976fde6cbce7dae1a8a1053da94202e51dd35d184fadd6", size = 378741, upload-time = "2026-02-25T18:18:17.398Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/2c/42277afc1ba1a18f8358561eee40785d27becab8f80a1f945c0a3051c6eb/fastapi-0.121.0-py3-none-any.whl", hash = "sha256:8bdf1b15a55f4e4b0d6201033da9109ea15632cb76cf156e7b8b4019f2172106", size = 109183, upload-time = "2025-11-03T10:25:53.27Z" }, + { url = "https://files.pythonhosted.org/packages/d2/c9/a175a7779f3599dfa4adfc97a6ce0e157237b3d7941538604aadaf97bfb6/fastapi-0.133.1-py3-none-any.whl", hash = "sha256:658f34ba334605b1617a65adf2ea6461901bdb9af3a3080d63ff791ecf7dc2e2", size = 109029, upload-time = "2026-02-25T18:18:18.578Z" }, ] [[package]] name = "fastapi-clean-example" -version = "0.1" +version = "0.2" source = { editable = "." } dependencies = [ { name = "alembic" }, @@ -304,111 +407,105 @@ dependencies = [ { name = "dishka" }, { name = "fastapi" }, { name = "fastapi-error-map" }, - { name = "orjson" }, { name = "psycopg", extra = ["binary"] }, + { name = "pydantic-settings" }, { name = "pyjwt", extra = ["crypto"] }, { name = "sqlalchemy", extra = ["mypy"] }, { name = "uuid-utils" }, { name = "uvicorn" }, - { name = "uvloop" }, ] [package.dev-dependencies] dev = [ + { name = "asgi-lifespan" }, { name = "coverage" }, { name = "deptry" }, + { name = "httpx" }, { name = "import-linter" }, { name = "line-profiler" }, { name = "mypy" }, + { name = "pip-audit" }, { name = "pre-commit" }, { name = "pytest" }, { name = "pytest-asyncio" }, + { name = "pytest-cov" }, { name = "ruff" }, { name = "slotscheck" }, -] -test = [ - { name = "coverage" }, - { name = "line-profiler" }, - { name = "pytest" }, - { name = "pytest-asyncio" }, + { name = "tombi" }, ] [package.metadata] requires-dist = [ - { name = "alembic", specifier = "==1.17.1" }, - { name = "alembic-postgresql-enum", specifier = "==1.8.0" }, + { name = "alembic", specifier = "==1.18.4" }, + { name = "alembic-postgresql-enum", specifier = "==1.10.0" }, { name = "bcrypt", specifier = "==5.0.0" }, - { name = "dishka", specifier = "==1.7.2" }, - { name = "fastapi", specifier = "==0.121.0" }, - { name = "fastapi-error-map", specifier = "==0.9.8" }, - { name = "orjson", specifier = "==3.11.4" }, - { name = "psycopg", extras = ["binary"], specifier = "==3.2.12" }, - { name = "pyjwt", extras = ["crypto"], specifier = "==2.10.1" }, - { name = "sqlalchemy", extras = ["mypy"], specifier = "==2.0.44" }, - { name = "uuid-utils", specifier = "==0.11.1" }, - { name = "uvicorn", specifier = "==0.38.0" }, - { name = "uvloop", specifier = "==0.22.1" }, + { name = "dishka", specifier = "==1.9.1" }, + { name = "fastapi", specifier = "==0.133.1" }, + { name = "fastapi-error-map", specifier = "==0.9.10" }, + { name = "psycopg", extras = ["binary"], specifier = "==3.3.3" }, + { name = "pydantic-settings", specifier = "==2.13.1" }, + { name = "pyjwt", extras = ["crypto"], specifier = "==2.11.0" }, + { name = "sqlalchemy", extras = ["mypy"], specifier = "==2.0.47" }, + { name = "uuid-utils", specifier = "==0.14.1" }, + { name = "uvicorn", specifier = "==0.41.0" }, ] [package.metadata.requires-dev] dev = [ - { name = "coverage", specifier = "==7.10.0" }, + { name = "asgi-lifespan", specifier = "==2.1.0" }, + { name = "coverage", specifier = "==7.13.4" }, { name = "deptry", specifier = "==0.24.0" }, - { name = "import-linter", specifier = "==2.9" }, - { name = "line-profiler", specifier = "==5.0.0" }, - { name = "mypy", specifier = "==1.17.0" }, - { name = "pre-commit", specifier = "==4.2.0" }, - { name = "pytest", specifier = "==8.4.1" }, - { name = "pytest-asyncio", specifier = "==1.1.0" }, - { name = "ruff", specifier = "==0.12.5" }, + { name = "httpx", specifier = "==0.28.1" }, + { name = "import-linter", specifier = "==2.10" }, + { name = "line-profiler", specifier = "==5.0.2" }, + { name = "mypy", specifier = "==1.19.1" }, + { name = "pip-audit", specifier = "==2.10.0" }, + { name = "pre-commit", specifier = "==4.5.1" }, + { name = "pytest", specifier = "==9.0.2" }, + { name = "pytest-asyncio", specifier = "==1.3.0" }, + { name = "pytest-cov", specifier = "==7.0.0" }, + { name = "ruff", specifier = "==0.15.4" }, { name = "slotscheck", specifier = "==0.19.1" }, -] -test = [ - { name = "coverage", specifier = "==7.10.0" }, - { name = "line-profiler", specifier = "==5.0.0" }, - { name = "pytest", specifier = "==8.4.1" }, - { name = "pytest-asyncio", specifier = "==1.1.0" }, + { name = "tombi", specifier = "==0.7.33" }, ] [[package]] name = "fastapi-error-map" -version = "0.9.8" +version = "0.9.10" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "fastapi" }, - { name = "orjson" }, + { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/84/9a/81aefff01594bfced5afdfb6c93de02a0f28fccc562f28c6bd721d7876a8/fastapi_error_map-0.9.8.tar.gz", hash = "sha256:894f6884598e4dd8b6c76cae59dee1522813ac3799ba0231b05465193c752f93", size = 386418, upload-time = "2025-11-02T01:58:06.546Z" } +sdist = { url = "https://files.pythonhosted.org/packages/62/64/a48e0da4a9f07ac4893335f64e2ceccb44baeb3e296095f9f8d572d993b6/fastapi_error_map-0.9.10.tar.gz", hash = "sha256:38a62f48981515e5e861691d70c06675bb021e1c5be347d03518b09699d27ad4", size = 381321, upload-time = "2026-02-24T15:58:07.37Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/07/850dc161f16d79ec86f61e90e1dda2bc95c70c462b2b3fb0e46455b1dc98/fastapi_error_map-0.9.8-py3-none-any.whl", hash = "sha256:1d54a5a40b4a7c8653266f0c3f1f3d6be4729e19fd6ec34c29addc85d3e27b58", size = 20462, upload-time = "2025-11-02T01:58:04.545Z" }, + { url = "https://files.pythonhosted.org/packages/ac/1e/a4406d8f5dcf900be04bd4a0410d37487f5a1c1de6ba9a1c258e28414e32/fastapi_error_map-0.9.10-py3-none-any.whl", hash = "sha256:5bf727bf7de3a4aabdcb5107e29281b812060c6c198a16264e7fee20aa3c4c89", size = 21422, upload-time = "2026-02-24T15:58:06.315Z" }, ] [[package]] name = "filelock" -version = "3.20.0" +version = "3.24.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922, upload-time = "2025-10-08T18:03:50.056Z" } +sdist = { url = "https://files.pythonhosted.org/packages/73/92/a8e2479937ff39185d20dd6a851c1a63e55849e447a55e798cc2e1f49c65/filelock-3.24.3.tar.gz", hash = "sha256:011a5644dc937c22699943ebbfc46e969cdde3e171470a6e40b9533e5a72affa", size = 37935, upload-time = "2026-02-19T00:48:20.543Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" }, + { url = "https://files.pythonhosted.org/packages/9c/0f/5d0c71a1aefeb08efff26272149e07ab922b64f46c63363756224bd6872e/filelock-3.24.3-py3-none-any.whl", hash = "sha256:426e9a4660391f7f8a810d71b0555bce9008b0a1cc342ab1f6947d37639e002d", size = 24331, upload-time = "2026-02-19T00:48:18.465Z" }, ] [[package]] name = "greenlet" -version = "3.2.4" +version = "3.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/51/1664f6b78fc6ebbd98019a1fd730e83fa78f2db7058f72b1463d3612b8db/greenlet-3.3.2.tar.gz", hash = "sha256:2eaf067fc6d886931c7962e8c6bede15d2f01965560f3359b27c80bde2d151f2", size = 188267, upload-time = "2026-02-20T20:54:15.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" }, - { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" }, - { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" }, - { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" }, - { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" }, - { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, - { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, - { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" }, - { url = "https://files.pythonhosted.org/packages/1c/53/f9c440463b3057485b8594d7a638bed53ba531165ef0ca0e6c364b5cc807/greenlet-3.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e343822feb58ac4d0a1211bd9399de2b3a04963ddeec21530fc426cc121f19b", size = 1564759, upload-time = "2025-11-04T12:42:19.395Z" }, - { url = "https://files.pythonhosted.org/packages/47/e4/3bb4240abdd0a8d23f4f88adec746a3099f0d86bfedb623f063b2e3b4df0/greenlet-3.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca7f6f1f2649b89ce02f6f229d7c19f680a6238af656f61e0115b24857917929", size = 1634288, upload-time = "2025-11-04T12:42:21.174Z" }, - { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" }, + { url = "https://files.pythonhosted.org/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4", size = 279120, upload-time = "2026-02-20T20:19:01.9Z" }, + { url = "https://files.pythonhosted.org/packages/49/8d/9771d03e7a8b1ee456511961e1b97a6d77ae1dea4a34a5b98eee706689d3/greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986", size = 603238, upload-time = "2026-02-20T20:47:32.873Z" }, + { url = "https://files.pythonhosted.org/packages/59/0e/4223c2bbb63cd5c97f28ffb2a8aee71bdfb30b323c35d409450f51b91e3e/greenlet-3.3.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92", size = 614219, upload-time = "2026-02-20T20:55:59.817Z" }, + { url = "https://files.pythonhosted.org/packages/94/2b/4d012a69759ac9d77210b8bfb128bc621125f5b20fc398bce3940d036b1c/greenlet-3.3.2-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccd21bb86944ca9be6d967cf7691e658e43417782bce90b5d2faeda0ff78a7dd", size = 628268, upload-time = "2026-02-20T21:02:48.024Z" }, + { url = "https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab", size = 616774, upload-time = "2026-02-20T20:21:02.454Z" }, + { url = "https://files.pythonhosted.org/packages/0a/03/996c2d1689d486a6e199cb0f1cf9e4aa940c500e01bdf201299d7d61fa69/greenlet-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a", size = 1571277, upload-time = "2026-02-20T20:49:34.795Z" }, + { url = "https://files.pythonhosted.org/packages/d9/c4/2570fc07f34a39f2caf0bf9f24b0a1a0a47bc2e8e465b2c2424821389dfc/greenlet-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b", size = 1640455, upload-time = "2026-02-20T20:21:10.261Z" }, + { url = "https://files.pythonhosted.org/packages/91/39/5ef5aa23bc545aa0d31e1b9b55822b32c8da93ba657295840b6b34124009/greenlet-3.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:a7945dd0eab63ded0a48e4dcade82939783c172290a7903ebde9e184333ca124", size = 230961, upload-time = "2026-02-20T20:16:58.461Z" }, + { url = "https://files.pythonhosted.org/packages/62/6b/a89f8456dcb06becff288f563618e9f20deed8dd29beea14f9a168aef64b/greenlet-3.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:394ead29063ee3515b4e775216cb756b2e3b4a7e55ae8fd884f17fa579e6b327", size = 230221, upload-time = "2026-02-20T20:17:37.152Z" }, ] [[package]] @@ -453,13 +550,41 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + [[package]] name = "identify" -version = "2.6.15" +version = "2.6.16" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ff/e7/685de97986c916a6d93b3876139e00eef26ad5bbbd61925d670ae8013449/identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf", size = 99311, upload-time = "2025-10-02T17:43:40.631Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/8d/e8b97e6bd3fb6fb271346f7981362f1e04d6a7463abd0de79e1fda17c067/identify-2.6.16.tar.gz", hash = "sha256:846857203b5511bbe94d5a352a48ef2359532bc8f6727b5544077a0dcfb24980", size = 99360, upload-time = "2026-01-12T18:58:58.201Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757", size = 99183, upload-time = "2025-10-02T17:43:39.137Z" }, + { url = "https://files.pythonhosted.org/packages/b8/58/40fbbcefeda82364720eba5cf2270f98496bdfa19ea75b4cccae79c698e6/identify-2.6.16-py2.py3-none-any.whl", hash = "sha256:391ee4d77741d994189522896270b787aed8670389bfd60f326d677d64a6dfb0", size = 99202, upload-time = "2026-01-12T18:58:56.627Z" }, ] [[package]] @@ -473,17 +598,19 @@ wheels = [ [[package]] name = "import-linter" -version = "2.9" +version = "2.10" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, + { name = "fastapi" }, { name = "grimp" }, { name = "rich" }, { name = "typing-extensions" }, + { name = "uvicorn" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/03/ea/9d3ba8e6851d22a073d21ff143a6b23f844dc97f46b41c0dccd26e26d6d3/import_linter-2.9.tar.gz", hash = "sha256:0d7da2a9bb0a534171a592795bd46c8cca86bd6dc6e6e665fa95ba4ed5024215", size = 288196, upload-time = "2025-12-11T11:55:06.087Z" } +sdist = { url = "https://files.pythonhosted.org/packages/10/c4/a83cc1ea9ed0171725c0e2edc11fd929994d4f026028657e8b30d62bca37/import_linter-2.10.tar.gz", hash = "sha256:c6a5057d2dbd32e1854c4d6b60e90dfad459b7ab5356230486d8521f25872963", size = 1149263, upload-time = "2026-02-06T17:57:24.779Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/3d/657a586f9324ad24538cd797d5c471286e217987e1d0f265575cebe594a9/import_linter-2.9-py3-none-any.whl", hash = "sha256:06403ede04c975cda2ea9050498c16b2021c0261b5cedf47c6c5d8725894b1a2", size = 44899, upload-time = "2025-12-11T11:55:04.87Z" }, + { url = "https://files.pythonhosted.org/packages/1c/e5/4b7b9435eac78ecfd537fa1004a0bcf0f4eac17d3a893f64d38a7bacb51b/import_linter-2.10-py3-none-any.whl", hash = "sha256:cc2ddd7ec0145cbf83f3b25391d2a5dbbf138382aaf80708612497fa6ebc8f60", size = 637081, upload-time = "2026-02-06T17:57:23.386Z" }, ] [[package]] @@ -495,19 +622,57 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] +[[package]] +name = "librt" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/9c/b4b0c54d84da4a94b37bd44151e46d5e583c9534c7e02250b961b1b6d8a8/librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73", size = 177471, upload-time = "2026-02-17T16:13:06.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/3c/f614c8e4eaac7cbf2bbdf9528790b21d89e277ee20d57dc6e559c626105f/librt-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e6bad1cd94f6764e1e21950542f818a09316645337fd5ab9a7acc45d99a8f35", size = 66529, upload-time = "2026-02-17T16:11:57.809Z" }, + { url = "https://files.pythonhosted.org/packages/ab/96/5836544a45100ae411eda07d29e3d99448e5258b6e9c8059deb92945f5c2/librt-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf450f498c30af55551ba4f66b9123b7185362ec8b625a773b3d39aa1a717583", size = 68669, upload-time = "2026-02-17T16:11:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/06/53/f0b992b57af6d5531bf4677d75c44f095f2366a1741fb695ee462ae04b05/librt-0.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eca45e982fa074090057132e30585a7e8674e9e885d402eae85633e9f449ce6c", size = 199279, upload-time = "2026-02-17T16:11:59.862Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ad/4848cc16e268d14280d8168aee4f31cea92bbd2b79ce33d3e166f2b4e4fc/librt-0.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3811485fccfda840861905b8c70bba5ec094e02825598bb9d4ca3936857a04", size = 210288, upload-time = "2026-02-17T16:12:00.954Z" }, + { url = "https://files.pythonhosted.org/packages/52/05/27fdc2e95de26273d83b96742d8d3b7345f2ea2bdbd2405cc504644f2096/librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363", size = 224809, upload-time = "2026-02-17T16:12:02.108Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d0/78200a45ba3240cb042bc597d6f2accba9193a2c57d0356268cbbe2d0925/librt-0.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5212a5bd7fae98dae95710032902edcd2ec4dc994e883294f75c857b83f9aba0", size = 218075, upload-time = "2026-02-17T16:12:03.631Z" }, + { url = "https://files.pythonhosted.org/packages/af/72/a210839fa74c90474897124c064ffca07f8d4b347b6574d309686aae7ca6/librt-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e692aa2d1d604e6ca12d35e51fdc36f4cda6345e28e36374579f7ef3611b3012", size = 225486, upload-time = "2026-02-17T16:12:04.725Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c1/a03cc63722339ddbf087485f253493e2b013039f5b707e8e6016141130fa/librt-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4be2a5c926b9770c9e08e717f05737a269b9d0ebc5d2f0060f0fe3fe9ce47acb", size = 218219, upload-time = "2026-02-17T16:12:05.828Z" }, + { url = "https://files.pythonhosted.org/packages/58/f5/fff6108af0acf941c6f274a946aea0e484bd10cd2dc37610287ce49388c5/librt-0.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd1a720332ea335ceb544cf0a03f81df92abd4bb887679fd1e460976b0e6214b", size = 218750, upload-time = "2026-02-17T16:12:07.09Z" }, + { url = "https://files.pythonhosted.org/packages/71/67/5a387bfef30ec1e4b4f30562c8586566faf87e47d696768c19feb49e3646/librt-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2af9e01e0ef80d95ae3c720be101227edae5f2fe7e3dc63d8857fadfc5a1d", size = 241624, upload-time = "2026-02-17T16:12:08.43Z" }, + { url = "https://files.pythonhosted.org/packages/d4/be/24f8502db11d405232ac1162eb98069ca49c3306c1d75c6ccc61d9af8789/librt-0.8.1-cp313-cp313-win32.whl", hash = "sha256:086a32dbb71336627e78cc1d6ee305a68d038ef7d4c39aaff41ae8c9aa46e91a", size = 54969, upload-time = "2026-02-17T16:12:09.633Z" }, + { url = "https://files.pythonhosted.org/packages/5c/73/c9fdf6cb2a529c1a092ce769a12d88c8cca991194dfe641b6af12fa964d2/librt-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79", size = 62000, upload-time = "2026-02-17T16:12:10.632Z" }, + { url = "https://files.pythonhosted.org/packages/d3/97/68f80ca3ac4924f250cdfa6e20142a803e5e50fca96ef5148c52ee8c10ea/librt-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:924817ab3141aca17893386ee13261f1d100d1ef410d70afe4389f2359fea4f0", size = 52495, upload-time = "2026-02-17T16:12:11.633Z" }, +] + +[[package]] +name = "license-expression" +version = "30.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "boolean-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/71/d89bb0e71b1415453980fd32315f2a037aad9f7f70f695c7cec7035feb13/license_expression-30.4.4.tar.gz", hash = "sha256:73448f0aacd8d0808895bdc4b2c8e01a8d67646e4188f887375398c761f340fd", size = 186402, upload-time = "2025-07-22T11:13:32.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/40/791891d4c0c4dab4c5e187c17261cedc26285fd41541577f900470a45a4d/license_expression-30.4.4-py3-none-any.whl", hash = "sha256:421788fdcadb41f049d2dc934ce666626265aeccefddd25e162a26f23bcbf8a4", size = 120615, upload-time = "2025-07-22T11:13:31.217Z" }, +] + [[package]] name = "line-profiler" -version = "5.0.0" +version = "5.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ea/5c/bbe9042ef5cf4c6cad4bf4d6f7975193430eba9191b7278ea114a3993fbb/line_profiler-5.0.0.tar.gz", hash = "sha256:a80f0afb05ba0d275d9dddc5ff97eab637471167ff3e66dcc7d135755059398c", size = 376919, upload-time = "2025-07-23T20:15:41.819Z" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/03/b6/6d18ad201417a9c5168995541d0fd7981b5652b2b34f6e46a3a93c0f1beb/line_profiler-5.0.2.tar.gz", hash = "sha256:8d8a990c84c64bcde45af22af502d17bc0ae107be405ce41bba92af5c39c0000", size = 407075, upload-time = "2026-02-23T23:31:20.698Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/eb/bc4420cf68661406c98d590656d72eed6f7d76e45accf568802dc83615ef/line_profiler-5.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9873fabbae1587778a551176758a70a5f6c89d8d070a1aca7a689677d41a1348", size = 624828, upload-time = "2025-07-23T20:15:05.315Z" }, - { url = "https://files.pythonhosted.org/packages/f2/6e/6e0a4c1009975d27810027427d601acbad75b45947040d0fd80cec5b3e94/line_profiler-5.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2cd6cdb5a4d3b4ced607104dbed73ec820a69018decd1a90904854380536ed32", size = 487651, upload-time = "2025-07-23T20:15:06.961Z" }, - { url = "https://files.pythonhosted.org/packages/3b/2c/e60e61f24faa0e6eca375bdac9c4b4b37c3267488d7cb1a8c5bd74cf5cdc/line_profiler-5.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:34d6172a3bd14167b3ea2e629d71b08683b17b3bc6eb6a4936d74e3669f875b6", size = 474071, upload-time = "2025-07-23T20:15:08.607Z" }, - { url = "https://files.pythonhosted.org/packages/e1/d5/6f178e74746f84cc17381f607d191c54772207770d585fda773b868bfe28/line_profiler-5.0.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5edd859be322aa8252253e940ac1c60cca4c385760d90a402072f8f35e4b967", size = 1405434, upload-time = "2025-07-23T20:15:09.862Z" }, - { url = "https://files.pythonhosted.org/packages/9b/32/ce67bbf81e5c78cc8d606afe6a192fbef30395021b2aaffe15681e186e3f/line_profiler-5.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d4f97b223105eed6e525994f5653061bd981e04838ee5d14e01d17c26185094", size = 1467553, upload-time = "2025-07-23T20:15:11.195Z" }, - { url = "https://files.pythonhosted.org/packages/c1/c1/431ffb89a351aaa63f8358442e0b9456a3bb745cebdf9c0d7aa4d47affca/line_profiler-5.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4758007e491bee3be40ebcca460596e0e28e7f39b735264694a9cafec729dfa9", size = 2442489, upload-time = "2025-07-23T20:15:12.602Z" }, - { url = "https://files.pythonhosted.org/packages/ce/9d/e34cc99c8abca3a27911d3542a87361e9c292fa1258d182e4a0a5c442850/line_profiler-5.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:213b19c4b65942db5d477e603c18c76126e3811a39d8bab251d930d8ce82ffba", size = 461377, upload-time = "2025-07-23T20:15:13.871Z" }, + { url = "https://files.pythonhosted.org/packages/a7/64/856b920e026fbd239df875ec05e63583f7bd7f250805215ab6e132da11d1/line_profiler-5.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:016effba91d34d15229d41984e921a27f66a7b634f1d7adf6c57c743f3d6a0eb", size = 642642, upload-time = "2026-02-23T23:30:27.63Z" }, + { url = "https://files.pythonhosted.org/packages/3b/08/0a56fab0a36818af6ffc8073700db2f402db5a62477b69d938c19871d631/line_profiler-5.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:506e800dd408a8aafadf39ff4e4a1375ae7794910d00098f191520a2f390cb99", size = 503787, upload-time = "2026-02-23T23:30:29.226Z" }, + { url = "https://files.pythonhosted.org/packages/ed/9a/0ab45cf92b2c13261b475c440e18bb18d9497cc2ad5dfaf38c231c72b02b/line_profiler-5.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e67f77bcb349a663cb22819f65621bcd2a39889524dd890d1d88f8736841b7b", size = 493631, upload-time = "2026-02-23T23:30:30.502Z" }, + { url = "https://files.pythonhosted.org/packages/fb/15/a5b603f0c7c795aa656a95e2a70d139dc499b5d153b6a3129bbba6b6f913/line_profiler-5.0.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6b9d08e85fd48d254ae253e76dc72598e94200ef7002eb1ae0bab4cc9c5e41a", size = 1464022, upload-time = "2026-02-23T23:30:31.793Z" }, + { url = "https://files.pythonhosted.org/packages/27/6f/0f399c72eecaf8f8c00e84238b5786afc34d0a4ef5ad10c63c712715ba86/line_profiler-5.0.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31290e06ac25cd87fee46ebe979541d4ec7c8d6f15c5cbe5874a932b1cee95bb", size = 1483425, upload-time = "2026-02-23T23:30:33.15Z" }, + { url = "https://files.pythonhosted.org/packages/65/18/f4c642a29719a84d17ea8b58cd6e60943573a28228c30c568565ed5512aa/line_profiler-5.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1d7fbcc2dbd8534fc6f7d2b440076749b2235cdc525eb177fefafeaf7550373f", size = 2410276, upload-time = "2026-02-23T23:30:34.943Z" }, + { url = "https://files.pythonhosted.org/packages/90/33/701203686e7d27a545e3bbc8e81fffc7d091c42ed33564be4e72376ef45b/line_profiler-5.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55f04671f48afcd90858c18fbdb2509463c77d717ed5424664f096e902206b6b", size = 2495283, upload-time = "2026-02-23T23:30:36.616Z" }, + { url = "https://files.pythonhosted.org/packages/34/e1/59fe065f67ed1fb8f974a9e3434685af1fc1f6a154489f7ab0992eab1c73/line_profiler-5.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:d2262d4bbbcf72bd430fc5763073792a0f1cb20e64de0f7ecf6e8ae16627d876", size = 479287, upload-time = "2026-02-23T23:30:38.152Z" }, + { url = "https://files.pythonhosted.org/packages/e9/83/89f6ae52fa77960404ee88fc078ee680e504bf1ab8724ac01430cee0f5a5/line_profiler-5.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:abf755b020d91b639cbc563015eca381ca64e6bd27ee55ef9004a3a17b6d4dcf", size = 461960, upload-time = "2026-02-23T23:30:39.657Z" }, ] [[package]] @@ -573,24 +738,42 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] +[[package]] +name = "msgpack" +version = "1.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/f2/bfb55a6236ed8725a96b0aa3acbd0ec17588e6a2c3b62a93eb513ed8783f/msgpack-1.1.2.tar.gz", hash = "sha256:3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e", size = 173581, upload-time = "2025-10-08T09:15:56.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4efd7b5979ccb539c221a4c4e16aac1a533efc97f3b759bb5a5ac9f6d10383bf", size = 81212, upload-time = "2025-10-08T09:15:14.552Z" }, + { url = "https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42eefe2c3e2af97ed470eec850facbe1b5ad1d6eacdbadc42ec98e7dcf68b4b7", size = 84315, upload-time = "2025-10-08T09:15:15.543Z" }, + { url = "https://files.pythonhosted.org/packages/d3/68/93180dce57f684a61a88a45ed13047558ded2be46f03acb8dec6d7c513af/msgpack-1.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fdf7d83102bf09e7ce3357de96c59b627395352a4024f6e2458501f158bf999", size = 412721, upload-time = "2025-10-08T09:15:16.567Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fac4be746328f90caa3cd4bc67e6fe36ca2bf61d5c6eb6d895b6527e3f05071e", size = 424657, upload-time = "2025-10-08T09:15:17.825Z" }, + { url = "https://files.pythonhosted.org/packages/38/f8/4398c46863b093252fe67368b44edc6c13b17f4e6b0e4929dbf0bdb13f23/msgpack-1.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fffee09044073e69f2bad787071aeec727183e7580443dfeb8556cbf1978d162", size = 402668, upload-time = "2025-10-08T09:15:19.003Z" }, + { url = "https://files.pythonhosted.org/packages/28/ce/698c1eff75626e4124b4d78e21cca0b4cc90043afb80a507626ea354ab52/msgpack-1.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5928604de9b032bc17f5099496417f113c45bc6bc21b5c6920caf34b3c428794", size = 419040, upload-time = "2025-10-08T09:15:20.183Z" }, + { url = "https://files.pythonhosted.org/packages/67/32/f3cd1667028424fa7001d82e10ee35386eea1408b93d399b09fb0aa7875f/msgpack-1.1.2-cp313-cp313-win32.whl", hash = "sha256:a7787d353595c7c7e145e2331abf8b7ff1e6673a6b974ded96e6d4ec09f00c8c", size = 65037, upload-time = "2025-10-08T09:15:21.416Z" }, + { url = "https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:a465f0dceb8e13a487e54c07d04ae3ba131c7c5b95e2612596eafde1dccf64a9", size = 72631, upload-time = "2025-10-08T09:15:22.431Z" }, + { url = "https://files.pythonhosted.org/packages/e5/db/0314e4e2db56ebcf450f277904ffd84a7988b9e5da8d0d61ab2d057df2b6/msgpack-1.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:e69b39f8c0aa5ec24b57737ebee40be647035158f14ed4b40e6f150077e21a84", size = 64118, upload-time = "2025-10-08T09:15:23.402Z" }, +] + [[package]] name = "mypy" -version = "1.17.0" +version = "1.19.1" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, { name = "mypy-extensions" }, { name = "pathspec" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1e/e3/034322d5a779685218ed69286c32faa505247f1f096251ef66c8fd203b08/mypy-1.17.0.tar.gz", hash = "sha256:e5d7ccc08ba089c06e2f5629c660388ef1fee708444f1dee0b9203fa031dee03", size = 3352114, upload-time = "2025-07-14T20:34:30.181Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/be/7b/5f8ab461369b9e62157072156935cec9d272196556bdc7c2ff5f4c7c0f9b/mypy-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c41aa59211e49d717d92b3bb1238c06d387c9325d3122085113c79118bebb06", size = 11070019, upload-time = "2025-07-14T20:32:07.99Z" }, - { url = "https://files.pythonhosted.org/packages/9c/f8/c49c9e5a2ac0badcc54beb24e774d2499748302c9568f7f09e8730e953fa/mypy-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e69db1fb65b3114f98c753e3930a00514f5b68794ba80590eb02090d54a5d4a", size = 10114457, upload-time = "2025-07-14T20:33:47.285Z" }, - { url = "https://files.pythonhosted.org/packages/89/0c/fb3f9c939ad9beed3e328008b3fb90b20fda2cddc0f7e4c20dbefefc3b33/mypy-1.17.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:03ba330b76710f83d6ac500053f7727270b6b8553b0423348ffb3af6f2f7b889", size = 11857838, upload-time = "2025-07-14T20:33:14.462Z" }, - { url = "https://files.pythonhosted.org/packages/4c/66/85607ab5137d65e4f54d9797b77d5a038ef34f714929cf8ad30b03f628df/mypy-1.17.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:037bc0f0b124ce46bfde955c647f3e395c6174476a968c0f22c95a8d2f589bba", size = 12731358, upload-time = "2025-07-14T20:32:25.579Z" }, - { url = "https://files.pythonhosted.org/packages/73/d0/341dbbfb35ce53d01f8f2969facbb66486cee9804048bf6c01b048127501/mypy-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c38876106cb6132259683632b287238858bd58de267d80defb6f418e9ee50658", size = 12917480, upload-time = "2025-07-14T20:34:21.868Z" }, - { url = "https://files.pythonhosted.org/packages/64/63/70c8b7dbfc520089ac48d01367a97e8acd734f65bd07813081f508a8c94c/mypy-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:d30ba01c0f151998f367506fab31c2ac4527e6a7b2690107c7a7f9e3cb419a9c", size = 9589666, upload-time = "2025-07-14T20:34:16.841Z" }, - { url = "https://files.pythonhosted.org/packages/e3/fc/ee058cc4316f219078464555873e99d170bde1d9569abd833300dbeb484a/mypy-1.17.0-py3-none-any.whl", hash = "sha256:15d9d0018237ab058e5de3d8fce61b6fa72cc59cc78fd91f1b474bce12abf496", size = 2283195, upload-time = "2025-07-14T20:31:54.753Z" }, + { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" }, + { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" }, + { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" }, + { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" }, + { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, ] [[package]] @@ -604,61 +787,102 @@ wheels = [ [[package]] name = "nodeenv" -version = "1.9.1" +version = "1.10.0" 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, upload-time = "2024-06-04T18:44:11.171Z" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, ] [[package]] -name = "orjson" -version = "3.11.4" +name = "packageurl-python" +version = "0.17.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c6/fe/ed708782d6709cc60eb4c2d8a361a440661f74134675c72990f2c48c785f/orjson-3.11.4.tar.gz", hash = "sha256:39485f4ab4c9b30a3943cfe99e1a213c4776fb69e8abd68f66b83d5a0b0fdc6d", size = 5945188, upload-time = "2025-10-24T15:50:38.027Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/d6/3b5a4e3cfaef7a53869a26ceb034d1ff5e5c27c814ce77260a96d50ab7bb/packageurl_python-0.17.6.tar.gz", hash = "sha256:1252ce3a102372ca6f86eb968e16f9014c4ba511c5c37d95a7f023e2ca6e5c25", size = 50618, upload-time = "2025-11-24T15:20:17.998Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/15/c52aa7112006b0f3d6180386c3a46ae057f932ab3425bc6f6ac50431cca1/orjson-3.11.4-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2d6737d0e616a6e053c8b4acc9eccea6b6cce078533666f32d140e4f85002534", size = 243525, upload-time = "2025-10-24T15:49:29.737Z" }, - { url = "https://files.pythonhosted.org/packages/ec/38/05340734c33b933fd114f161f25a04e651b0c7c33ab95e9416ade5cb44b8/orjson-3.11.4-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:afb14052690aa328cc118a8e09f07c651d301a72e44920b887c519b313d892ff", size = 128871, upload-time = "2025-10-24T15:49:31.109Z" }, - { url = "https://files.pythonhosted.org/packages/55/b9/ae8d34899ff0c012039b5a7cb96a389b2476e917733294e498586b45472d/orjson-3.11.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38aa9e65c591febb1b0aed8da4d469eba239d434c218562df179885c94e1a3ad", size = 130055, upload-time = "2025-10-24T15:49:33.382Z" }, - { url = "https://files.pythonhosted.org/packages/33/aa/6346dd5073730451bee3681d901e3c337e7ec17342fb79659ec9794fc023/orjson-3.11.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f2cf4dfaf9163b0728d061bebc1e08631875c51cd30bf47cb9e3293bfbd7dcd5", size = 129061, upload-time = "2025-10-24T15:49:34.935Z" }, - { url = "https://files.pythonhosted.org/packages/39/e4/8eea51598f66a6c853c380979912d17ec510e8e66b280d968602e680b942/orjson-3.11.4-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89216ff3dfdde0e4070932e126320a1752c9d9a758d6a32ec54b3b9334991a6a", size = 136541, upload-time = "2025-10-24T15:49:36.923Z" }, - { url = "https://files.pythonhosted.org/packages/9a/47/cb8c654fa9adcc60e99580e17c32b9e633290e6239a99efa6b885aba9dbc/orjson-3.11.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9daa26ca8e97fae0ce8aa5d80606ef8f7914e9b129b6b5df9104266f764ce436", size = 137535, upload-time = "2025-10-24T15:49:38.307Z" }, - { url = "https://files.pythonhosted.org/packages/43/92/04b8cc5c2b729f3437ee013ce14a60ab3d3001465d95c184758f19362f23/orjson-3.11.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c8b2769dc31883c44a9cd126560327767f848eb95f99c36c9932f51090bfce9", size = 136703, upload-time = "2025-10-24T15:49:40.795Z" }, - { url = "https://files.pythonhosted.org/packages/aa/fd/d0733fcb9086b8be4ebcfcda2d0312865d17d0d9884378b7cffb29d0763f/orjson-3.11.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1469d254b9884f984026bd9b0fa5bbab477a4bfe558bba6848086f6d43eb5e73", size = 136293, upload-time = "2025-10-24T15:49:42.347Z" }, - { url = "https://files.pythonhosted.org/packages/c2/d7/3c5514e806837c210492d72ae30ccf050ce3f940f45bf085bab272699ef4/orjson-3.11.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:68e44722541983614e37117209a194e8c3ad07838ccb3127d96863c95ec7f1e0", size = 140131, upload-time = "2025-10-24T15:49:43.638Z" }, - { url = "https://files.pythonhosted.org/packages/9c/dd/ba9d32a53207babf65bd510ac4d0faaa818bd0df9a9c6f472fe7c254f2e3/orjson-3.11.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8e7805fda9672c12be2f22ae124dcd7b03928d6c197544fe12174b86553f3196", size = 406164, upload-time = "2025-10-24T15:49:45.498Z" }, - { url = "https://files.pythonhosted.org/packages/8e/f9/f68ad68f4af7c7bde57cd514eaa2c785e500477a8bc8f834838eb696a685/orjson-3.11.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:04b69c14615fb4434ab867bf6f38b2d649f6f300af30a6705397e895f7aec67a", size = 149859, upload-time = "2025-10-24T15:49:46.981Z" }, - { url = "https://files.pythonhosted.org/packages/b6/d2/7f847761d0c26818395b3d6b21fb6bc2305d94612a35b0a30eae65a22728/orjson-3.11.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:639c3735b8ae7f970066930e58cf0ed39a852d417c24acd4a25fc0b3da3c39a6", size = 139926, upload-time = "2025-10-24T15:49:48.321Z" }, - { url = "https://files.pythonhosted.org/packages/9f/37/acd14b12dc62db9a0e1d12386271b8661faae270b22492580d5258808975/orjson-3.11.4-cp313-cp313-win32.whl", hash = "sha256:6c13879c0d2964335491463302a6ca5ad98105fc5db3565499dcb80b1b4bd839", size = 136007, upload-time = "2025-10-24T15:49:49.938Z" }, - { url = "https://files.pythonhosted.org/packages/c0/a9/967be009ddf0a1fffd7a67de9c36656b28c763659ef91352acc02cbe364c/orjson-3.11.4-cp313-cp313-win_amd64.whl", hash = "sha256:09bf242a4af98732db9f9a1ec57ca2604848e16f132e3f72edfd3c5c96de009a", size = 131314, upload-time = "2025-10-24T15:49:51.248Z" }, - { url = "https://files.pythonhosted.org/packages/cb/db/399abd6950fbd94ce125cb8cd1a968def95174792e127b0642781e040ed4/orjson-3.11.4-cp313-cp313-win_arm64.whl", hash = "sha256:a85f0adf63319d6c1ba06fb0dbf997fced64a01179cf17939a6caca662bf92de", size = 126152, upload-time = "2025-10-24T15:49:52.922Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2f/c7277b7615a93f51b5fbc1eacfc1b75e8103370e786fd8ce2abf6e5c04ab/packageurl_python-0.17.6-py3-none-any.whl", hash = "sha256:31a85c2717bc41dd818f3c62908685ff9eebcb68588213745b14a6ee9e7df7c9", size = 36776, upload-time = "2025-11-24T15:20:16.962Z" }, ] [[package]] name = "packaging" -version = "25.0" +version = "26.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] [[package]] name = "pathspec" -version = "0.12.1" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, +] + +[[package]] +name = "pip" +version = "26.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +sdist = { url = "https://files.pythonhosted.org/packages/48/83/0d7d4e9efe3344b8e2fe25d93be44f64b65364d3c8d7bc6dc90198d5422e/pip-26.0.1.tar.gz", hash = "sha256:c4037d8a277c89b320abe636d59f91e6d0922d08a05b60e85e53b296613346d8", size = 1812747, upload-time = "2026-02-05T02:20:18.702Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, + { url = "https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl", hash = "sha256:bdb1b08f4274833d62c1aa29e20907365a2ceb950410df15fc9521bad440122b", size = 1787723, upload-time = "2026-02-05T02:20:16.416Z" }, +] + +[[package]] +name = "pip-api" +version = "0.0.34" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pip" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/f1/ee85f8c7e82bccf90a3c7aad22863cc6e20057860a1361083cd2adacb92e/pip_api-0.0.34.tar.gz", hash = "sha256:9b75e958f14c5a2614bae415f2adf7eeb54d50a2cfbe7e24fd4826471bac3625", size = 123017, upload-time = "2024-07-09T20:32:30.641Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/f7/ebf5003e1065fd00b4cbef53bf0a65c3d3e1b599b676d5383ccb7a8b88ba/pip_api-0.0.34-py3-none-any.whl", hash = "sha256:8b2d7d7c37f2447373aa2cf8b1f60a2f2b27a84e1e9e0294a3f6ef10eb3ba6bb", size = 120369, upload-time = "2024-07-09T20:32:29.099Z" }, +] + +[[package]] +name = "pip-audit" +version = "2.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachecontrol", extra = ["filecache"] }, + { name = "cyclonedx-python-lib" }, + { name = "packaging" }, + { name = "pip-api" }, + { name = "pip-requirements-parser" }, + { name = "platformdirs" }, + { name = "requests" }, + { name = "rich" }, + { name = "tomli" }, + { name = "tomli-w" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/89/0e999b413facab81c33d118f3ac3739fd02c0622ccf7c4e82e37cebd8447/pip_audit-2.10.0.tar.gz", hash = "sha256:427ea5bf61d1d06b98b1ae29b7feacc00288a2eced52c9c58ceed5253ef6c2a4", size = 53776, upload-time = "2025-12-01T23:42:40.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/f3/4888f895c02afa085630a3a3329d1b18b998874642ad4c530e9a4d7851fe/pip_audit-2.10.0-py3-none-any.whl", hash = "sha256:16e02093872fac97580303f0848fa3ad64f7ecf600736ea7835a2b24de49613f", size = 61518, upload-time = "2025-12-01T23:42:39.193Z" }, +] + +[[package]] +name = "pip-requirements-parser" +version = "32.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "pyparsing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/2a/63b574101850e7f7b306ddbdb02cb294380d37948140eecd468fae392b54/pip-requirements-parser-32.0.1.tar.gz", hash = "sha256:b4fa3a7a0be38243123cf9d1f3518da10c51bdb165a2b2985566247f9155a7d3", size = 209359, upload-time = "2022-12-21T15:25:22.732Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/d0/d04f1d1e064ac901439699ee097f58688caadea42498ec9c4b4ad2ef84ab/pip_requirements_parser-32.0.1-py3-none-any.whl", hash = "sha256:4659bc2a667783e7a15d190f6fccf8b2486685b6dba4c19c3876314769c57526", size = 35648, upload-time = "2022-12-21T15:25:21.046Z" }, ] [[package]] name = "platformdirs" -version = "4.5.0" +version = "4.9.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, + { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" }, ] [[package]] @@ -672,7 +896,7 @@ wheels = [ [[package]] name = "pre-commit" -version = "4.2.0" +version = "4.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cfgv" }, @@ -681,21 +905,21 @@ dependencies = [ { name = "pyyaml" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/08/39/679ca9b26c7bb2999ff122d50faa301e49af82ca9c066ec061cfbc0c6784/pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146", size = 193424, upload-time = "2025-03-18T21:35:20.987Z" } +sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" }, + { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, ] [[package]] name = "psycopg" -version = "3.2.12" +version = "3.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/77/c72d10262b872617e509a0c60445afcc4ce2cd5cd6bc1c97700246d69c85/psycopg-3.2.12.tar.gz", hash = "sha256:85c08d6f6e2a897b16280e0ff6406bef29b1327c045db06d21f364d7cd5da90b", size = 160642, upload-time = "2025-10-26T00:46:03.045Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/b6/379d0a960f8f435ec78720462fd94c4863e7a31237cf81bf76d0af5883bf/psycopg-3.3.3.tar.gz", hash = "sha256:5e9a47458b3c1583326513b2556a2a9473a1001a56c9efe9e587245b43148dd9", size = 165624, upload-time = "2026-02-18T16:52:16.546Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/28/8c4f90e415411dc9c78d6ba10b549baa324659907c13f64bfe3779d4066c/psycopg-3.2.12-py3-none-any.whl", hash = "sha256:8a1611a2d4c16ae37eada46438be9029a35bb959bb50b3d0e1e93c0f3d54c9ee", size = 206765, upload-time = "2025-10-26T00:10:42.173Z" }, + { url = "https://files.pythonhosted.org/packages/c8/5b/181e2e3becb7672b502f0ed7f16ed7352aca7c109cfb94cf3878a9186db9/psycopg-3.3.3-py3-none-any.whl", hash = "sha256:f96525a72bcfade6584ab17e89de415ff360748c766f0106959144dcbb38c698", size = 212768, upload-time = "2026-02-18T16:46:27.365Z" }, ] [package.optional-dependencies] @@ -705,32 +929,46 @@ binary = [ [[package]] name = "psycopg-binary" -version = "3.2.12" +version = "3.3.3" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/0a/cac9fdf1df16a269ba0e5f0f06cac61f826c94cadb39df028cdfe19d3a33/psycopg_binary-3.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05f32239aec25c5fb15f7948cffdc2dc0dac098e48b80a140e4ba32b572a2e7d", size = 4590414, upload-time = "2026-02-18T16:50:01.441Z" }, + { url = "https://files.pythonhosted.org/packages/9c/c0/d8f8508fbf440edbc0099b1abff33003cd80c9e66eb3a1e78834e3fb4fb9/psycopg_binary-3.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c84f9d214f2d1de2fafebc17fa68ac3f6561a59e291553dfc45ad299f4898c1", size = 4669021, upload-time = "2026-02-18T16:50:08.803Z" }, + { url = "https://files.pythonhosted.org/packages/04/05/097016b77e343b4568feddf12c72171fc513acef9a4214d21b9478569068/psycopg_binary-3.3.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e77957d2ba17cada11be09a5066d93026cdb61ada7c8893101d7fe1c6e1f3925", size = 5467453, upload-time = "2026-02-18T16:50:14.985Z" }, + { url = "https://files.pythonhosted.org/packages/91/23/73244e5feb55b5ca109cede6e97f32ef45189f0fdac4c80d75c99862729d/psycopg_binary-3.3.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:42961609ac07c232a427da7c87a468d3c82fee6762c220f38e37cfdacb2b178d", size = 5151135, upload-time = "2026-02-18T16:50:24.82Z" }, + { url = "https://files.pythonhosted.org/packages/11/49/5309473b9803b207682095201d8708bbc7842ddf3f192488a69204e36455/psycopg_binary-3.3.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae07a3114313dd91fce686cab2f4c44af094398519af0e0f854bc707e1aeedf1", size = 6737315, upload-time = "2026-02-18T16:50:35.106Z" }, + { url = "https://files.pythonhosted.org/packages/d4/5d/03abe74ef34d460b33c4d9662bf6ec1dd38888324323c1a1752133c10377/psycopg_binary-3.3.3-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d257c58d7b36a621dcce1d01476ad8b60f12d80eb1406aee4cf796f88b2ae482", size = 4979783, upload-time = "2026-02-18T16:50:42.067Z" }, + { url = "https://files.pythonhosted.org/packages/f0/6c/3fbf8e604e15f2f3752900434046c00c90bb8764305a1b81112bff30ba24/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:07c7211f9327d522c9c47560cae00a4ecf6687f4e02d779d035dd3177b41cb12", size = 4509023, upload-time = "2026-02-18T16:50:50.116Z" }, + { url = "https://files.pythonhosted.org/packages/9c/6b/1a06b43b7c7af756c80b67eac8bfaa51d77e68635a8a8d246e4f0bb7604a/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:8e7e9eca9b363dbedeceeadd8be97149d2499081f3c52d141d7cd1f395a91f83", size = 4185874, upload-time = "2026-02-18T16:50:55.97Z" }, + { url = "https://files.pythonhosted.org/packages/2b/d3/bf49e3dcaadba510170c8d111e5e69e5ae3f981c1554c5bb71c75ce354bb/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:cb85b1d5702877c16f28d7b92ba030c1f49ebcc9b87d03d8c10bf45a2f1c7508", size = 3925668, upload-time = "2026-02-18T16:51:03.299Z" }, + { url = "https://files.pythonhosted.org/packages/f8/92/0aac830ed6a944fe334404e1687a074e4215630725753f0e3e9a9a595b62/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4d4606c84d04b80f9138d72f1e28c6c02dc5ae0c7b8f3f8aaf89c681ce1cd1b1", size = 4234973, upload-time = "2026-02-18T16:51:09.097Z" }, + { url = "https://files.pythonhosted.org/packages/2e/96/102244653ee5a143ece5afe33f00f52fe64e389dfce8dbc87580c6d70d3d/psycopg_binary-3.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:74eae563166ebf74e8d950ff359be037b85723d99ca83f57d9b244a871d6c13b", size = 3551342, upload-time = "2026-02-18T16:51:13.892Z" }, +] + +[[package]] +name = "py-serializable" +version = "2.1.0" source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "defusedxml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/21/d250cfca8ff30c2e5a7447bc13861541126ce9bd4426cd5d0c9f08b5547d/py_serializable-2.1.0.tar.gz", hash = "sha256:9d5db56154a867a9b897c0163b33a793c804c80cee984116d02d49e4578fc103", size = 52368, upload-time = "2025-07-21T09:56:48.07Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/0b/9d480aba4a4864832c29e6fc94ddd34d9927c276448eb3b56ffe24ed064c/psycopg_binary-3.2.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:442f20153415f374ae5753ca618637611a41a3c58c56d16ce55f845d76a3cf7b", size = 4017829, upload-time = "2025-10-26T00:26:27.031Z" }, - { url = "https://files.pythonhosted.org/packages/a4/f3/0d294b30349bde24a46741a1f27a10e8ab81e9f4118d27c2fe592acfb42a/psycopg_binary-3.2.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:79de3cc5adbf51677009a8fda35ac9e9e3686d5595ab4b0c43ec7099ece6aeb5", size = 4089835, upload-time = "2025-10-26T00:27:01.392Z" }, - { url = "https://files.pythonhosted.org/packages/82/d4/ff82e318e5a55d6951b278d3af7b4c7c1b19344e3a3722b6613f156a38ea/psycopg_binary-3.2.12-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:095ccda59042a1239ac2fefe693a336cb5cecf8944a8d9e98b07f07e94e2b78d", size = 4625474, upload-time = "2025-10-26T00:27:40.34Z" }, - { url = "https://files.pythonhosted.org/packages/b1/e8/2c9df6475a5ab6d614d516f4497c568d84f7d6c21d0e11444468c9786c9f/psycopg_binary-3.2.12-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:efab679a2c7d1bf7d0ec0e1ecb47fe764945eff75bb4321f2e699b30a12db9b3", size = 4720350, upload-time = "2025-10-26T00:28:20.104Z" }, - { url = "https://files.pythonhosted.org/packages/74/f5/7aec81b0c41985dc006e2d5822486ad4b7c2a1a97a5a05e37dc2adaf1512/psycopg_binary-3.2.12-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d369e79ad9647fc8217cbb51bbbf11f9a1ffca450be31d005340157ffe8e91b3", size = 4411621, upload-time = "2025-10-26T00:28:59.104Z" }, - { url = "https://files.pythonhosted.org/packages/fc/15/d3cb41b8fa9d5f14320ab250545fbb66f9ddb481e448e618902672a806c0/psycopg_binary-3.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eedc410f82007038030650aa58f620f9fe0009b9d6b04c3dc71cbd3bae5b2675", size = 3863081, upload-time = "2025-10-26T00:29:31.235Z" }, - { url = "https://files.pythonhosted.org/packages/69/8a/72837664e63e3cd3aa145cedcf29e5c21257579739aba78ab7eb668f7d9c/psycopg_binary-3.2.12-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f3bae4be7f6781bf6c9576eedcd5e1bb74468126fa6de991e47cdb1a8ea3a42a", size = 3537428, upload-time = "2025-10-26T00:30:01.465Z" }, - { url = "https://files.pythonhosted.org/packages/cc/7e/1b78ae38e7d69e6d7fb1e2dcce101493f5fa429480bac3a68b876c9b1635/psycopg_binary-3.2.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8ffe75fe6be902dadd439adf4228c98138a992088e073ede6dd34e7235f4e03e", size = 3585981, upload-time = "2025-10-26T00:30:31.635Z" }, - { url = "https://files.pythonhosted.org/packages/a3/f8/245b4868b2dac46c3fb6383b425754ae55df1910c826d305ed414da03777/psycopg_binary-3.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:2598d0e4f2f258da13df0560187b3f1dfc9b8688c46b9d90176360ae5212c3fc", size = 2912929, upload-time = "2025-10-26T00:30:56.413Z" }, + { url = "https://files.pythonhosted.org/packages/9b/bf/7595e817906a29453ba4d99394e781b6fabe55d21f3c15d240f85dd06bb1/py_serializable-2.1.0-py3-none-any.whl", hash = "sha256:b56d5d686b5a03ba4f4db5e769dc32336e142fc3bd4d68a8c25579ebb0a67304", size = 23045, upload-time = "2025-07-21T09:56:46.848Z" }, ] [[package]] name = "pycparser" -version = "2.23" +version = "3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] [[package]] name = "pydantic" -version = "2.12.3" +version = "2.12.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -738,39 +976,48 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/1e/4f0a3233767010308f2fd6bd0814597e3f63f1dc98304a9112b8759df4ff/pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74", size = 819383, upload-time = "2025-10-17T15:04:21.222Z" } +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/6b/83661fa77dcefa195ad5f8cd9af3d1a7450fd57cc883ad04d65446ac2029/pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf", size = 462431, upload-time = "2025-10-17T15:04:19.346Z" }, + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, ] [[package]] name = "pydantic-core" -version = "2.41.4" +version = "2.41.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/18/d0944e8eaaa3efd0a91b0f1fc537d3be55ad35091b6a87638211ba691964/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5", size = 457557, upload-time = "2025-10-14T10:23:47.909Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/13/d0/c20adabd181a029a970738dfe23710b52a31f1258f591874fcdec7359845/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746", size = 2105688, upload-time = "2025-10-14T10:20:54.448Z" }, - { url = "https://files.pythonhosted.org/packages/00/b6/0ce5c03cec5ae94cca220dfecddc453c077d71363b98a4bbdb3c0b22c783/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced", size = 1910807, upload-time = "2025-10-14T10:20:56.115Z" }, - { url = "https://files.pythonhosted.org/packages/68/3e/800d3d02c8beb0b5c069c870cbb83799d085debf43499c897bb4b4aaff0d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a", size = 1956669, upload-time = "2025-10-14T10:20:57.874Z" }, - { url = "https://files.pythonhosted.org/packages/60/a4/24271cc71a17f64589be49ab8bd0751f6a0a03046c690df60989f2f95c2c/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02", size = 2051629, upload-time = "2025-10-14T10:21:00.006Z" }, - { url = "https://files.pythonhosted.org/packages/68/de/45af3ca2f175d91b96bfb62e1f2d2f1f9f3b14a734afe0bfeff079f78181/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1", size = 2224049, upload-time = "2025-10-14T10:21:01.801Z" }, - { url = "https://files.pythonhosted.org/packages/af/8f/ae4e1ff84672bf869d0a77af24fd78387850e9497753c432875066b5d622/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2", size = 2342409, upload-time = "2025-10-14T10:21:03.556Z" }, - { url = "https://files.pythonhosted.org/packages/18/62/273dd70b0026a085c7b74b000394e1ef95719ea579c76ea2f0cc8893736d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84", size = 2069635, upload-time = "2025-10-14T10:21:05.385Z" }, - { url = "https://files.pythonhosted.org/packages/30/03/cf485fff699b4cdaea469bc481719d3e49f023241b4abb656f8d422189fc/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d", size = 2194284, upload-time = "2025-10-14T10:21:07.122Z" }, - { url = "https://files.pythonhosted.org/packages/f9/7e/c8e713db32405dfd97211f2fc0a15d6bf8adb7640f3d18544c1f39526619/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d", size = 2137566, upload-time = "2025-10-14T10:21:08.981Z" }, - { url = "https://files.pythonhosted.org/packages/04/f7/db71fd4cdccc8b75990f79ccafbbd66757e19f6d5ee724a6252414483fb4/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2", size = 2316809, upload-time = "2025-10-14T10:21:10.805Z" }, - { url = "https://files.pythonhosted.org/packages/76/63/a54973ddb945f1bca56742b48b144d85c9fc22f819ddeb9f861c249d5464/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab", size = 2311119, upload-time = "2025-10-14T10:21:12.583Z" }, - { url = "https://files.pythonhosted.org/packages/f8/03/5d12891e93c19218af74843a27e32b94922195ded2386f7b55382f904d2f/pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c", size = 1981398, upload-time = "2025-10-14T10:21:14.584Z" }, - { url = "https://files.pythonhosted.org/packages/be/d8/fd0de71f39db91135b7a26996160de71c073d8635edfce8b3c3681be0d6d/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4", size = 2030735, upload-time = "2025-10-14T10:21:16.432Z" }, - { url = "https://files.pythonhosted.org/packages/72/86/c99921c1cf6650023c08bfab6fe2d7057a5142628ef7ccfa9921f2dda1d5/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564", size = 1973209, upload-time = "2025-10-14T10:21:18.213Z" }, - { url = "https://files.pythonhosted.org/packages/36/0d/b5706cacb70a8414396efdda3d72ae0542e050b591119e458e2490baf035/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4", size = 1877324, upload-time = "2025-10-14T10:21:20.363Z" }, - { url = "https://files.pythonhosted.org/packages/de/2d/cba1fa02cfdea72dfb3a9babb067c83b9dff0bbcb198368e000a6b756ea7/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2", size = 1884515, upload-time = "2025-10-14T10:21:22.339Z" }, - { url = "https://files.pythonhosted.org/packages/07/ea/3df927c4384ed9b503c9cc2d076cf983b4f2adb0c754578dfb1245c51e46/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf", size = 2042819, upload-time = "2025-10-14T10:21:26.683Z" }, - { url = "https://files.pythonhosted.org/packages/6a/ee/df8e871f07074250270a3b1b82aad4cd0026b588acd5d7d3eb2fcb1471a3/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2", size = 1995866, upload-time = "2025-10-14T10:21:28.951Z" }, - { url = "https://files.pythonhosted.org/packages/fc/de/b20f4ab954d6d399499c33ec4fafc46d9551e11dc1858fb7f5dca0748ceb/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89", size = 1970034, upload-time = "2025-10-14T10:21:30.869Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/6d/fffca34caecc4a3f97bda81b2098da5e8ab7efc9a66e819074a11955d87e/pydantic_settings-2.13.1.tar.gz", hash = "sha256:b4c11847b15237fb0171e1462bf540e294affb9b86db4d9aa5c01730bdbe4025", size = 223826, upload-time = "2026-02-19T13:45:08.055Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/4b/ccc026168948fec4f7555b9164c724cf4125eac006e176541483d2c959be/pydantic_settings-2.13.1-py3-none-any.whl", hash = "sha256:d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237", size = 58929, upload-time = "2026-02-19T13:45:06.034Z" }, ] [[package]] @@ -784,11 +1031,11 @@ wheels = [ [[package]] name = "pyjwt" -version = "2.10.1" +version = "2.11.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019, upload-time = "2026-01-30T19:59:55.694Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, + { url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" }, ] [package.optional-dependencies] @@ -796,9 +1043,18 @@ crypto = [ { name = "cryptography" }, ] +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + [[package]] name = "pytest" -version = "8.4.1" +version = "9.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -807,21 +1063,57 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] [[package]] name = "pytest-asyncio" -version = "1.1.0" +version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/51/f8794af39eeb870e87a8c8068642fc07bce0c854d6865d7dd0f2a9d338c2/pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea", size = 46652, upload-time = "2025-07-16T04:29:26.393Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, +] + +[[package]] +name = "python-discovery" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/bb/93a3e83bdf9322c7e21cafd092e56a4a17c4d8ef4277b6eb01af1a540a6f/python_discovery-1.1.0.tar.gz", hash = "sha256:447941ba1aed8cc2ab7ee3cb91be5fc137c5bdbb05b7e6ea62fbdcb66e50b268", size = 55674, upload-time = "2026-02-26T09:42:49.668Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/54/82a6e2ef37f0f23dccac604b9585bdcbd0698604feb64807dcb72853693e/python_discovery-1.1.0-py3-none-any.whl", hash = "sha256:a162893b8809727f54594a99ad2179d2ede4bf953e12d4c7abc3cc9cdbd1437b", size = 30687, upload-time = "2026-02-26T09:42:48.548Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/9d/bf86eddabf8c6c9cb1ea9a869d6873b46f105a5d292d3a6f7071f5b07935/pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf", size = 15157, upload-time = "2025-07-16T04:29:24.929Z" }, + { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, ] [[package]] @@ -842,6 +1134,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, ] +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + [[package]] name = "requirements-parser" version = "0.13.0" @@ -856,40 +1163,40 @@ wheels = [ [[package]] name = "rich" -version = "14.2.0" +version = "14.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" }, + { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" }, ] [[package]] name = "ruff" -version = "0.12.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/cd/01015eb5034605fd98d829c5839ec2c6b4582b479707f7c1c2af861e8258/ruff-0.12.5.tar.gz", hash = "sha256:b209db6102b66f13625940b7f8c7d0f18e20039bb7f6101fbdac935c9612057e", size = 5170722, upload-time = "2025-07-24T13:26:37.456Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/de/ad2f68f0798ff15dd8c0bcc2889558970d9a685b3249565a937cd820ad34/ruff-0.12.5-py3-none-linux_armv6l.whl", hash = "sha256:1de2c887e9dec6cb31fcb9948299de5b2db38144e66403b9660c9548a67abd92", size = 11819133, upload-time = "2025-07-24T13:25:56.369Z" }, - { url = "https://files.pythonhosted.org/packages/f8/fc/c6b65cd0e7fbe60f17e7ad619dca796aa49fbca34bb9bea5f8faf1ec2643/ruff-0.12.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d1ab65e7d8152f519e7dea4de892317c9da7a108da1c56b6a3c1d5e7cf4c5e9a", size = 12501114, upload-time = "2025-07-24T13:25:59.471Z" }, - { url = "https://files.pythonhosted.org/packages/c5/de/c6bec1dce5ead9f9e6a946ea15e8d698c35f19edc508289d70a577921b30/ruff-0.12.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:962775ed5b27c7aa3fdc0d8f4d4433deae7659ef99ea20f783d666e77338b8cf", size = 11716873, upload-time = "2025-07-24T13:26:01.496Z" }, - { url = "https://files.pythonhosted.org/packages/a1/16/cf372d2ebe91e4eb5b82a2275c3acfa879e0566a7ac94d331ea37b765ac8/ruff-0.12.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b4cae449597e7195a49eb1cdca89fd9fbb16140c7579899e87f4c85bf82f73", size = 11958829, upload-time = "2025-07-24T13:26:03.721Z" }, - { url = "https://files.pythonhosted.org/packages/25/bf/cd07e8f6a3a6ec746c62556b4c4b79eeb9b0328b362bb8431b7b8afd3856/ruff-0.12.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b13489c3dc50de5e2d40110c0cce371e00186b880842e245186ca862bf9a1ac", size = 11626619, upload-time = "2025-07-24T13:26:06.118Z" }, - { url = "https://files.pythonhosted.org/packages/d8/c9/c2ccb3b8cbb5661ffda6925f81a13edbb786e623876141b04919d1128370/ruff-0.12.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1504fea81461cf4841778b3ef0a078757602a3b3ea4b008feb1308cb3f23e08", size = 13221894, upload-time = "2025-07-24T13:26:08.292Z" }, - { url = "https://files.pythonhosted.org/packages/6b/58/68a5be2c8e5590ecdad922b2bcd5583af19ba648f7648f95c51c3c1eca81/ruff-0.12.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c7da4129016ae26c32dfcbd5b671fe652b5ab7fc40095d80dcff78175e7eddd4", size = 14163909, upload-time = "2025-07-24T13:26:10.474Z" }, - { url = "https://files.pythonhosted.org/packages/bd/d1/ef6b19622009ba8386fdb792c0743f709cf917b0b2f1400589cbe4739a33/ruff-0.12.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ca972c80f7ebcfd8af75a0f18b17c42d9f1ef203d163669150453f50ca98ab7b", size = 13583652, upload-time = "2025-07-24T13:26:13.381Z" }, - { url = "https://files.pythonhosted.org/packages/62/e3/1c98c566fe6809a0c83751d825a03727f242cdbe0d142c9e292725585521/ruff-0.12.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8dbbf9f25dfb501f4237ae7501d6364b76a01341c6f1b2cd6764fe449124bb2a", size = 12700451, upload-time = "2025-07-24T13:26:15.488Z" }, - { url = "https://files.pythonhosted.org/packages/24/ff/96058f6506aac0fbc0d0fc0d60b0d0bd746240a0594657a2d94ad28033ba/ruff-0.12.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c47dea6ae39421851685141ba9734767f960113d51e83fd7bb9958d5be8763a", size = 12937465, upload-time = "2025-07-24T13:26:17.808Z" }, - { url = "https://files.pythonhosted.org/packages/eb/d3/68bc5e7ab96c94b3589d1789f2dd6dd4b27b263310019529ac9be1e8f31b/ruff-0.12.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5076aa0e61e30f848846f0265c873c249d4b558105b221be1828f9f79903dc5", size = 11771136, upload-time = "2025-07-24T13:26:20.422Z" }, - { url = "https://files.pythonhosted.org/packages/52/75/7356af30a14584981cabfefcf6106dea98cec9a7af4acb5daaf4b114845f/ruff-0.12.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a5a4c7830dadd3d8c39b1cc85386e2c1e62344f20766be6f173c22fb5f72f293", size = 11601644, upload-time = "2025-07-24T13:26:22.928Z" }, - { url = "https://files.pythonhosted.org/packages/c2/67/91c71d27205871737cae11025ee2b098f512104e26ffd8656fd93d0ada0a/ruff-0.12.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:46699f73c2b5b137b9dc0fc1a190b43e35b008b398c6066ea1350cce6326adcb", size = 12478068, upload-time = "2025-07-24T13:26:26.134Z" }, - { url = "https://files.pythonhosted.org/packages/34/04/b6b00383cf2f48e8e78e14eb258942fdf2a9bf0287fbf5cdd398b749193a/ruff-0.12.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5a655a0a0d396f0f072faafc18ebd59adde8ca85fb848dc1b0d9f024b9c4d3bb", size = 12991537, upload-time = "2025-07-24T13:26:28.533Z" }, - { url = "https://files.pythonhosted.org/packages/3e/b9/053d6445dc7544fb6594785056d8ece61daae7214859ada4a152ad56b6e0/ruff-0.12.5-py3-none-win32.whl", hash = "sha256:dfeb2627c459b0b78ca2bbdc38dd11cc9a0a88bf91db982058b26ce41714ffa9", size = 11751575, upload-time = "2025-07-24T13:26:30.835Z" }, - { url = "https://files.pythonhosted.org/packages/bc/0f/ab16e8259493137598b9149734fec2e06fdeda9837e6f634f5c4e35916da/ruff-0.12.5-py3-none-win_amd64.whl", hash = "sha256:ae0d90cf5f49466c954991b9d8b953bd093c32c27608e409ae3564c63c5306a5", size = 12882273, upload-time = "2025-07-24T13:26:32.929Z" }, - { url = "https://files.pythonhosted.org/packages/00/db/c376b0661c24cf770cb8815268190668ec1330eba8374a126ceef8c72d55/ruff-0.12.5-py3-none-win_arm64.whl", hash = "sha256:48cdbfc633de2c5c37d9f090ba3b352d1576b0015bfc3bc98eaf230275b7e805", size = 11951564, upload-time = "2025-07-24T13:26:34.994Z" }, +version = "0.15.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/31/d6e536cdebb6568ae75a7f00e4b4819ae0ad2640c3604c305a0428680b0c/ruff-0.15.4.tar.gz", hash = "sha256:3412195319e42d634470cc97aa9803d07e9d5c9223b99bcb1518f0c725f26ae1", size = 4569550, upload-time = "2026-02-26T20:04:14.959Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/82/c11a03cfec3a4d26a0ea1e571f0f44be5993b923f905eeddfc397c13d360/ruff-0.15.4-py3-none-linux_armv6l.whl", hash = "sha256:a1810931c41606c686bae8b5b9a8072adac2f611bb433c0ba476acba17a332e0", size = 10453333, upload-time = "2026-02-26T20:04:20.093Z" }, + { url = "https://files.pythonhosted.org/packages/ce/5d/6a1f271f6e31dffb31855996493641edc3eef8077b883eaf007a2f1c2976/ruff-0.15.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5a1632c66672b8b4d3e1d1782859e98d6e0b4e70829530666644286600a33992", size = 10853356, upload-time = "2026-02-26T20:04:05.808Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d8/0fab9f8842b83b1a9c2bf81b85063f65e93fb512e60effa95b0be49bfc54/ruff-0.15.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a4386ba2cd6c0f4ff75252845906acc7c7c8e1ac567b7bc3d373686ac8c222ba", size = 10187434, upload-time = "2026-02-26T20:03:54.656Z" }, + { url = "https://files.pythonhosted.org/packages/85/cc/cc220fd9394eff5db8d94dec199eec56dd6c9f3651d8869d024867a91030/ruff-0.15.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2496488bdfd3732747558b6f95ae427ff066d1fcd054daf75f5a50674411e75", size = 10535456, upload-time = "2026-02-26T20:03:52.738Z" }, + { url = "https://files.pythonhosted.org/packages/fa/0f/bced38fa5cf24373ec767713c8e4cadc90247f3863605fb030e597878661/ruff-0.15.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f1c4893841ff2d54cbda1b2860fa3260173df5ddd7b95d370186f8a5e66a4ac", size = 10287772, upload-time = "2026-02-26T20:04:08.138Z" }, + { url = "https://files.pythonhosted.org/packages/2b/90/58a1802d84fed15f8f281925b21ab3cecd813bde52a8ca033a4de8ab0e7a/ruff-0.15.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:820b8766bd65503b6c30aaa6331e8ef3a6e564f7999c844e9a547c40179e440a", size = 11049051, upload-time = "2026-02-26T20:04:03.53Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ac/b7ad36703c35f3866584564dc15f12f91cb1a26a897dc2fd13d7cb3ae1af/ruff-0.15.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9fb74bab47139c1751f900f857fa503987253c3ef89129b24ed375e72873e85", size = 11890494, upload-time = "2026-02-26T20:04:10.497Z" }, + { url = "https://files.pythonhosted.org/packages/93/3d/3eb2f47a39a8b0da99faf9c54d3eb24720add1e886a5309d4d1be73a6380/ruff-0.15.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f80c98765949c518142b3a50a5db89343aa90f2c2bf7799de9986498ae6176db", size = 11326221, upload-time = "2026-02-26T20:04:12.84Z" }, + { url = "https://files.pythonhosted.org/packages/ff/90/bf134f4c1e5243e62690e09d63c55df948a74084c8ac3e48a88468314da6/ruff-0.15.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:451a2e224151729b3b6c9ffb36aed9091b2996fe4bdbd11f47e27d8f2e8888ec", size = 11168459, upload-time = "2026-02-26T20:04:00.969Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e5/a64d27688789b06b5d55162aafc32059bb8c989c61a5139a36e1368285eb/ruff-0.15.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a8f157f2e583c513c4f5f896163a93198297371f34c04220daf40d133fdd4f7f", size = 11104366, upload-time = "2026-02-26T20:03:48.099Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f6/32d1dcb66a2559763fc3027bdd65836cad9eb09d90f2ed6a63d8e9252b02/ruff-0.15.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:917cc68503357021f541e69b35361c99387cdbbf99bd0ea4aa6f28ca99ff5338", size = 10510887, upload-time = "2026-02-26T20:03:45.771Z" }, + { url = "https://files.pythonhosted.org/packages/ff/92/22d1ced50971c5b6433aed166fcef8c9343f567a94cf2b9d9089f6aa80fe/ruff-0.15.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e9737c8161da79fd7cfec19f1e35620375bd8b2a50c3e77fa3d2c16f574105cc", size = 10285939, upload-time = "2026-02-26T20:04:22.42Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f4/7c20aec3143837641a02509a4668fb146a642fd1211846634edc17eb5563/ruff-0.15.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:291258c917539e18f6ba40482fe31d6f5ac023994ee11d7bdafd716f2aab8a68", size = 10765471, upload-time = "2026-02-26T20:03:58.924Z" }, + { url = "https://files.pythonhosted.org/packages/d0/09/6d2f7586f09a16120aebdff8f64d962d7c4348313c77ebb29c566cefc357/ruff-0.15.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3f83c45911da6f2cd5936c436cf86b9f09f09165f033a99dcf7477e34041cbc3", size = 11263382, upload-time = "2026-02-26T20:04:24.424Z" }, + { url = "https://files.pythonhosted.org/packages/1b/fa/2ef715a1cd329ef47c1a050e10dee91a9054b7ce2fcfdd6a06d139afb7ec/ruff-0.15.4-py3-none-win32.whl", hash = "sha256:65594a2d557d4ee9f02834fcdf0a28daa8b3b9f6cb2cb93846025a36db47ef22", size = 10506664, upload-time = "2026-02-26T20:03:50.56Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a8/c688ef7e29983976820d18710f955751d9f4d4eb69df658af3d006e2ba3e/ruff-0.15.4-py3-none-win_amd64.whl", hash = "sha256:04196ad44f0df220c2ece5b0e959c2f37c777375ec744397d21d15b50a75264f", size = 11651048, upload-time = "2026-02-26T20:04:17.191Z" }, + { url = "https://files.pythonhosted.org/packages/3e/0a/9e1be9035b37448ce2e68c978f0591da94389ade5a5abafa4cf99985d1b2/ruff-0.15.4-py3-none-win_arm64.whl", hash = "sha256:60d5177e8cfc70e51b9c5fad936c634872a74209f934c1e79107d11787ad5453", size = 10966776, upload-time = "2026-02-26T20:03:56.908Z" }, ] [[package]] @@ -913,25 +1220,39 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, +] + [[package]] name = "sqlalchemy" -version = "2.0.44" +version = "2.0.47" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f0/f2/840d7b9496825333f532d2e3976b8eadbf52034178aac53630d09fe6e1ef/sqlalchemy-2.0.44.tar.gz", hash = "sha256:0ae7454e1ab1d780aee69fd2aae7d6b8670a581d8847f2d1e0f7ddfbf47e5a22", size = 9819830, upload-time = "2025-10-10T14:39:12.935Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/4b/1e00561093fe2cd8eef09d406da003c8a118ff02d6548498c1ae677d68d9/sqlalchemy-2.0.47.tar.gz", hash = "sha256:e3e7feb57b267fe897e492b9721ae46d5c7de6f9e8dee58aacf105dc4e154f3d", size = 9886323, upload-time = "2026-02-24T16:34:27.947Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/d3/c67077a2249fdb455246e6853166360054c331db4613cda3e31ab1cadbef/sqlalchemy-2.0.44-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ff486e183d151e51b1d694c7aa1695747599bb00b9f5f604092b54b74c64a8e1", size = 2135479, upload-time = "2025-10-10T16:03:37.671Z" }, - { url = "https://files.pythonhosted.org/packages/2b/91/eabd0688330d6fd114f5f12c4f89b0d02929f525e6bf7ff80aa17ca802af/sqlalchemy-2.0.44-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b1af8392eb27b372ddb783b317dea0f650241cea5bd29199b22235299ca2e45", size = 2123212, upload-time = "2025-10-10T16:03:41.755Z" }, - { url = "https://files.pythonhosted.org/packages/b0/bb/43e246cfe0e81c018076a16036d9b548c4cc649de241fa27d8d9ca6f85ab/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b61188657e3a2b9ac4e8f04d6cf8e51046e28175f79464c67f2fd35bceb0976", size = 3255353, upload-time = "2025-10-10T15:35:31.221Z" }, - { url = "https://files.pythonhosted.org/packages/b9/96/c6105ed9a880abe346b64d3b6ddef269ddfcab04f7f3d90a0bf3c5a88e82/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b87e7b91a5d5973dda5f00cd61ef72ad75a1db73a386b62877d4875a8840959c", size = 3260222, upload-time = "2025-10-10T15:43:50.124Z" }, - { url = "https://files.pythonhosted.org/packages/44/16/1857e35a47155b5ad927272fee81ae49d398959cb749edca6eaa399b582f/sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:15f3326f7f0b2bfe406ee562e17f43f36e16167af99c4c0df61db668de20002d", size = 3189614, upload-time = "2025-10-10T15:35:32.578Z" }, - { url = "https://files.pythonhosted.org/packages/88/ee/4afb39a8ee4fc786e2d716c20ab87b5b1fb33d4ac4129a1aaa574ae8a585/sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e77faf6ff919aa8cd63f1c4e561cac1d9a454a191bb864d5dd5e545935e5a40", size = 3226248, upload-time = "2025-10-10T15:43:51.862Z" }, - { url = "https://files.pythonhosted.org/packages/32/d5/0e66097fc64fa266f29a7963296b40a80d6a997b7ac13806183700676f86/sqlalchemy-2.0.44-cp313-cp313-win32.whl", hash = "sha256:ee51625c2d51f8baadf2829fae817ad0b66b140573939dd69284d2ba3553ae73", size = 2101275, upload-time = "2025-10-10T15:03:26.096Z" }, - { url = "https://files.pythonhosted.org/packages/03/51/665617fe4f8c6450f42a6d8d69243f9420f5677395572c2fe9d21b493b7b/sqlalchemy-2.0.44-cp313-cp313-win_amd64.whl", hash = "sha256:c1c80faaee1a6c3428cecf40d16a2365bcf56c424c92c2b6f0f9ad204b899e9e", size = 2127901, upload-time = "2025-10-10T15:03:27.548Z" }, - { url = "https://files.pythonhosted.org/packages/9c/5e/6a29fa884d9fb7ddadf6b69490a9d45fded3b38541713010dad16b77d015/sqlalchemy-2.0.44-py3-none-any.whl", hash = "sha256:19de7ca1246fbef9f9d1bff8f1ab25641569df226364a0e40457dc5457c54b05", size = 1928718, upload-time = "2025-10-10T15:29:45.32Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e5/0af64ce7d8f60ec5328c10084e2f449e7912a9b8bdbefdcfb44454a25f49/sqlalchemy-2.0.47-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:456a135b790da5d3c6b53d0ef71ac7b7d280b7f41eb0c438986352bf03ca7143", size = 2152551, upload-time = "2026-02-24T17:05:47.675Z" }, + { url = "https://files.pythonhosted.org/packages/63/79/746b8d15f6940e2ac469ce22d7aa5b1124b1ab820bad9b046eb3000c88a6/sqlalchemy-2.0.47-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09a2f7698e44b3135433387da5d8846cf7cc7c10e5425af7c05fee609df978b6", size = 3278782, upload-time = "2026-02-24T17:18:10.012Z" }, + { url = "https://files.pythonhosted.org/packages/91/b1/bd793ddb34345d1ed43b13ab2d88c95d7d4eb2e28f5b5a99128b9cc2bca2/sqlalchemy-2.0.47-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0bbc72e6a177c78d724f9106aaddc0d26a2ada89c6332b5935414eccf04cbd5", size = 3295155, upload-time = "2026-02-24T17:27:22.827Z" }, + { url = "https://files.pythonhosted.org/packages/97/84/7213def33f94e5ca6f5718d259bc9f29de0363134648425aa218d4356b23/sqlalchemy-2.0.47-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:75460456b043b78b6006e41bdf5b86747ee42eafaf7fffa3b24a6e9a456a2092", size = 3226834, upload-time = "2026-02-24T17:18:11.465Z" }, + { url = "https://files.pythonhosted.org/packages/ef/06/456810204f4dc29b5f025b1b0a03b4bd6b600ebf3c1040aebd90a257fa33/sqlalchemy-2.0.47-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d9adaa616c3bc7d80f9ded57cd84b51d6617cad6a5456621d858c9f23aaee01", size = 3265001, upload-time = "2026-02-24T17:27:24.813Z" }, + { url = "https://files.pythonhosted.org/packages/fb/20/df3920a4b2217dbd7390a5bd277c1902e0393f42baaf49f49b3c935e7328/sqlalchemy-2.0.47-cp313-cp313-win32.whl", hash = "sha256:76e09f974382a496a5ed985db9343628b1cb1ac911f27342e4cc46a8bac10476", size = 2113647, upload-time = "2026-02-24T17:22:55.747Z" }, + { url = "https://files.pythonhosted.org/packages/46/06/7873ddf69918efbfabd7211829f4bd8019739d0a719253112d305d3ba51d/sqlalchemy-2.0.47-cp313-cp313-win_amd64.whl", hash = "sha256:0664089b0bf6724a0bfb49a0cf4d4da24868a0a5c8e937cd7db356d5dcdf2c66", size = 2139425, upload-time = "2026-02-24T17:22:57.033Z" }, + { url = "https://files.pythonhosted.org/packages/54/fa/61ad9731370c90ac7ea5bf8f5eaa12c48bb4beec41c0fa0360becf4ac10d/sqlalchemy-2.0.47-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed0c967c701ae13da98eb220f9ddab3044ab63504c1ba24ad6a59b26826ad003", size = 3558809, upload-time = "2026-02-24T17:12:15.232Z" }, + { url = "https://files.pythonhosted.org/packages/33/d5/221fac96f0529391fe374875633804c866f2b21a9c6d3a6ca57d9c12cfd7/sqlalchemy-2.0.47-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3537943a61fd25b241e976426a0c6814434b93cf9b09d39e8e78f3c9eb9a487", size = 3525480, upload-time = "2026-02-24T17:27:59.602Z" }, + { url = "https://files.pythonhosted.org/packages/ec/55/8247d53998c3673e4a8d1958eba75c6f5cc3b39082029d400bb1f2a911ae/sqlalchemy-2.0.47-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:57f7e336a64a0dba686c66392d46b9bc7af2c57d55ce6dc1697b4ef32b043ceb", size = 3466569, upload-time = "2026-02-24T17:12:16.94Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b5/c1f0eea1bac6790845f71420a7fe2f2a0566203aa57543117d4af3b77d1c/sqlalchemy-2.0.47-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dff735a621858680217cb5142b779bad40ef7322ddbb7c12062190db6879772e", size = 3475770, upload-time = "2026-02-24T17:28:02.034Z" }, + { url = "https://files.pythonhosted.org/packages/c5/ed/2f43f92474ea0c43c204657dc47d9d002cd738b96ca2af8e6d29a9b5e42d/sqlalchemy-2.0.47-cp313-cp313t-win32.whl", hash = "sha256:3893dc096bb3cca9608ea3487372ffcea3ae9b162f40e4d3c51dd49db1d1b2dc", size = 2141300, upload-time = "2026-02-24T17:14:37.024Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a9/8b73f9f1695b6e92f7aaf1711135a1e3bbeb78bca9eded35cb79180d3c6d/sqlalchemy-2.0.47-cp313-cp313t-win_amd64.whl", hash = "sha256:b5103427466f4b3e61f04833ae01f9a914b1280a2a8bcde3a9d7ab11f3755b42", size = 2173053, upload-time = "2026-02-24T17:14:38.688Z" }, + { url = "https://files.pythonhosted.org/packages/15/9f/7c378406b592fcf1fc157248607b495a40e3202ba4a6f1372a2ba6447717/sqlalchemy-2.0.47-py3-none-any.whl", hash = "sha256:e2647043599297a1ef10e720cf310846b7f31b6c841fee093d2b09d81215eb93", size = 1940159, upload-time = "2026-02-24T17:15:07.158Z" }, ] [package.optional-dependencies] @@ -941,14 +1262,63 @@ mypy = [ [[package]] name = "starlette" -version = "0.49.3" +version = "0.52.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/1a/608df0b10b53b0beb96a37854ee05864d182ddd4b1156a22f1ad3860425a/starlette-0.49.3.tar.gz", hash = "sha256:1c14546f299b5901a1ea0e34410575bc33bbd741377a10484a54445588d00284", size = 2655031, upload-time = "2025-11-01T15:12:26.13Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/e0/021c772d6a662f43b63044ab481dc6ac7592447605b5b35a957785363122/starlette-0.49.3-py3-none-any.whl", hash = "sha256:b579b99715fdc2980cf88c8ec96d3bf1ce16f5a8051a7c2b84ef9b1cdecaea2f", size = 74340, upload-time = "2025-11-01T15:12:24.387Z" }, + { url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" }, +] + +[[package]] +name = "tombi" +version = "0.7.33" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/3e/fedb827e9fefb30b8032b1fa0da7ce22f74d3cded06dfd017388b30a5a51/tombi-0.7.33.tar.gz", hash = "sha256:d9b416b83495d5b367a26c6a3264f581c3b4b3e0355e659d349849f65949d927", size = 496800, upload-time = "2026-02-26T12:33:42.942Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/51/41d77af7562d5278927292682bd6b812509033579c23406e28d2343da459/tombi-0.7.33-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fd0c058110ed4588056dbc024f923b979728572c6d652ed2b2ba1be0b6d77621", size = 8859117, upload-time = "2026-02-26T12:33:33.898Z" }, + { url = "https://files.pythonhosted.org/packages/dc/05/c5f69a3c2cf95dcece1642d7d3f7d052fb40be8d7c7bb7baa031fa2ffe67/tombi-0.7.33-py3-none-macosx_11_0_arm64.whl", hash = "sha256:47334bccab9d94f5bf2de9c5d2d52b93d08945e84207266dde8e85cb4cae9e98", size = 8579752, upload-time = "2026-02-26T12:33:32.238Z" }, + { url = "https://files.pythonhosted.org/packages/16/d4/bff163b6c70d66ff91a4ebb9f25241f59d54ea7c4f651ca711a0f2a3124c/tombi-0.7.33-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4617da85a37430e170ce6d57670566c802caf41a466269e1ce54a8be889a1653", size = 8776853, upload-time = "2026-02-26T12:33:23.414Z" }, + { url = "https://files.pythonhosted.org/packages/0a/a3/17b2355e531c654ff97e608261cf6e264465665827b98625f6840f5123bd/tombi-0.7.33-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c4969427c30f6cb30a64fc8672b3d77d2db9f67aecc7ca437dbc28c7e673e3c", size = 9906639, upload-time = "2026-02-26T12:33:28.739Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d9/1320257c57f927d6881e35d897df99c4674e8a673848685a34438731e572/tombi-0.7.33-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50995e36d0d02ac12afe6f554789e718a55e307bf07901eabb53fe05090032c7", size = 10089362, upload-time = "2026-02-26T12:33:25.292Z" }, + { url = "https://files.pythonhosted.org/packages/4f/2b/b9844ccba3b598fb153d1c85db5a18652a2b69ba6fd20f4b214fc733a93f/tombi-0.7.33-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07a6a914529c4f980631cfa0477c747c59179f8f411831412388316bf151db15", size = 8926789, upload-time = "2026-02-26T12:33:26.968Z" }, + { url = "https://files.pythonhosted.org/packages/1c/4a/f3986efed8c00c08250cfa3c47fc168c761612913af06ac134f8d029ad93/tombi-0.7.33-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c434113529cc9c6349b03b5e4212b5306e37854a815f29e5e85a99d530f4242", size = 9241245, upload-time = "2026-02-26T12:33:30.379Z" }, + { url = "https://files.pythonhosted.org/packages/3f/f3/0d7280d2456c63c114e1fc9c22ad052bfbf44a992d138a98e1401102ea53/tombi-0.7.33-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:2d7d8d6652a1f16eedd5fdd9f7add5fb420ba02091ebb74a7697a1ed40e84eab", size = 9051631, upload-time = "2026-02-26T12:33:21.226Z" }, + { url = "https://files.pythonhosted.org/packages/1a/3c/53c3048e0b5ec8d5f8804f6249901d392a66f79ebb7a680bfc55a45b47dc/tombi-0.7.33-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:61403577982914f2feeaec9f77d7bf33fb046bedc13e48d8940fe034cd5a46ec", size = 9087680, upload-time = "2026-02-26T12:33:35.569Z" }, + { url = "https://files.pythonhosted.org/packages/9c/11/40f5aee65d4f2e2ecf6dc75fb41664b979f39b171502f7df169e0613f257/tombi-0.7.33-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:34312399beb47e9134a4ad7e3d2283a46d6c8f9f49201f00dbc94352f2e825db", size = 8824062, upload-time = "2026-02-26T12:33:37.468Z" }, + { url = "https://files.pythonhosted.org/packages/28/8e/075c883390cbf91cbc737cbf69ae0b462837b3ab7d1905a302cd8db68e16/tombi-0.7.33-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4e502a39551f2522204e02994fb54f7a6a7c2f9c83bbad99800177122301b8b4", size = 9407628, upload-time = "2026-02-26T12:33:39.247Z" }, + { url = "https://files.pythonhosted.org/packages/a2/19/1e08efb31699ca38993aab0c3db70528c13623044c0ffdd0ad6c66c5dc2b/tombi-0.7.33-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f0e796de9b5efc0ded76161646db06398b0013e090a1438dab0c7054fc931d31", size = 9456244, upload-time = "2026-02-26T12:33:41.343Z" }, + { url = "https://files.pythonhosted.org/packages/6a/79/cdadd57eae5c6b251f8bacc238e5ca7cc145825aa0ce043691b6d8b7fa84/tombi-0.7.33-py3-none-win32.whl", hash = "sha256:a51206edd521f3d055cf7e3cd01aa3fd43fdb4e56e4c8d9495043c8314cad6c1", size = 7004210, upload-time = "2026-02-26T12:33:45.215Z" }, + { url = "https://files.pythonhosted.org/packages/52/ab/57a86ece7001b5ef18376478cc77b2a3526933f6d6a3588b342ffd13459e/tombi-0.7.33-py3-none-win_amd64.whl", hash = "sha256:53e207e525913d7faf78d5883c05efc0d7d5e3578dcd0d80e8523d10b5cddc7a", size = 8110936, upload-time = "2026-02-26T12:33:43.847Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" }, + { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" }, + { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" }, + { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" }, + { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" }, + { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" }, + { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, +] + +[[package]] +name = "tomli-w" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/75/241269d1da26b624c0d5e110e8149093c759b7a286138f4efd61a60e75fe/tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021", size = 7184, upload-time = "2025-01-15T12:07:24.262Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" }, ] [[package]] @@ -974,70 +1344,68 @@ wheels = [ [[package]] name = "tzdata" -version = "2025.2" +version = "2025.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] [[package]] name = "uuid-utils" -version = "0.11.1" +version = "0.14.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e2/ef/b6c1fd4fee3b2854bf9d602530ab8b6624882e2691c15a9c4d22ea8c03eb/uuid_utils-0.11.1.tar.gz", hash = "sha256:7ef455547c2ccb712840b106b5ab006383a9bfe4125ba1c5ab92e47bcbf79b46", size = 19933, upload-time = "2025-10-02T13:32:09.526Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/d1/38a573f0c631c062cf42fa1f5d021d4dd3c31fb23e4376e4b56b0c9fbbed/uuid_utils-0.14.1.tar.gz", hash = "sha256:9bfc95f64af80ccf129c604fb6b8ca66c6f256451e32bc4570f760e4309c9b69", size = 22195, upload-time = "2026-02-20T22:50:38.833Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/f5/254d7ce4b3aa4a1a3a4f279e0cc74eec8b4d3a61641d8ffc6e983907f2ca/uuid_utils-0.11.1-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:4bc8cf73c375b9ea11baf70caacc2c4bf7ce9bfd804623aa0541e5656f3dbeaf", size = 581019, upload-time = "2025-10-02T13:31:32.239Z" }, - { url = "https://files.pythonhosted.org/packages/68/e6/f7d14c4e1988d8beb3ac9bd773f370376c704925bdfb07380f5476bb2986/uuid_utils-0.11.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:0d2cb3bcc6f5862d08a0ee868b18233bc63ba9ea0e85ea9f3f8e703983558eba", size = 294377, upload-time = "2025-10-02T13:31:34.01Z" }, - { url = "https://files.pythonhosted.org/packages/8e/40/847a9a0258e7a2a14b015afdaa06ee4754a2680db7b74bac159d594eeb18/uuid_utils-0.11.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:463400604f623969f198aba9133ebfd717636f5e34257340302b1c3ff685dc0f", size = 328070, upload-time = "2025-10-02T13:31:35.619Z" }, - { url = "https://files.pythonhosted.org/packages/44/0c/c5d342d31860c9b4f481ef31a4056825961f9b462d216555e76dcee580ea/uuid_utils-0.11.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aef66b935342b268c6ffc1796267a1d9e73135740a10fe7e4098e1891cbcc476", size = 333610, upload-time = "2025-10-02T13:31:37.058Z" }, - { url = "https://files.pythonhosted.org/packages/e1/4b/52edc023ffcb9ab9a4042a58974a79c39ba7a565e683f1fd9814b504cf13/uuid_utils-0.11.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd65c41b81b762278997de0d027161f27f9cc4058fa57bbc0a1aaa63a63d6d1a", size = 475669, upload-time = "2025-10-02T13:31:38.38Z" }, - { url = "https://files.pythonhosted.org/packages/59/81/ee55ee63264531bb1c97b5b6033ad6ec81b5cd77f89174e9aef3af3d8889/uuid_utils-0.11.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccfac9d5d7522d61accabb8c68448ead6407933415e67e62123ed6ed11f86510", size = 331946, upload-time = "2025-10-02T13:31:39.66Z" }, - { url = "https://files.pythonhosted.org/packages/cf/07/5d4be27af0e9648afa512f0d11bb6d96cb841dd6d29b57baa3fbf55fd62e/uuid_utils-0.11.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:003f48f05c01692d0c1f7e413d194e7299a1a364e0047a4eb904d3478b84eca1", size = 352920, upload-time = "2025-10-02T13:31:40.94Z" }, - { url = "https://files.pythonhosted.org/packages/5b/48/a69dddd9727512b0583b87bfff97d82a8813b28fb534a183c9e37033cfef/uuid_utils-0.11.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a5c936042120bdc30d62f539165beaa4a6ba7e817a89e5409a6f06dc62c677a9", size = 509413, upload-time = "2025-10-02T13:31:42.547Z" }, - { url = "https://files.pythonhosted.org/packages/66/0d/1b529a3870c2354dd838d5f133a1cba75220242b0061f04a904ca245a131/uuid_utils-0.11.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:2e16dcdbdf4cd34ffb31ead6236960adb50e6c962c9f4554a6ecfdfa044c6259", size = 529454, upload-time = "2025-10-02T13:31:44.338Z" }, - { url = "https://files.pythonhosted.org/packages/bd/f2/04a3f77c85585aac09d546edaf871a4012052fb8ace6dbddd153b4d50f02/uuid_utils-0.11.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f8b21fed11b23134502153d652c77c3a37fa841a9aa15a4e6186d440a22f1a0e", size = 498084, upload-time = "2025-10-02T13:31:45.601Z" }, - { url = "https://files.pythonhosted.org/packages/89/08/538b380b4c4b220f3222c970930fe459cc37f1dfc6c8dc912568d027f17d/uuid_utils-0.11.1-cp39-abi3-win32.whl", hash = "sha256:72abab5ab27c1b914e3f3f40f910532ae242df1b5f0ae43f1df2ef2f610b2a8c", size = 174314, upload-time = "2025-10-02T13:31:47.269Z" }, - { url = "https://files.pythonhosted.org/packages/00/66/971ec830094ac1c7d46381678f7138c1805015399805e7dd7769c893c9c8/uuid_utils-0.11.1-cp39-abi3-win_amd64.whl", hash = "sha256:5ed9962f8993ef2fd418205f92830c29344102f86871d99b57cef053abf227d9", size = 179214, upload-time = "2025-10-02T13:31:48.344Z" }, + { url = "https://files.pythonhosted.org/packages/43/b7/add4363039a34506a58457d96d4aa2126061df3a143eb4d042aedd6a2e76/uuid_utils-0.14.1-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:93a3b5dc798a54a1feb693f2d1cb4cf08258c32ff05ae4929b5f0a2ca624a4f0", size = 604679, upload-time = "2026-02-20T22:50:27.469Z" }, + { url = "https://files.pythonhosted.org/packages/dd/84/d1d0bef50d9e66d31b2019997c741b42274d53dde2e001b7a83e9511c339/uuid_utils-0.14.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:ccd65a4b8e83af23eae5e56d88034b2fe7264f465d3e830845f10d1591b81741", size = 309346, upload-time = "2026-02-20T22:50:31.857Z" }, + { url = "https://files.pythonhosted.org/packages/ef/ed/b6d6fd52a6636d7c3eddf97d68da50910bf17cd5ac221992506fb56cf12e/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b56b0cacd81583834820588378e432b0696186683b813058b707aedc1e16c4b1", size = 344714, upload-time = "2026-02-20T22:50:42.642Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a7/a19a1719fb626fe0b31882db36056d44fe904dc0cf15b06fdf56b2679cf7/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb3cf14de789097320a3c56bfdfdd51b1225d11d67298afbedee7e84e3837c96", size = 350914, upload-time = "2026-02-20T22:50:36.487Z" }, + { url = "https://files.pythonhosted.org/packages/1d/fc/f6690e667fdc3bb1a73f57951f97497771c56fe23e3d302d7404be394d4f/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e0854a90d67f4b0cc6e54773deb8be618f4c9bad98d3326f081423b5d14fae", size = 482609, upload-time = "2026-02-20T22:50:37.511Z" }, + { url = "https://files.pythonhosted.org/packages/54/6e/dcd3fa031320921a12ec7b4672dea3bd1dd90ddffa363a91831ba834d559/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce6743ba194de3910b5feb1a62590cd2587e33a73ab6af8a01b642ceb5055862", size = 345699, upload-time = "2026-02-20T22:50:46.87Z" }, + { url = "https://files.pythonhosted.org/packages/04/28/e5220204b58b44ac0047226a9d016a113fde039280cc8732d9e6da43b39f/uuid_utils-0.14.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:043fb58fde6cf1620a6c066382f04f87a8e74feb0f95a585e4ed46f5d44af57b", size = 372205, upload-time = "2026-02-20T22:50:28.438Z" }, + { url = "https://files.pythonhosted.org/packages/c7/d9/3d2eb98af94b8dfffc82b6a33b4dfc87b0a5de2c68a28f6dde0db1f8681b/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c915d53f22945e55fe0d3d3b0b87fd965a57f5fd15666fd92d6593a73b1dd297", size = 521836, upload-time = "2026-02-20T22:50:23.057Z" }, + { url = "https://files.pythonhosted.org/packages/a8/15/0eb106cc6fe182f7577bc0ab6e2f0a40be247f35c5e297dbf7bbc460bd02/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:0972488e3f9b449e83f006ead5a0e0a33ad4a13e4462e865b7c286ab7d7566a3", size = 625260, upload-time = "2026-02-20T22:50:25.949Z" }, + { url = "https://files.pythonhosted.org/packages/3c/17/f539507091334b109e7496830af2f093d9fc8082411eafd3ece58af1f8ba/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:1c238812ae0c8ffe77d8d447a32c6dfd058ea4631246b08b5a71df586ff08531", size = 587824, upload-time = "2026-02-20T22:50:35.225Z" }, + { url = "https://files.pythonhosted.org/packages/2e/c2/d37a7b2e41f153519367d4db01f0526e0d4b06f1a4a87f1c5dfca5d70a8b/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:bec8f8ef627af86abf8298e7ec50926627e29b34fa907fcfbedb45aaa72bca43", size = 551407, upload-time = "2026-02-20T22:50:44.915Z" }, + { url = "https://files.pythonhosted.org/packages/65/36/2d24b2cbe78547c6532da33fb8613debd3126eccc33a6374ab788f5e46e9/uuid_utils-0.14.1-cp39-abi3-win32.whl", hash = "sha256:b54d6aa6252d96bac1fdbc80d26ba71bad9f220b2724d692ad2f2310c22ef523", size = 183476, upload-time = "2026-02-20T22:50:32.745Z" }, + { url = "https://files.pythonhosted.org/packages/83/92/2d7e90df8b1a69ec4cff33243ce02b7a62f926ef9e2f0eca5a026889cd73/uuid_utils-0.14.1-cp39-abi3-win_amd64.whl", hash = "sha256:fc27638c2ce267a0ce3e06828aff786f91367f093c80625ee21dad0208e0f5ba", size = 187147, upload-time = "2026-02-20T22:50:45.807Z" }, + { url = "https://files.pythonhosted.org/packages/d9/26/529f4beee17e5248e37e0bc17a2761d34c0fa3b1e5729c88adb2065bae6e/uuid_utils-0.14.1-cp39-abi3-win_arm64.whl", hash = "sha256:b04cb49b42afbc4ff8dbc60cf054930afc479d6f4dd7f1ec3bbe5dbfdde06b7a", size = 188132, upload-time = "2026-02-20T22:50:41.718Z" }, ] [[package]] name = "uvicorn" -version = "0.38.0" +version = "0.41.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cb/ce/f06b84e2697fef4688ca63bdb2fdf113ca0a3be33f94488f2cadb690b0cf/uvicorn-0.38.0.tar.gz", hash = "sha256:fd97093bdd120a2609fc0d3afe931d4d4ad688b6e75f0f929fde1bc36fe0e91d", size = 80605, upload-time = "2025-10-18T13:46:44.63Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/d9/d88e73ca598f4f6ff671fb5fde8a32925c2e08a637303a1d12883c7305fa/uvicorn-0.38.0-py3-none-any.whl", hash = "sha256:48c0afd214ceb59340075b4a052ea1ee91c16fbc2a9b1469cca0e54566977b02", size = 68109, upload-time = "2025-10-18T13:46:42.958Z" }, -] - -[[package]] -name = "uvloop" -version = "0.22.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } +sdist = { url = "https://files.pythonhosted.org/packages/32/ce/eeb58ae4ac36fe09e3842eb02e0eb676bf2c53ae062b98f1b2531673efdd/uvicorn-0.41.0.tar.gz", hash = "sha256:09d11cf7008da33113824ee5a1c6422d89fbc2ff476540d69a34c87fab8b571a", size = 82633, upload-time = "2026-02-16T23:07:24.1Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" }, - { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" }, - { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" }, - { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" }, - { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, + { url = "https://files.pythonhosted.org/packages/83/e4/d04a086285c20886c0daad0e026f250869201013d18f81d9ff5eada73a88/uvicorn-0.41.0-py3-none-any.whl", hash = "sha256:29e35b1d2c36a04b9e180d4007ede3bcb32a85fbdfd6c6aeb3f26839de088187", size = 68783, upload-time = "2026-02-16T23:07:22.357Z" }, ] [[package]] name = "virtualenv" -version = "20.35.4" +version = "21.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, + { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/20/28/e6f1a6f655d620846bd9df527390ecc26b3805a0c5989048c210e22c5ca9/virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c", size = 6028799, upload-time = "2025-10-29T06:57:40.511Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/c9/18d4b36606d6091844daa3bd93cf7dc78e6f5da21d9f21d06c221104b684/virtualenv-21.1.0.tar.gz", hash = "sha256:1990a0188c8f16b6b9cf65c9183049007375b26aad415514d377ccacf1e4fb44", size = 5840471, upload-time = "2026-02-27T08:49:29.702Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b", size = 6005095, upload-time = "2025-10-29T06:57:37.598Z" }, + { url = "https://files.pythonhosted.org/packages/78/55/896b06bf93a49bec0f4ae2a6f1ed12bd05c8860744ac3a70eda041064e4d/virtualenv-21.1.0-py3-none-any.whl", hash = "sha256:164f5e14c5587d170cf98e60378eb91ea35bf037be313811905d3a24ea33cc07", size = 5825072, upload-time = "2026-02-27T08:49:27.516Z" }, ]