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
|
@@ -8,10 +8,11 @@ import pkgutil
|
|
|
8
8
|
from collections.abc import Mapping
|
|
9
9
|
from dataclasses import dataclass
|
|
10
10
|
from types import MappingProxyType, ModuleType
|
|
11
|
+
from typing import get_origin
|
|
11
12
|
|
|
12
13
|
from ..diagnostics import SourceLocation
|
|
13
14
|
from ..errors import ConventionScanError
|
|
14
|
-
from .contracts import ModuleKey
|
|
15
|
+
from .contracts import AppModule, ModuleKey, _module_declarations
|
|
15
16
|
from .registry import ModuleDescriptor, ModuleRegistry
|
|
16
17
|
|
|
17
18
|
|
|
@@ -26,7 +27,7 @@ class _DiscoveredType:
|
|
|
26
27
|
def _discover_types(
|
|
27
28
|
registry: ModuleRegistry, packages: Mapping[ModuleKey, tuple[str, ...]]
|
|
28
29
|
) -> tuple[_DiscoveredType, ...]:
|
|
29
|
-
|
|
30
|
+
discoveries = tuple(
|
|
30
31
|
_DiscoveredType(
|
|
31
32
|
descriptor.key, package, candidate, _class_source(candidate, descriptor.key)
|
|
32
33
|
)
|
|
@@ -35,6 +36,24 @@ def _discover_types(
|
|
|
35
36
|
for module in _import_modules(descriptor, package)
|
|
36
37
|
for candidate in _module_candidates(module)
|
|
37
38
|
)
|
|
39
|
+
for discovery in discoveries:
|
|
40
|
+
if _is_concrete_implementation(discovery.candidate):
|
|
41
|
+
_require_type_dependencies(
|
|
42
|
+
registry, discovery.owner, discovery.candidate, source=discovery.source
|
|
43
|
+
)
|
|
44
|
+
return discoveries
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _require_type_dependencies(
|
|
48
|
+
registry: ModuleRegistry, owner: ModuleKey, candidate: object, *, source: SourceLocation
|
|
49
|
+
) -> None:
|
|
50
|
+
candidate = get_origin(candidate) or candidate
|
|
51
|
+
# 原生注册与扫描共用角色声明;Host 的同级能力不能补足贡献 owner 的依赖。
|
|
52
|
+
# Module 类本身只由正式图解释,不作为扫描/Provider owner 的服务角色。
|
|
53
|
+
if not isinstance(candidate, type) or issubclass(candidate, AppModule):
|
|
54
|
+
return
|
|
55
|
+
for declaration in _module_declarations(candidate).values():
|
|
56
|
+
registry.require_dependency(owner, declaration.required_module, source=source)
|
|
38
57
|
|
|
39
58
|
|
|
40
59
|
def _import_modules(
|
|
@@ -7,6 +7,7 @@ import inspect
|
|
|
7
7
|
from collections.abc import Mapping
|
|
8
8
|
from graphlib import CycleError, TopologicalSorter
|
|
9
9
|
from itertools import pairwise
|
|
10
|
+
from types import MappingProxyType
|
|
10
11
|
from typing import TypeGuard
|
|
11
12
|
|
|
12
13
|
from ..diagnostics import SourceLocation
|
|
@@ -18,7 +19,15 @@ from ..errors import (
|
|
|
18
19
|
ModuleInstantiationError,
|
|
19
20
|
ModuleResolutionError,
|
|
20
21
|
)
|
|
21
|
-
from .contracts import
|
|
22
|
+
from .contracts import (
|
|
23
|
+
AppModule,
|
|
24
|
+
ModuleDeclaration,
|
|
25
|
+
ModuleDependency,
|
|
26
|
+
ModuleDependencyEdge,
|
|
27
|
+
ModuleKey,
|
|
28
|
+
ModuleRef,
|
|
29
|
+
_module_declarations,
|
|
30
|
+
)
|
|
22
31
|
from .registry import ModuleDescriptor, ModuleRegistry
|
|
23
32
|
|
|
24
33
|
|
|
@@ -50,6 +59,7 @@ class _ModuleGraphBuilder:
|
|
|
50
59
|
)
|
|
51
60
|
self._discover(startup_type, startup_key, (), ())
|
|
52
61
|
dependency_keys = self._stable_dependency_order(startup_key)
|
|
62
|
+
declarations = self._validate_declarations(dependency_keys)
|
|
53
63
|
instances = self._instantiate(dependency_keys)
|
|
54
64
|
descriptors = {
|
|
55
65
|
key: ModuleDescriptor(
|
|
@@ -64,6 +74,7 @@ class _ModuleGraphBuilder:
|
|
|
64
74
|
),
|
|
65
75
|
discovery_index=self._discovery_indices[key],
|
|
66
76
|
declaration_source=self._sources[key],
|
|
77
|
+
declarations=declarations[key],
|
|
67
78
|
)
|
|
68
79
|
for key in dependency_keys
|
|
69
80
|
}
|
|
@@ -74,6 +85,39 @@ class _ModuleGraphBuilder:
|
|
|
74
85
|
dependency_order=dependency_order,
|
|
75
86
|
)
|
|
76
87
|
|
|
88
|
+
def _validate_declarations(
|
|
89
|
+
self, dependency_keys: tuple[ModuleKey, ...]
|
|
90
|
+
) -> dict[ModuleKey, Mapping[str, ModuleDeclaration]]:
|
|
91
|
+
declarations: dict[ModuleKey, Mapping[str, ModuleDeclaration]] = {}
|
|
92
|
+
reachable: dict[ModuleKey, set[type[AppModule]]] = {}
|
|
93
|
+
for key in dependency_keys:
|
|
94
|
+
module_type = self._module_types[key]
|
|
95
|
+
available = {module_type}
|
|
96
|
+
for dependency in self._predecessors[key]:
|
|
97
|
+
available.update(reachable[dependency])
|
|
98
|
+
reachable[key] = available
|
|
99
|
+
# 按 Python MRO 取有效属性;声明名称和能力种类均不成为内核第二清单。
|
|
100
|
+
attributes = _module_declarations(module_type)
|
|
101
|
+
for name, value in attributes.items():
|
|
102
|
+
required, _ = self._resolve_module(
|
|
103
|
+
value.required_module,
|
|
104
|
+
owner=key,
|
|
105
|
+
owner_source=self._sources[key],
|
|
106
|
+
resolution_chain=(str(key), name),
|
|
107
|
+
)
|
|
108
|
+
if required not in available:
|
|
109
|
+
raise InvalidModuleReferenceError(
|
|
110
|
+
owner=key,
|
|
111
|
+
reference=name,
|
|
112
|
+
source=self._sources[key],
|
|
113
|
+
reason=(
|
|
114
|
+
f"declaration requires a dependency on "
|
|
115
|
+
f"{required.__module__}:{required.__qualname__}"
|
|
116
|
+
),
|
|
117
|
+
)
|
|
118
|
+
declarations[key] = MappingProxyType(attributes)
|
|
119
|
+
return declarations
|
|
120
|
+
|
|
77
121
|
def _discover(
|
|
78
122
|
self,
|
|
79
123
|
module_type: type[AppModule],
|
|
@@ -3,12 +3,23 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from collections.abc import Iterator, Mapping
|
|
6
|
-
from dataclasses import dataclass
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
7
|
from types import MappingProxyType
|
|
8
|
+
from typing import TypeVar, cast
|
|
8
9
|
|
|
9
10
|
from ..diagnostics import SourceLocation
|
|
10
|
-
from ..errors import UnknownModuleError
|
|
11
|
-
from .contracts import
|
|
11
|
+
from ..errors import InvalidModuleReferenceError, UnknownModuleError
|
|
12
|
+
from .contracts import (
|
|
13
|
+
AppModule,
|
|
14
|
+
ModuleDeclaration,
|
|
15
|
+
ModuleDependency,
|
|
16
|
+
ModuleDependencyEdge,
|
|
17
|
+
ModuleKey,
|
|
18
|
+
_module_declarations,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
_TDeclaration = TypeVar("_TDeclaration", bound=ModuleDeclaration)
|
|
22
|
+
_TModule = TypeVar("_TModule", bound=AppModule)
|
|
12
23
|
|
|
13
24
|
|
|
14
25
|
@dataclass(frozen=True, slots=True)
|
|
@@ -21,6 +32,9 @@ class ModuleDescriptor:
|
|
|
21
32
|
dependency_edges: tuple[ModuleDependencyEdge, ...]
|
|
22
33
|
discovery_index: int
|
|
23
34
|
declaration_source: SourceLocation
|
|
35
|
+
declarations: Mapping[str, ModuleDeclaration] = field(
|
|
36
|
+
default_factory=lambda: MappingProxyType({})
|
|
37
|
+
)
|
|
24
38
|
|
|
25
39
|
|
|
26
40
|
@dataclass(frozen=True, slots=True)
|
|
@@ -93,6 +107,72 @@ class ModuleRegistry:
|
|
|
93
107
|
except KeyError:
|
|
94
108
|
raise UnknownModuleError(key) from None
|
|
95
109
|
|
|
110
|
+
def declarations(
|
|
111
|
+
self, declaration_type: type[_TDeclaration]
|
|
112
|
+
) -> tuple[tuple[ModuleDescriptor, _TDeclaration], ...]:
|
|
113
|
+
"""只读取本次图冻结的声明,避免生命周期中改写 class 属性改变组装事实。"""
|
|
114
|
+
return tuple(
|
|
115
|
+
(descriptor, declaration)
|
|
116
|
+
for descriptor in self._dependency_order
|
|
117
|
+
for declaration in descriptor.declarations.values()
|
|
118
|
+
if isinstance(declaration, declaration_type)
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def instance(self, module_type: type[_TModule]) -> _TModule:
|
|
122
|
+
"""取得图内的 Module 组装 owner,不解析或创建任何 DI 服务。"""
|
|
123
|
+
key = ModuleKey(module_type.__module__, module_type.__qualname__)
|
|
124
|
+
descriptor = self[key]
|
|
125
|
+
if descriptor.module_type is not module_type:
|
|
126
|
+
raise UnknownModuleError(key)
|
|
127
|
+
return cast(_TModule, descriptor.instance)
|
|
128
|
+
|
|
129
|
+
def require_dependency(
|
|
130
|
+
self,
|
|
131
|
+
owner: ModuleKey,
|
|
132
|
+
dependency: ModuleDependency,
|
|
133
|
+
*,
|
|
134
|
+
source: SourceLocation,
|
|
135
|
+
) -> None:
|
|
136
|
+
from .graph import _ModuleGraphBuilder
|
|
137
|
+
|
|
138
|
+
required, key = _ModuleGraphBuilder._resolve_module(
|
|
139
|
+
dependency,
|
|
140
|
+
owner=owner,
|
|
141
|
+
owner_source=source,
|
|
142
|
+
resolution_chain=(str(owner),),
|
|
143
|
+
)
|
|
144
|
+
pending = [owner]
|
|
145
|
+
visited: set[ModuleKey] = set()
|
|
146
|
+
while pending:
|
|
147
|
+
current = pending.pop()
|
|
148
|
+
if current in visited:
|
|
149
|
+
continue
|
|
150
|
+
visited.add(current)
|
|
151
|
+
descriptor = self[current]
|
|
152
|
+
if current == key and descriptor.module_type is required:
|
|
153
|
+
return
|
|
154
|
+
pending.extend(descriptor.dependencies)
|
|
155
|
+
raise InvalidModuleReferenceError(
|
|
156
|
+
owner=owner,
|
|
157
|
+
reference=str(key),
|
|
158
|
+
source=source,
|
|
159
|
+
reason="capability declaration requires an explicit Module dependency",
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def assert_declarations_unchanged(self) -> None:
|
|
163
|
+
for descriptor in self._dependency_order:
|
|
164
|
+
current = _module_declarations(descriptor.module_type)
|
|
165
|
+
frozen = descriptor.declarations
|
|
166
|
+
if current.keys() != frozen.keys() or any(
|
|
167
|
+
current[name] is not value for name, value in frozen.items()
|
|
168
|
+
):
|
|
169
|
+
raise InvalidModuleReferenceError(
|
|
170
|
+
owner=descriptor.key,
|
|
171
|
+
reference=str(descriptor.key),
|
|
172
|
+
source=descriptor.declaration_source,
|
|
173
|
+
reason="Module declarations changed after the Application snapshot",
|
|
174
|
+
)
|
|
175
|
+
|
|
96
176
|
def __iter__(self) -> Iterator[ModuleKey]:
|
|
97
177
|
return iter(self._descriptors)
|
|
98
178
|
|
|
@@ -8,11 +8,13 @@ from .contracts import (
|
|
|
8
8
|
NotificationPublishStatus,
|
|
9
9
|
Notifications,
|
|
10
10
|
)
|
|
11
|
+
from .declarations import NotificationDefinitions
|
|
11
12
|
|
|
12
13
|
__all__ = (
|
|
13
14
|
"NotificationCatalog",
|
|
14
15
|
"NotificationDefinition",
|
|
15
16
|
"NotificationDefinitionError",
|
|
17
|
+
"NotificationDefinitions",
|
|
16
18
|
"NotificationPayloadError",
|
|
17
19
|
"NotificationPublishStatus",
|
|
18
20
|
"Notifications",
|
|
@@ -8,6 +8,7 @@ from typing import Any
|
|
|
8
8
|
|
|
9
9
|
from ..modularity import ModuleRegistry
|
|
10
10
|
from .contracts import NotificationDefinition, NotificationDefinitionError
|
|
11
|
+
from .declarations import NotificationDefinitions
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class NotificationCatalog:
|
|
@@ -36,8 +37,8 @@ class NotificationCatalog:
|
|
|
36
37
|
|
|
37
38
|
def _build_notification_catalog(registry: ModuleRegistry) -> NotificationCatalog:
|
|
38
39
|
definitions: list[NotificationDefinition[Any]] = []
|
|
39
|
-
for
|
|
40
|
-
declared =
|
|
40
|
+
for _, declaration in registry.declarations(NotificationDefinitions):
|
|
41
|
+
declared = declaration.definitions
|
|
41
42
|
if not isinstance(declared, tuple):
|
|
42
43
|
raise NotificationDefinitionError(
|
|
43
44
|
"Module notification_definitions must be an immutable tuple"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""通知声明由通知能力解释,传输选择仍归 Host。"""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from ..modularity import AppModule, ModuleDeclaration
|
|
7
|
+
from .contracts import NotificationDefinition
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True, slots=True)
|
|
11
|
+
class NotificationDefinitions(ModuleDeclaration):
|
|
12
|
+
definitions: tuple[NotificationDefinition[Any], ...]
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def required_module(self) -> type[AppModule]:
|
|
16
|
+
from .module import NotificationsModule
|
|
17
|
+
|
|
18
|
+
return NotificationsModule
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""通知目录的组装 owner;不选择或创建传输资源。"""
|
|
2
|
+
|
|
3
|
+
from dishka import Provider, Scope
|
|
4
|
+
|
|
5
|
+
from ..lifecycle import ConfigureContext
|
|
6
|
+
from ..modularity import AppModule
|
|
7
|
+
from ..services.composition import ServiceBindingContext, ServiceComposition
|
|
8
|
+
from .catalog import NotificationCatalog, _build_notification_catalog
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class NotificationsModule(AppModule):
|
|
12
|
+
catalog: NotificationCatalog
|
|
13
|
+
|
|
14
|
+
def configure(self, context: ConfigureContext) -> None:
|
|
15
|
+
context.services.add_composition(ServiceComposition(bind=self._bind))
|
|
16
|
+
|
|
17
|
+
def _bind(self, context: ServiceBindingContext) -> None:
|
|
18
|
+
self.catalog = _build_notification_catalog(context.modules)
|
|
19
|
+
provider = Provider()
|
|
20
|
+
provider.provide(lambda: self.catalog, provides=NotificationCatalog, scope=Scope.APP)
|
|
21
|
+
context.add_provider(provider)
|
|
@@ -10,6 +10,7 @@ from pythonjsonlogger.json import JsonFormatter
|
|
|
10
10
|
from python_ddd_framework import ConfigureContext
|
|
11
11
|
|
|
12
12
|
from ..application_services.invocation import _InvocationRuntime
|
|
13
|
+
from ..invocation.module import InvocationModule
|
|
13
14
|
from ..lifecycle import PreInitializeContext, ShutdownContext
|
|
14
15
|
from ..modularity import AppModule
|
|
15
16
|
from .formatting import _DIAGNOSTIC_FIELDS, _SafeTextFormatter
|
|
@@ -65,6 +66,8 @@ class _SafeJsonFormatter(_SafeTextFormatter, JsonFormatter):
|
|
|
65
66
|
|
|
66
67
|
|
|
67
68
|
class StructuredLoggingModule(AppModule):
|
|
69
|
+
dependencies = (InvocationModule,)
|
|
70
|
+
|
|
68
71
|
def configure(self, context: ConfigureContext) -> None:
|
|
69
72
|
context.configure(LoggingOptions, section="logging")
|
|
70
73
|
|
|
@@ -12,6 +12,7 @@ from opentelemetry.sdk.trace import TracerProvider
|
|
|
12
12
|
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
13
13
|
|
|
14
14
|
from ..application_services.invocation import _InvocationRuntime
|
|
15
|
+
from ..invocation.module import InvocationModule
|
|
15
16
|
from ..lifecycle import ConfigureContext, PreInitializeContext, ShutdownContext
|
|
16
17
|
from ..modularity import AppModule
|
|
17
18
|
from ..options import Options
|
|
@@ -22,6 +23,8 @@ _LOGGER = logging.getLogger(__name__)
|
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
class TracingModule(AppModule):
|
|
26
|
+
dependencies = (InvocationModule,)
|
|
27
|
+
|
|
25
28
|
def __init__(self) -> None:
|
|
26
29
|
self._provider: TracerProvider | None = None
|
|
27
30
|
self._export_filter: _ExportDiagnosticFilter | None = None
|
|
@@ -60,3 +60,15 @@ class _OptionsContributor:
|
|
|
60
60
|
|
|
61
61
|
def _close(self) -> None:
|
|
62
62
|
self._active = False
|
|
63
|
+
|
|
64
|
+
def default(self, model_type: type[TOptions], *, section: str) -> None:
|
|
65
|
+
"""贡献能力默认选项;显式声明仍在全局阶段屏障后优先生效。"""
|
|
66
|
+
if not self._active:
|
|
67
|
+
raise OptionsContributionClosedError(option_kind=self._kind, stage=self._stage.value)
|
|
68
|
+
self._registry.declare_default(model_type, section, owner=self._owner)
|
|
69
|
+
|
|
70
|
+
def section(self, section: str) -> None:
|
|
71
|
+
"""登记由该能力自行验证的配置节,不生成第二套 Options 模型。"""
|
|
72
|
+
if not self._active:
|
|
73
|
+
raise OptionsContributionClosedError(option_kind=self._kind, stage=self._stage.value)
|
|
74
|
+
self._registry.declare_section(section)
|
|
@@ -10,7 +10,7 @@ from pydantic import BaseModel, ValidationError
|
|
|
10
10
|
|
|
11
11
|
from ..configuration.immutability import freeze_typed_value
|
|
12
12
|
from ..configuration.root import ConfigurationRoot
|
|
13
|
-
from ..configuration.values import MISSING, freeze_value, normalize_mapping
|
|
13
|
+
from ..configuration.values import MISSING, canonical_path, freeze_value, normalize_mapping
|
|
14
14
|
from ..diagnostics import LifecycleStage
|
|
15
15
|
from ..errors import (
|
|
16
16
|
DuplicateOptionDefinitionError,
|
|
@@ -42,7 +42,15 @@ from .validation import _validate_input
|
|
|
42
42
|
class _OptionsRegistry:
|
|
43
43
|
"""一次 Application build 的 definitions 与 append-only patch owner。"""
|
|
44
44
|
|
|
45
|
-
__slots__ = (
|
|
45
|
+
__slots__ = (
|
|
46
|
+
"_claims",
|
|
47
|
+
"_defaults",
|
|
48
|
+
"_definitions",
|
|
49
|
+
"_kind",
|
|
50
|
+
"_ordered",
|
|
51
|
+
"_patches",
|
|
52
|
+
"_sections",
|
|
53
|
+
)
|
|
46
54
|
|
|
47
55
|
def __init__(
|
|
48
56
|
self,
|
|
@@ -55,10 +63,15 @@ class _OptionsRegistry:
|
|
|
55
63
|
self._claims = claims
|
|
56
64
|
self._kind = kind
|
|
57
65
|
self._patches: list[_OptionPatch] = []
|
|
66
|
+
self._defaults: list[_OwnedOptionDefinition] = []
|
|
67
|
+
self._sections: set[str] = set()
|
|
58
68
|
|
|
59
69
|
@property
|
|
60
70
|
def sections(self) -> frozenset[str]:
|
|
61
|
-
return frozenset(owned.definition.section for owned in self._ordered)
|
|
71
|
+
return frozenset(owned.definition.section for owned in self._ordered) | self._sections
|
|
72
|
+
|
|
73
|
+
def declare_section(self, section: str) -> None:
|
|
74
|
+
self._sections.add(".".join(canonical_path(section)))
|
|
62
75
|
|
|
63
76
|
def mapping_paths(
|
|
64
77
|
self,
|
|
@@ -126,14 +139,20 @@ class _OptionsRegistry:
|
|
|
126
139
|
def declare_default(
|
|
127
140
|
self, model_type: type[BaseModel], section: str, *, owner: ModuleDescriptor
|
|
128
141
|
) -> None:
|
|
129
|
-
|
|
130
|
-
self.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
142
|
+
definition = _validate_definition(
|
|
143
|
+
_OptionDefinition(model_type, section, frozenset()), owner=owner, kind=self._kind
|
|
144
|
+
)
|
|
145
|
+
for existing in self._defaults:
|
|
146
|
+
if existing.definition.model_type is model_type:
|
|
147
|
+
raise DuplicateOptionDefinitionError(
|
|
148
|
+
model_type=model_type,
|
|
149
|
+
option_kind=self._kind,
|
|
150
|
+
first_owner=existing.owner.key,
|
|
151
|
+
second_owner=owner.key,
|
|
152
|
+
)
|
|
153
|
+
self._defaults.append(
|
|
154
|
+
_OwnedOptionDefinition(definition, owner, LifecycleStage.CONFIGURE, None)
|
|
155
|
+
)
|
|
137
156
|
|
|
138
157
|
def patch(
|
|
139
158
|
self,
|
|
@@ -174,10 +193,24 @@ class _OptionsRegistry:
|
|
|
174
193
|
def bind_options(self, configuration: ConfigurationRoot) -> OptionsSnapshot:
|
|
175
194
|
return OptionsSnapshot(self._bind(configuration), kind=self._kind)
|
|
176
195
|
|
|
196
|
+
def _apply_defaults(self) -> None:
|
|
197
|
+
# 默认声明归能力模块;等所有显式声明和覆盖完成后再补齐,不能依赖 configure 顺序抢占。
|
|
198
|
+
for default in self._defaults:
|
|
199
|
+
definition = default.definition
|
|
200
|
+
if definition.model_type not in self._definitions:
|
|
201
|
+
self.declare(
|
|
202
|
+
definition.model_type,
|
|
203
|
+
definition.section,
|
|
204
|
+
secret_paths=definition.secret_paths,
|
|
205
|
+
owner=default.owner,
|
|
206
|
+
stage=default.stage,
|
|
207
|
+
)
|
|
208
|
+
|
|
177
209
|
def _bind(
|
|
178
210
|
self,
|
|
179
211
|
configuration: ConfigurationRoot,
|
|
180
212
|
) -> Mapping[type[BaseModel], BaseModel]:
|
|
213
|
+
self._apply_defaults()
|
|
181
214
|
configuration = configuration._bind_input_keys(self.mapping_paths, sections=self.sections)
|
|
182
215
|
for patch in self._patches:
|
|
183
216
|
if patch.model_type not in self._definitions:
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
from .contracts import RealtimePublisher
|
|
4
4
|
from .diagnostics import RealtimeDeliveryReport, RealtimeSnapshot
|
|
5
|
-
from .messages import RealtimeEnvelope
|
|
5
|
+
from .messages import RealtimeEnvelope, RealtimeMessage
|
|
6
6
|
|
|
7
7
|
__all__ = (
|
|
8
8
|
"RealtimeDeliveryReport",
|
|
9
9
|
"RealtimeEnvelope",
|
|
10
|
+
"RealtimeMessage",
|
|
10
11
|
"RealtimePublisher",
|
|
11
12
|
"RealtimeSnapshot",
|
|
12
13
|
)
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from collections.abc import Mapping
|
|
6
5
|
from typing import Protocol, runtime_checkable
|
|
7
6
|
from uuid import UUID
|
|
8
7
|
|
|
9
8
|
from .diagnostics import RealtimeDeliveryReport
|
|
9
|
+
from .messages import RealtimeMessage
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
@runtime_checkable
|
|
@@ -14,15 +14,11 @@ class RealtimePublisher(Protocol):
|
|
|
14
14
|
async def send_to_connection(
|
|
15
15
|
self,
|
|
16
16
|
connection_id: UUID,
|
|
17
|
-
|
|
18
|
-
message_type: str,
|
|
19
|
-
payload: Mapping[str, object],
|
|
17
|
+
message: RealtimeMessage,
|
|
20
18
|
) -> RealtimeDeliveryReport: ...
|
|
21
19
|
|
|
22
20
|
async def send_to_user(
|
|
23
21
|
self,
|
|
24
22
|
user_id: UUID,
|
|
25
|
-
|
|
26
|
-
message_type: str,
|
|
27
|
-
payload: Mapping[str, object],
|
|
23
|
+
message: RealtimeMessage,
|
|
28
24
|
) -> RealtimeDeliveryReport: ...
|
|
@@ -9,15 +9,40 @@ from collections.abc import Mapping
|
|
|
9
9
|
from dataclasses import dataclass, field
|
|
10
10
|
from datetime import UTC, datetime
|
|
11
11
|
from types import MappingProxyType
|
|
12
|
-
from typing import Final, Literal
|
|
12
|
+
from typing import ClassVar, Final, Literal
|
|
13
13
|
from uuid import UUID, uuid4
|
|
14
14
|
|
|
15
|
+
from pydantic import BaseModel, ConfigDict
|
|
16
|
+
|
|
15
17
|
from ..invocation.contracts import _is_safe_correlation_id
|
|
18
|
+
from ..validation.models import _revalidate_model
|
|
16
19
|
|
|
17
20
|
_MESSAGE_TYPE_PATTERN = re.compile(r"^[a-z][a-z0-9_-]*(?:\.[a-z][a-z0-9_-]*)*\.v[1-9][0-9]*$")
|
|
18
21
|
_REALTIME_SCHEMA_VERSION: Final = 1
|
|
19
22
|
|
|
20
23
|
|
|
24
|
+
class RealtimeMessage(BaseModel):
|
|
25
|
+
"""业务消息类型拥有稳定名称与 payload schema;envelope 仍归传输边界。"""
|
|
26
|
+
|
|
27
|
+
model_config = ConfigDict(frozen=True)
|
|
28
|
+
message_type: ClassVar[str]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _message_envelope(message: RealtimeMessage, correlation_id: str) -> RealtimeEnvelope:
|
|
32
|
+
if not isinstance(message, RealtimeMessage):
|
|
33
|
+
raise TypeError("realtime message must be a RealtimeMessage")
|
|
34
|
+
message_type = getattr(type(message), "message_type", None)
|
|
35
|
+
if not isinstance(message_type, str) or _MESSAGE_TYPE_PATTERN.fullmatch(message_type) is None:
|
|
36
|
+
raise ValueError("realtime message_type must be a versioned dotted name")
|
|
37
|
+
# 即使没有在线接收者也重验;model_construct 和可变嵌套字段不能绕过 schema。
|
|
38
|
+
validated = _revalidate_model(type(message), message)
|
|
39
|
+
return RealtimeEnvelope(
|
|
40
|
+
message_type=message_type,
|
|
41
|
+
correlation_id=correlation_id,
|
|
42
|
+
payload=validated.model_dump(mode="json", by_alias=True, warnings="error"),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
21
46
|
@dataclass(frozen=True, slots=True)
|
|
22
47
|
class RealtimeEnvelope:
|
|
23
48
|
"""不可变、可序列化且不携带 transport/provider object 的消息。"""
|