python-ddd-framework 0.3.2__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_ddd_framework/__init__.py +139 -21
- python_ddd_framework/application/builder.py +23 -78
- python_ddd_framework/application/composition.py +4 -22
- python_ddd_framework/application/runtime.py +96 -291
- python_ddd_framework/application/state.py +7 -75
- python_ddd_framework/application_services/__init__.py +5 -21
- python_ddd_framework/{services/application_bindings.py → application_services/bindings.py} +52 -9
- python_ddd_framework/application_services/catalog.py +36 -288
- python_ddd_framework/application_services/contracts.py +14 -13
- python_ddd_framework/application_services/dispatcher.py +19 -269
- python_ddd_framework/application_services/invocation.py +126 -7
- python_ddd_framework/application_services/module.py +168 -0
- python_ddd_framework/application_services/pagination.py +21 -0
- python_ddd_framework/auditing/control.py +1 -1
- python_ddd_framework/auditing/module.py +15 -0
- python_ddd_framework/authorization/__init__.py +2 -0
- python_ddd_framework/authorization/contracts.py +7 -2
- python_ddd_framework/authorization/definitions.py +5 -0
- python_ddd_framework/authorization/module.py +40 -0
- python_ddd_framework/authorization/options.py +7 -0
- python_ddd_framework/background_execution/child.py +13 -3
- python_ddd_framework/background_execution/declarations.py +45 -0
- python_ddd_framework/background_execution/lifecycle.py +12 -0
- python_ddd_framework/background_execution/module.py +107 -1
- python_ddd_framework/background_jobs/catalog.py +59 -42
- python_ddd_framework/background_jobs/contracts.py +16 -19
- python_ddd_framework/background_jobs/pgqueuer/enqueue.py +4 -0
- python_ddd_framework/background_jobs/pgqueuer/module.py +2 -1
- python_ddd_framework/background_jobs/pgqueuer/runtime.py +7 -7
- python_ddd_framework/background_workers/catalog.py +23 -36
- python_ddd_framework/background_workers/contracts.py +3 -0
- python_ddd_framework/background_workers/execution.py +1 -1
- python_ddd_framework/background_workers/runtime.py +38 -1
- python_ddd_framework/caching/__init__.py +7 -6
- python_ddd_framework/caching/contracts.py +65 -67
- python_ddd_framework/caching/errors.py +0 -7
- python_ddd_framework/caching/module.py +18 -0
- python_ddd_framework/caching/naming.py +29 -0
- python_ddd_framework/caching/options.py +38 -0
- python_ddd_framework/caching/unit_of_work.py +46 -0
- python_ddd_framework/cli/__init__.py +31 -6
- python_ddd_framework/cli/project.py +68 -6
- python_ddd_framework/cli/runtime.py +1 -1
- python_ddd_framework/developer_kit/generation.py +75 -22
- python_ddd_framework/developer_kit/project_metadata.py +2 -4
- python_ddd_framework/developer_kit/templates/basic/cookiecutter.json +4 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/README.md +45 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/__init__.py.jinja +0 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/contracts/__init__.py.jinja +0 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/contracts/conversion_service.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/module.py.jinja +38 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/services/__init__.py.jinja +0 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/services/default_conversion_service.py.jinja +10 -0
- python_ddd_framework/developer_kit/templates/module/cookiecutter.json +1 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/README.md +41 -49
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/handler.py.jinja +45 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/payload.py.jinja +8 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/handler.py.jinja +35 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/payload.py.jinja +5 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/schedule.py.jinja +15 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/order_maintenance_worker.py.jinja +28 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/order_statistics_worker.py.jinja +26 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/order_cache.py.jinja +9 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/statistics_cache.py.jinja +9 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/event_handlers/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/event_handlers/order_changed_handler.py.jinja +23 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/{integration.py.jinja → hosted_services/order_integration_service.py.jinja} +8 -2
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/order_observation_handler.py.jinja +16 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration_services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration_services/order_reporting_service.py.jinja +16 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/interceptors/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/interceptors/order_timing_interceptor.py.jinja +17 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/module.py.jinja +105 -10
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/options/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/{options.py.jinja → options/order_options.py.jinja} +1 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_approval_service.py.jinja +65 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_management_service.py.jinja +35 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_query_service.py.jinja +41 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/approval_setting_observer.py.jinja +17 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/approve_order.py.jinja +6 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/create_order.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/integration_services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/integration_services/order_reporting_service.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/module.py.jinja +39 -2
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_approval_service.py.jinja +13 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_management_service.py.jinja +10 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_query_service.py.jinja +12 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/order_statistics_snapshot.py.jinja +9 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/order_view.py.jinja +12 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/entities/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/{orders.py.jinja → entities/order.py.jinja} +10 -18
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/events/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/events/order_changed.py.jinja +13 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/module.py.jinja +42 -3
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repositories/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repositories/order_repository.py.jinja +17 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding/order_seed_contributor.py.jinja +22 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/services/order_approval_service.py.jinja +23 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/settings/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/{settings.py.jinja → settings/approval_settings.py.jinja} +3 -2
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/value_objects/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/value_objects/order_title.py.jinja +10 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/constants/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/constants/order_constants.py.jinja +3 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/enums/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/enums/order_status.py.jinja +6 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/errors/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/errors/order_errors.py.jinja +11 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/order_messages.py.jinja +26 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/order_observation.py.jinja +13 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/module.py.jinja +40 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/{permissions.py.jinja → permissions/order_permission_provider.py.jinja} +6 -2
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions/order_permissions.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/value_objects/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/value_objects/money.py.jinja +18 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/filters/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/filters/export_filter.py.jinja +15 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/models/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/models/refresh_orders.py.jinja +5 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/module.py.jinja +85 -7
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/routers/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/{files.py.jinja → routers/order_files.py.jinja} +23 -20
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/websockets/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/websockets/order_socket.py.jinja +44 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/migrations/__init__.py.jinja +1 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/__init__.py.jinja +1 -7
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/base.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/{orders.py.jinja → order_model.py.jinja} +7 -3
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/module.py.jinja +42 -5
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/order_repository.py.jinja +79 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/test_domain.py.jinja +4 -3
- python_ddd_framework/developer_kit/templates/project/cookiecutter.json +1 -1
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/AGENTS.md +16 -3
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/README.md +29 -22
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{app.development.yaml.jinja → backend/app.development.yaml.jinja} +2 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{pyproject.toml.jinja → backend/pyproject.toml.jinja} +1 -1
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/main.py.jinja +2 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/module.py.jinja +40 -3
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/architecture.md +40 -10
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/development.md +134 -23
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/scripts/.gitkeep +0 -0
- python_ddd_framework/distributed_lock/contracts.py +4 -1
- python_ddd_framework/domain/__init__.py +2 -0
- python_ddd_framework/domain/services.py +11 -0
- python_ddd_framework/errors/business.py +10 -1
- python_ddd_framework/errors/lifecycle.py +3 -31
- python_ddd_framework/events/__init__.py +5 -2
- python_ddd_framework/events/aggregate.py +3 -3
- python_ddd_framework/events/catalog.py +12 -11
- python_ddd_framework/events/contracts.py +6 -1
- python_ddd_framework/events/contribution.py +1 -52
- python_ddd_framework/events/entities.py +23 -0
- python_ddd_framework/events/module.py +97 -0
- python_ddd_framework/events/runtime.py +80 -29
- python_ddd_framework/events/subscription.py +36 -0
- python_ddd_framework/events/unit_of_work.py +25 -0
- python_ddd_framework/extensions/__init__.py +22 -0
- python_ddd_framework/extensions/catalog.py +198 -0
- python_ddd_framework/extensions/contracts.py +85 -0
- python_ddd_framework/extensions/models.py +142 -0
- python_ddd_framework/extensions/module.py +21 -0
- python_ddd_framework/fastapi/__init__.py +2 -0
- python_ddd_framework/fastapi/action.py +1 -2
- python_ddd_framework/fastapi/adapter.py +16 -0
- python_ddd_framework/fastapi/application_services.py +43 -25
- python_ddd_framework/fastapi/background.py +2 -1
- python_ddd_framework/fastapi/contracts.py +26 -8
- python_ddd_framework/fastapi/health.py +1 -1
- python_ddd_framework/fastapi/module.py +14 -0
- python_ddd_framework/fastapi/parameters.py +3 -18
- python_ddd_framework/fastapi/realtime/connection.py +10 -7
- python_ddd_framework/fastapi/realtime/module.py +3 -0
- python_ddd_framework/fastapi/realtime/runtime.py +10 -24
- python_ddd_framework/fastapi/request_context.py +2 -1
- python_ddd_framework/fastapi/routing.py +2 -1
- python_ddd_framework/fastapi/server.py +8 -0
- python_ddd_framework/fastapi/settings.py +2 -1
- python_ddd_framework/hosted_services/bridge.py +20 -1
- python_ddd_framework/hosted_services/catalog.py +6 -16
- python_ddd_framework/hosted_services/contracts.py +4 -0
- python_ddd_framework/hosted_services/declarations.py +15 -0
- python_ddd_framework/hosted_services/module.py +69 -0
- python_ddd_framework/hosted_services/runtime.py +30 -1
- python_ddd_framework/identity/__init__.py +24 -0
- python_ddd_framework/identity/application.py +38 -90
- python_ddd_framework/identity/contracts.py +181 -16
- python_ddd_framework/identity/http_api.py +79 -2
- python_ddd_framework/identity/management.py +166 -0
- python_ddd_framework/identity/module.py +12 -4
- python_ddd_framework/identity/services.py +29 -16
- python_ddd_framework/identity/sqlalchemy/migrations/0003_extra_properties.py +26 -0
- python_ddd_framework/identity/sqlalchemy/migrations/0004_concurrency_version.py +29 -0
- python_ddd_framework/identity/sqlalchemy/models.py +21 -2
- python_ddd_framework/identity/sqlalchemy/module.py +11 -2
- python_ddd_framework/identity/sqlalchemy/repository.py +458 -0
- python_ddd_framework/identity/sqlalchemy/session_security.py +88 -0
- python_ddd_framework/identity/sqlalchemy/stores.py +69 -290
- python_ddd_framework/identity/tokens.py +2 -5
- python_ddd_framework/{application_services/validation.py → invocation/arguments.py} +8 -6
- python_ddd_framework/invocation/contribution.py +61 -0
- python_ddd_framework/invocation/dispatcher.py +291 -0
- python_ddd_framework/invocation/entries.py +2 -25
- python_ddd_framework/invocation/entrypoints.py +126 -0
- python_ddd_framework/{application_services → invocation}/execution.py +8 -5
- python_ddd_framework/invocation/function_runtime.py +3 -1
- python_ddd_framework/invocation/interception.py +113 -42
- python_ddd_framework/{application_services → invocation}/interceptors.py +2 -2
- python_ddd_framework/invocation/managed_proxy.py +116 -0
- python_ddd_framework/invocation/managed_services.py +250 -0
- python_ddd_framework/invocation/methods.py +345 -0
- python_ddd_framework/invocation/module.py +113 -0
- python_ddd_framework/{application_services → invocation}/policies.py +8 -1
- python_ddd_framework/invocation/validation.py +25 -0
- python_ddd_framework/lifecycle/__init__.py +2 -0
- python_ddd_framework/lifecycle/composition.py +5 -2
- python_ddd_framework/lifecycle/participants.py +33 -0
- python_ddd_framework/messaging/__init__.py +25 -0
- python_ddd_framework/messaging/channel.py +205 -0
- python_ddd_framework/messaging/contracts.py +69 -0
- python_ddd_framework/messaging/module.py +101 -0
- python_ddd_framework/messaging/options.py +13 -0
- python_ddd_framework/messaging/receipt.py +27 -0
- python_ddd_framework/messaging/runtime.py +124 -0
- python_ddd_framework/modularity/__init__.py +11 -1
- python_ddd_framework/modularity/contracts.py +47 -16
- python_ddd_framework/modularity/discovery.py +21 -2
- python_ddd_framework/modularity/graph.py +45 -1
- python_ddd_framework/modularity/registry.py +83 -3
- python_ddd_framework/notifications/__init__.py +2 -0
- python_ddd_framework/notifications/catalog.py +3 -2
- python_ddd_framework/notifications/declarations.py +18 -0
- python_ddd_framework/notifications/module.py +21 -0
- python_ddd_framework/observability/logging.py +3 -0
- python_ddd_framework/observability/tracing.py +3 -0
- python_ddd_framework/options/contribution.py +12 -0
- python_ddd_framework/options/registry.py +44 -11
- python_ddd_framework/realtime/__init__.py +2 -1
- python_ddd_framework/realtime/contracts.py +3 -7
- python_ddd_framework/realtime/messages.py +26 -1
- python_ddd_framework/redis/cache.py +243 -0
- python_ddd_framework/redis/cache_scripts.py +40 -0
- python_ddd_framework/redis/distributed_lock.py +10 -2
- python_ddd_framework/redis/module.py +13 -4
- python_ddd_framework/redis/notifications.py +5 -2
- python_ddd_framework/redis/runtime.py +1 -79
- python_ddd_framework/seeding/__init__.py +13 -0
- python_ddd_framework/seeding/contracts.py +45 -0
- python_ddd_framework/seeding/module.py +73 -0
- python_ddd_framework/seeding/runtime.py +63 -0
- python_ddd_framework/services/arbitration.py +23 -0
- python_ddd_framework/services/binding.py +2 -3
- python_ddd_framework/services/composition.py +108 -0
- python_ddd_framework/services/contribution.py +44 -54
- python_ddd_framework/services/convention.py +53 -181
- python_ddd_framework/services/fixed_lifetime.py +12 -15
- python_ddd_framework/services/framework_provider.py +3 -158
- python_ddd_framework/services/native_graph.py +35 -60
- python_ddd_framework/services/provider.py +59 -4
- python_ddd_framework/services/repository.py +6 -0
- python_ddd_framework/services/runtime.py +129 -215
- python_ddd_framework/settings/catalog.py +5 -4
- python_ddd_framework/settings/declarations.py +18 -0
- python_ddd_framework/settings/definitions.py +5 -0
- python_ddd_framework/settings/manager.py +1 -1
- python_ddd_framework/settings/module.py +50 -1
- python_ddd_framework/settings/notifications.py +4 -2
- python_ddd_framework/settings/refresh_module.py +9 -2
- python_ddd_framework/settings/sqlalchemy/module.py +2 -1
- python_ddd_framework/sqlalchemy/__init__.py +12 -0
- python_ddd_framework/sqlalchemy/alembic_runtime/env.py +1 -0
- python_ddd_framework/sqlalchemy/auditing.py +7 -1
- python_ddd_framework/sqlalchemy/extensions.py +186 -0
- python_ddd_framework/sqlalchemy/metadata.py +110 -6
- python_ddd_framework/sqlalchemy/migration.py +61 -8
- python_ddd_framework/sqlalchemy/migration_operations.py +86 -0
- python_ddd_framework/sqlalchemy/module.py +14 -11
- python_ddd_framework/sqlalchemy/module_migration.py +71 -3
- python_ddd_framework/sqlalchemy/repository.py +217 -13
- python_ddd_framework/sqlalchemy/session_provider.py +14 -1
- python_ddd_framework/testing/runtime.py +5 -4
- python_ddd_framework/unit_of_work/__init__.py +2 -1
- python_ddd_framework/unit_of_work/contracts.py +6 -7
- python_ddd_framework/unit_of_work/errors.py +11 -0
- python_ddd_framework/unit_of_work/lifecycle.py +19 -0
- python_ddd_framework/unit_of_work/manager.py +86 -32
- python_ddd_framework/unit_of_work/module.py +61 -0
- python_ddd_framework/unit_of_work/options.py +7 -1
- python_ddd_framework/validation/__init__.py +1 -0
- python_ddd_framework/validation/models.py +37 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/METADATA +139 -44
- python_ddd_framework-0.4.0.dist-info/RECORD +488 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/WHEEL +1 -1
- python_ddd_framework/application_services/seeding.py +0 -72
- python_ddd_framework/caching/catalog.py +0 -73
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/cache.py.jinja +0 -10
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/events.py.jinja +0 -24
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/orders.py.jinja +0 -83
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/tasks.py.jinja +0 -84
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/orders.py.jinja +0 -35
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repository.py.jinja +0 -14
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding.py.jinja +0 -19
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/definitions.py.jinja +0 -24
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/realtime.py.jinja +0 -36
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/orders.py.jinja +0 -49
- python_ddd_framework-0.3.2.dist-info/RECORD +0 -355
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.dockerignore → backend/.dockerignore} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.gitignore → backend/.gitignore} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.python-version → backend/.python-version} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{Dockerfile → backend/Dockerfile} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{compose.dev.yaml.jinja → backend/compose.dev.yaml.jinja} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{compose.production.yaml.jinja → backend/compose.production.yaml.jinja} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/__init__.py.jinja +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{tests → backend/tests}/conftest.py.jinja +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{tests → backend/tests}/host/test_http.py.jinja +0 -0
- /python_ddd_framework/{application_services → invocation}/signature.py +0 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/entry_points.txt +0 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/licenses/src/python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt +0 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Identity 管理用例;持久化并发与事务由 repository/UoW 负责。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from uuid import UUID, uuid4
|
|
6
|
+
|
|
7
|
+
from ..application_services import (
|
|
8
|
+
ApplicationService,
|
|
9
|
+
PagedResult,
|
|
10
|
+
PagedResultRequestDto,
|
|
11
|
+
application_service,
|
|
12
|
+
)
|
|
13
|
+
from ..authorization import (
|
|
14
|
+
PermissionCatalog,
|
|
15
|
+
PermissionName,
|
|
16
|
+
authorize,
|
|
17
|
+
)
|
|
18
|
+
from .contracts import (
|
|
19
|
+
IDENTITY_ROLES_MANAGE,
|
|
20
|
+
IDENTITY_USERS_MANAGE,
|
|
21
|
+
CreateRoleCommand,
|
|
22
|
+
CreateUserCommand,
|
|
23
|
+
IdentityConcurrencyCommand,
|
|
24
|
+
IdentityManagementApplicationService,
|
|
25
|
+
IdentityRepository,
|
|
26
|
+
IdentityRole,
|
|
27
|
+
IdentityUser,
|
|
28
|
+
RolePermissionsView,
|
|
29
|
+
RoleView,
|
|
30
|
+
SetPasswordCommand,
|
|
31
|
+
SetRolePermissionsCommand,
|
|
32
|
+
SetUserActiveCommand,
|
|
33
|
+
SetUserRolesCommand,
|
|
34
|
+
UpdateRoleCommand,
|
|
35
|
+
UpdateUserCommand,
|
|
36
|
+
UserRolesView,
|
|
37
|
+
UserView,
|
|
38
|
+
_normalize_name,
|
|
39
|
+
)
|
|
40
|
+
from .errors import (
|
|
41
|
+
IdentityConflictError,
|
|
42
|
+
IdentityResourceNotFoundError,
|
|
43
|
+
)
|
|
44
|
+
from .passwords import PasswordHasher
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@application_service(provides=IdentityManagementApplicationService)
|
|
48
|
+
class DefaultIdentityManagementApplicationService(ApplicationService):
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
repository: IdentityRepository,
|
|
52
|
+
passwords: PasswordHasher,
|
|
53
|
+
permissions: PermissionCatalog,
|
|
54
|
+
) -> None:
|
|
55
|
+
self._repository = repository
|
|
56
|
+
self._passwords = passwords
|
|
57
|
+
self._permissions = permissions
|
|
58
|
+
|
|
59
|
+
@authorize(IDENTITY_USERS_MANAGE)
|
|
60
|
+
async def create_user(self, command: CreateUserCommand) -> UserView:
|
|
61
|
+
normalized = _normalize_name(command.username, role="username")
|
|
62
|
+
if await self._repository.find_user_by_normalized_username(normalized) is not None:
|
|
63
|
+
raise IdentityConflictError(resource="user")
|
|
64
|
+
user = IdentityUser(
|
|
65
|
+
user_id=uuid4(),
|
|
66
|
+
username=command.username.strip(),
|
|
67
|
+
normalized_username=normalized,
|
|
68
|
+
password_hash=await self._passwords.hash(command.password.get_secret_value()),
|
|
69
|
+
is_active=True,
|
|
70
|
+
failed_access_count=0,
|
|
71
|
+
lockout_end=None,
|
|
72
|
+
permission_version=0,
|
|
73
|
+
extra_properties=command.extra_properties,
|
|
74
|
+
)
|
|
75
|
+
await self._repository.create_user(user)
|
|
76
|
+
return UserView(
|
|
77
|
+
user_id=user.user_id,
|
|
78
|
+
username=user.username,
|
|
79
|
+
is_active=user.is_active,
|
|
80
|
+
role_ids=(),
|
|
81
|
+
extra_properties=user.extra_properties,
|
|
82
|
+
concurrency_version=user.concurrency_version,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
@authorize(IDENTITY_USERS_MANAGE)
|
|
86
|
+
async def get_users(self, query: PagedResultRequestDto) -> PagedResult[UserView]:
|
|
87
|
+
return await self._repository.list_users(query)
|
|
88
|
+
|
|
89
|
+
@authorize(IDENTITY_USERS_MANAGE)
|
|
90
|
+
async def get_user(self, user_id: UUID) -> UserView:
|
|
91
|
+
return await self._repository.get_user_view(user_id)
|
|
92
|
+
|
|
93
|
+
@authorize(IDENTITY_USERS_MANAGE)
|
|
94
|
+
async def update_user(self, user_id: UUID, command: UpdateUserCommand) -> UserView:
|
|
95
|
+
return await self._repository.update_user(user_id, command)
|
|
96
|
+
|
|
97
|
+
@authorize(IDENTITY_USERS_MANAGE)
|
|
98
|
+
async def set_user_active(self, user_id: UUID, command: SetUserActiveCommand) -> UserView:
|
|
99
|
+
return await self._repository.set_user_active(user_id, command)
|
|
100
|
+
|
|
101
|
+
@authorize(IDENTITY_USERS_MANAGE)
|
|
102
|
+
async def delete_user(self, user_id: UUID, command: IdentityConcurrencyCommand) -> None:
|
|
103
|
+
await self._repository.delete_user(user_id, command.concurrency_version)
|
|
104
|
+
|
|
105
|
+
@authorize(IDENTITY_USERS_MANAGE)
|
|
106
|
+
async def set_password(self, user_id: UUID, command: SetPasswordCommand) -> None:
|
|
107
|
+
password_hash = await self._passwords.hash(command.new_password.get_secret_value())
|
|
108
|
+
await self._repository.set_password(user_id, password_hash, command.concurrency_version)
|
|
109
|
+
|
|
110
|
+
@authorize(IDENTITY_ROLES_MANAGE)
|
|
111
|
+
async def create_role(self, command: CreateRoleCommand) -> RoleView:
|
|
112
|
+
normalized = _normalize_name(command.name, role="role name")
|
|
113
|
+
if await self._repository.find_role_by_normalized_name(normalized) is not None:
|
|
114
|
+
raise IdentityConflictError(resource="role")
|
|
115
|
+
role = IdentityRole(uuid4(), command.name.strip(), normalized, 0)
|
|
116
|
+
await self._repository.create_role(role)
|
|
117
|
+
return RoleView(role.role_id, role.name, (), role.concurrency_version)
|
|
118
|
+
|
|
119
|
+
@authorize(IDENTITY_ROLES_MANAGE)
|
|
120
|
+
async def get_roles(self, query: PagedResultRequestDto) -> PagedResult[RoleView]:
|
|
121
|
+
return await self._repository.list_roles(query)
|
|
122
|
+
|
|
123
|
+
@authorize(IDENTITY_ROLES_MANAGE)
|
|
124
|
+
async def get_role(self, role_id: UUID) -> RoleView:
|
|
125
|
+
return await self._repository.get_role(role_id)
|
|
126
|
+
|
|
127
|
+
@authorize(IDENTITY_ROLES_MANAGE)
|
|
128
|
+
async def update_role(self, role_id: UUID, command: UpdateRoleCommand) -> RoleView:
|
|
129
|
+
return await self._repository.update_role(role_id, command)
|
|
130
|
+
|
|
131
|
+
@authorize(IDENTITY_ROLES_MANAGE)
|
|
132
|
+
async def delete_role(self, role_id: UUID, command: IdentityConcurrencyCommand) -> None:
|
|
133
|
+
await self._repository.delete_role(role_id, command.concurrency_version)
|
|
134
|
+
|
|
135
|
+
@authorize(IDENTITY_USERS_MANAGE, IDENTITY_ROLES_MANAGE)
|
|
136
|
+
async def get_user_roles(self, user_id: UUID) -> UserRolesView:
|
|
137
|
+
user = await self._repository.get_user_view(user_id)
|
|
138
|
+
return UserRolesView(role_ids=user.role_ids, concurrency_version=user.concurrency_version)
|
|
139
|
+
|
|
140
|
+
@authorize(IDENTITY_ROLES_MANAGE)
|
|
141
|
+
async def get_role_permissions(self, role_id: UUID) -> RolePermissionsView:
|
|
142
|
+
role = await self._repository.get_role(role_id)
|
|
143
|
+
return RolePermissionsView(
|
|
144
|
+
permissions=role.permissions, concurrency_version=role.concurrency_version
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
@authorize(IDENTITY_USERS_MANAGE, IDENTITY_ROLES_MANAGE)
|
|
148
|
+
async def set_user_roles(self, user_id: UUID, command: SetUserRolesCommand) -> UserRolesView:
|
|
149
|
+
role_ids = tuple(dict.fromkeys(command.role_ids))
|
|
150
|
+
return await self._repository.set_user_roles(user_id, role_ids, command.concurrency_version)
|
|
151
|
+
|
|
152
|
+
@authorize(IDENTITY_ROLES_MANAGE)
|
|
153
|
+
async def set_role_permissions(
|
|
154
|
+
self, role_id: UUID, command: SetRolePermissionsCommand
|
|
155
|
+
) -> RolePermissionsView:
|
|
156
|
+
names = tuple(PermissionName(item) for item in command.permissions)
|
|
157
|
+
if len(set(names)) != len(names):
|
|
158
|
+
raise IdentityConflictError(resource="role permission")
|
|
159
|
+
unknown = tuple(item for item in names if item not in self._permissions)
|
|
160
|
+
if unknown:
|
|
161
|
+
raise IdentityResourceNotFoundError(resource="permission")
|
|
162
|
+
return await self._repository.set_role_permissions(
|
|
163
|
+
role_id,
|
|
164
|
+
tuple(item.value for item in names),
|
|
165
|
+
command.concurrency_version,
|
|
166
|
+
)
|
|
@@ -5,14 +5,17 @@ from __future__ import annotations
|
|
|
5
5
|
from dishka import Provider, Scope
|
|
6
6
|
|
|
7
7
|
from ..authorization import AccessTokenAuthenticator, PermissionChecker
|
|
8
|
+
from ..caching.module import CachingModule
|
|
9
|
+
from ..extensions.contracts import ExtensionPoints
|
|
8
10
|
from ..lifecycle import ConfigureContext
|
|
9
|
-
from ..modularity import AppModule
|
|
11
|
+
from ..modularity import AppModule, ModuleRef
|
|
12
|
+
from ..seeding.module import DataSeedingModule
|
|
10
13
|
from .contracts import (
|
|
14
|
+
IDENTITY_USER_EXTENSION,
|
|
11
15
|
IdentityOptions,
|
|
12
16
|
)
|
|
13
17
|
from .passwords import PasswordHasher
|
|
14
18
|
from .services import (
|
|
15
|
-
IDENTITY_PERMISSION_CACHE,
|
|
16
19
|
IdentityAuthenticator,
|
|
17
20
|
IdentityPermissionChecker,
|
|
18
21
|
)
|
|
@@ -20,13 +23,18 @@ from .tokens import JwtTokenService
|
|
|
20
23
|
|
|
21
24
|
|
|
22
25
|
class IdentityModule(AppModule):
|
|
26
|
+
extension_points = ExtensionPoints((IDENTITY_USER_EXTENSION,))
|
|
27
|
+
dependencies = (
|
|
28
|
+
DataSeedingModule,
|
|
29
|
+
CachingModule,
|
|
30
|
+
ModuleRef("python_ddd_framework.application_services.module:ApplicationServicesModule"),
|
|
31
|
+
)
|
|
23
32
|
scan_packages = (
|
|
24
33
|
"python_ddd_framework.identity.application",
|
|
34
|
+
"python_ddd_framework.identity.management",
|
|
25
35
|
"python_ddd_framework.identity.permissions",
|
|
26
36
|
)
|
|
27
37
|
|
|
28
|
-
cache_definitions = (IDENTITY_PERMISSION_CACHE,)
|
|
29
|
-
|
|
30
38
|
def configure(self, context: ConfigureContext) -> None:
|
|
31
39
|
context.configure(
|
|
32
40
|
IdentityOptions,
|
|
@@ -9,8 +9,20 @@ from dishka import AsyncContainer
|
|
|
9
9
|
from dishka.exceptions import NoFactoryError
|
|
10
10
|
from pydantic import BaseModel, ConfigDict
|
|
11
11
|
|
|
12
|
-
from ..authorization import
|
|
13
|
-
|
|
12
|
+
from ..authorization import (
|
|
13
|
+
AuthorizationOptions,
|
|
14
|
+
CurrentUser,
|
|
15
|
+
PermissionCatalog,
|
|
16
|
+
PermissionChecker,
|
|
17
|
+
PermissionName,
|
|
18
|
+
)
|
|
19
|
+
from ..caching import (
|
|
20
|
+
DistributedCache,
|
|
21
|
+
DistributedCacheEntryOptions,
|
|
22
|
+
DistributedCacheError,
|
|
23
|
+
cache_name,
|
|
24
|
+
)
|
|
25
|
+
from ..options import Options
|
|
14
26
|
from .contracts import IdentityAccessStore
|
|
15
27
|
from .errors import InvalidCredentialsError
|
|
16
28
|
from .tokens import JwtTokenService
|
|
@@ -18,19 +30,13 @@ from .tokens import JwtTokenService
|
|
|
18
30
|
_LOGGER = logging.getLogger(__name__)
|
|
19
31
|
|
|
20
32
|
|
|
33
|
+
@cache_name("identity.permissions")
|
|
21
34
|
class PermissionCacheEntry(BaseModel):
|
|
22
35
|
model_config = ConfigDict(frozen=True)
|
|
23
36
|
|
|
24
37
|
permissions: tuple[str, ...]
|
|
25
38
|
|
|
26
39
|
|
|
27
|
-
IDENTITY_PERMISSION_CACHE = CacheDefinition(
|
|
28
|
-
"identity.permissions",
|
|
29
|
-
PermissionCacheEntry,
|
|
30
|
-
ttl=timedelta(minutes=5),
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
|
|
34
40
|
class IdentityAuthenticator:
|
|
35
41
|
__slots__ = ("_access", "_tokens")
|
|
36
42
|
|
|
@@ -51,31 +57,35 @@ class IdentityAuthenticator:
|
|
|
51
57
|
|
|
52
58
|
|
|
53
59
|
class IdentityPermissionChecker(PermissionChecker):
|
|
54
|
-
__slots__ = ("_access", "_catalog", "_container")
|
|
60
|
+
__slots__ = ("_access", "_catalog", "_container", "_options")
|
|
55
61
|
|
|
56
62
|
def __init__(
|
|
57
63
|
self,
|
|
58
64
|
access: IdentityAccessStore,
|
|
59
65
|
catalog: PermissionCatalog,
|
|
60
66
|
container: AsyncContainer,
|
|
67
|
+
options: Options[AuthorizationOptions],
|
|
61
68
|
) -> None:
|
|
62
69
|
self._access = access
|
|
63
70
|
self._catalog = catalog
|
|
64
71
|
self._container = container
|
|
72
|
+
self._options = options.value
|
|
65
73
|
|
|
66
74
|
async def is_granted(self, user: CurrentUser, permission: PermissionName) -> bool:
|
|
75
|
+
if self._options.always_allow:
|
|
76
|
+
return True
|
|
67
77
|
if not user.is_authenticated or permission not in self._catalog:
|
|
68
78
|
return False
|
|
69
79
|
cache = await self._optional_cache()
|
|
70
80
|
cache_key = _permission_cache_key(user, self._catalog.fingerprint)
|
|
71
81
|
if cache is not None:
|
|
72
82
|
try:
|
|
73
|
-
cached = await cache.get(
|
|
83
|
+
cached = await cache.get(cache_key, hide_errors=False)
|
|
74
84
|
except DistributedCacheError:
|
|
75
85
|
cached = None
|
|
76
86
|
_LOGGER.warning(
|
|
77
87
|
"permission cache read failed; falling back to authoritative database",
|
|
78
|
-
extra={"cache":
|
|
88
|
+
extra={"cache": "identity.permissions"},
|
|
79
89
|
)
|
|
80
90
|
if cached is not None:
|
|
81
91
|
return permission.value in cached.permissions
|
|
@@ -84,20 +94,23 @@ class IdentityPermissionChecker(PermissionChecker):
|
|
|
84
94
|
if cache is not None:
|
|
85
95
|
try:
|
|
86
96
|
await cache.set(
|
|
87
|
-
IDENTITY_PERMISSION_CACHE,
|
|
88
97
|
cache_key,
|
|
89
98
|
PermissionCacheEntry(permissions=tuple(sorted(permissions))),
|
|
99
|
+
options=DistributedCacheEntryOptions(
|
|
100
|
+
absolute_expiration_relative_to_now=timedelta(minutes=5)
|
|
101
|
+
),
|
|
102
|
+
hide_errors=False,
|
|
90
103
|
)
|
|
91
104
|
except DistributedCacheError:
|
|
92
105
|
_LOGGER.warning(
|
|
93
106
|
"permission cache write failed; database result remains authoritative",
|
|
94
|
-
extra={"cache":
|
|
107
|
+
extra={"cache": "identity.permissions"},
|
|
95
108
|
)
|
|
96
109
|
return permission.value in permissions
|
|
97
110
|
|
|
98
|
-
async def _optional_cache(self) -> DistributedCache | None:
|
|
111
|
+
async def _optional_cache(self) -> DistributedCache[PermissionCacheEntry] | None:
|
|
99
112
|
try:
|
|
100
|
-
return await self._container.get(DistributedCache)
|
|
113
|
+
return await self._container.get(DistributedCache[PermissionCacheEntry])
|
|
101
114
|
except NoFactoryError:
|
|
102
115
|
return None
|
|
103
116
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Identity 拥有基础扩展容器;消费者独立列由自身 revision 管理。"""
|
|
2
|
+
|
|
3
|
+
import sqlalchemy as sa
|
|
4
|
+
from alembic import op
|
|
5
|
+
from sqlalchemy.dialects import postgresql
|
|
6
|
+
|
|
7
|
+
revision = "identity_0003"
|
|
8
|
+
down_revision = "identity_0002"
|
|
9
|
+
branch_labels = None
|
|
10
|
+
depends_on = None
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def upgrade() -> None:
|
|
14
|
+
op.add_column(
|
|
15
|
+
"identity_users",
|
|
16
|
+
sa.Column(
|
|
17
|
+
"extra_properties",
|
|
18
|
+
postgresql.JSONB(),
|
|
19
|
+
nullable=False,
|
|
20
|
+
server_default=sa.text("'{}'::jsonb"),
|
|
21
|
+
),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def downgrade() -> None:
|
|
26
|
+
raise RuntimeError("removing extension data requires an explicit data migration")
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Identity owns edit versions independently of permission cache versions."""
|
|
2
|
+
|
|
3
|
+
import sqlalchemy as sa
|
|
4
|
+
from alembic import op
|
|
5
|
+
|
|
6
|
+
revision = "identity_0004"
|
|
7
|
+
down_revision = "identity_0003"
|
|
8
|
+
branch_labels = None
|
|
9
|
+
depends_on = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def upgrade() -> None:
|
|
13
|
+
op.add_column(
|
|
14
|
+
"identity_users",
|
|
15
|
+
sa.Column(
|
|
16
|
+
"concurrency_version", sa.BigInteger(), nullable=False, server_default=sa.text("0")
|
|
17
|
+
),
|
|
18
|
+
)
|
|
19
|
+
op.add_column(
|
|
20
|
+
"identity_roles",
|
|
21
|
+
sa.Column(
|
|
22
|
+
"concurrency_version", sa.BigInteger(), nullable=False, server_default=sa.text("0")
|
|
23
|
+
),
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def downgrade() -> None:
|
|
28
|
+
op.drop_column("identity_roles", "concurrency_version")
|
|
29
|
+
op.drop_column("identity_users", "concurrency_version")
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from datetime import datetime
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
6
7
|
from uuid import UUID
|
|
7
8
|
|
|
8
|
-
from sqlalchemy import BigInteger, Boolean, DateTime, ForeignKey, Integer, String, Uuid
|
|
9
|
+
from sqlalchemy import BigInteger, Boolean, DateTime, ForeignKey, Integer, String, Uuid, text
|
|
10
|
+
from sqlalchemy.dialects.postgresql import JSONB
|
|
9
11
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
|
10
12
|
|
|
11
13
|
from ...sqlalchemy import AuditedEntityMixin
|
|
@@ -15,7 +17,7 @@ class IdentityBase(DeclarativeBase):
|
|
|
15
17
|
pass
|
|
16
18
|
|
|
17
19
|
|
|
18
|
-
class
|
|
20
|
+
class UserRowValues(AuditedEntityMixin):
|
|
19
21
|
__tablename__ = "identity_users"
|
|
20
22
|
|
|
21
23
|
user_id: Mapped[UUID] = mapped_column(Uuid, primary_key=True)
|
|
@@ -28,6 +30,20 @@ class UserRow(AuditedEntityMixin, IdentityBase):
|
|
|
28
30
|
failed_access_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
|
29
31
|
lockout_end: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
30
32
|
permission_version: Mapped[int] = mapped_column(BigInteger, nullable=False, default=0)
|
|
33
|
+
concurrency_version: Mapped[int] = mapped_column(
|
|
34
|
+
BigInteger, nullable=False, default=0, server_default=text("0")
|
|
35
|
+
)
|
|
36
|
+
extra_properties: Mapped[dict[str, object]] = mapped_column(
|
|
37
|
+
JSONB, nullable=False, default=dict, server_default=text("'{}'::jsonb")
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if TYPE_CHECKING:
|
|
41
|
+
# 实例由 SQLAlchemy 原生 mapper 的关键字构造器创建。
|
|
42
|
+
def __init__(self, **values: object) -> None: ...
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class UserRow(UserRowValues, IdentityBase):
|
|
46
|
+
"""基础 Schema 声明;运行时使用本 Application 从 mixin 生成的 mapper。"""
|
|
31
47
|
|
|
32
48
|
|
|
33
49
|
class RoleRow(AuditedEntityMixin, IdentityBase):
|
|
@@ -39,6 +55,9 @@ class RoleRow(AuditedEntityMixin, IdentityBase):
|
|
|
39
55
|
String(120), nullable=False, unique=True, index=True
|
|
40
56
|
)
|
|
41
57
|
permission_version: Mapped[int] = mapped_column(BigInteger, nullable=False, default=0)
|
|
58
|
+
concurrency_version: Mapped[int] = mapped_column(
|
|
59
|
+
BigInteger, nullable=False, default=0, server_default=text("0")
|
|
60
|
+
)
|
|
42
61
|
|
|
43
62
|
|
|
44
63
|
class UserRoleRow(AuditedEntityMixin, IdentityBase):
|
|
@@ -7,22 +7,24 @@ from dishka import Provider, Scope
|
|
|
7
7
|
from ...lifecycle import ConfigureContext
|
|
8
8
|
from ...modularity import AppModule
|
|
9
9
|
from ...sqlalchemy import (
|
|
10
|
+
SqlAlchemyExtensionMapping,
|
|
10
11
|
SqlAlchemyModelRegistration,
|
|
11
12
|
SqlAlchemyPersistenceModule,
|
|
12
13
|
contributes_sqlalchemy_models,
|
|
13
14
|
)
|
|
14
15
|
from ..contracts import (
|
|
16
|
+
IDENTITY_USER_EXTENSION,
|
|
15
17
|
IdentityAccessStore,
|
|
16
18
|
IdentityLoginStore,
|
|
17
19
|
IdentityRepository,
|
|
18
20
|
RefreshSessionStore,
|
|
19
21
|
)
|
|
20
22
|
from ..module import IdentityModule
|
|
21
|
-
from .models import IdentityBase
|
|
23
|
+
from .models import IdentityBase, UserRow, UserRowValues
|
|
24
|
+
from .repository import SqlAlchemyIdentityRepository
|
|
22
25
|
from .stores import (
|
|
23
26
|
SqlAlchemyIdentityAccessStore,
|
|
24
27
|
SqlAlchemyIdentityLoginStore,
|
|
25
|
-
SqlAlchemyIdentityRepository,
|
|
26
28
|
SqlAlchemyRefreshSessionStore,
|
|
27
29
|
)
|
|
28
30
|
|
|
@@ -32,6 +34,13 @@ from .stores import (
|
|
|
32
34
|
metadata=IdentityBase.metadata,
|
|
33
35
|
migrations_package="python_ddd_framework.identity.sqlalchemy.migrations",
|
|
34
36
|
branch_label="identity",
|
|
37
|
+
extension_mappings=(
|
|
38
|
+
SqlAlchemyExtensionMapping(
|
|
39
|
+
IDENTITY_USER_EXTENSION,
|
|
40
|
+
IdentityBase.metadata.tables[UserRow.__tablename__],
|
|
41
|
+
UserRowValues,
|
|
42
|
+
),
|
|
43
|
+
),
|
|
35
44
|
)
|
|
36
45
|
)
|
|
37
46
|
class SqlAlchemyIdentityModule(AppModule):
|