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,142 @@
|
|
|
1
|
+
"""Application 内的有效模型图;共享 DTO、字段和原生泛型保持不变。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import types
|
|
6
|
+
from collections.abc import Mapping
|
|
7
|
+
from copy import copy
|
|
8
|
+
from functools import reduce
|
|
9
|
+
from operator import or_
|
|
10
|
+
from typing import Annotated, Any, ForwardRef, TypeAliasType, Union, get_args, get_origin
|
|
11
|
+
|
|
12
|
+
from pydantic import BaseModel, ConfigDict, Field, create_model, model_validator
|
|
13
|
+
from pydantic.fields import FieldInfo
|
|
14
|
+
|
|
15
|
+
from .contracts import ExtensibleModel, ExtensionProperties
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class _EffectiveModels:
|
|
19
|
+
def __init__(
|
|
20
|
+
self, properties: Mapping[type[ExtensibleModel], type[ExtensionProperties]]
|
|
21
|
+
) -> None:
|
|
22
|
+
self._properties: dict[type[BaseModel], type[ExtensionProperties]] = {
|
|
23
|
+
model: schema for model, schema in properties.items()
|
|
24
|
+
}
|
|
25
|
+
self._models: dict[type[BaseModel], type[BaseModel]] = {}
|
|
26
|
+
|
|
27
|
+
def annotation(self, annotation: object) -> object:
|
|
28
|
+
graph: dict[type[BaseModel], dict[str, FieldInfo]] = {}
|
|
29
|
+
self._collect(annotation, graph)
|
|
30
|
+
affected = set(self._properties) | self._models.keys()
|
|
31
|
+
# 先闭合依赖图,再创建所有前向引用,支持自递归及相互引用的普通 DTO。
|
|
32
|
+
while True:
|
|
33
|
+
parents = {
|
|
34
|
+
model
|
|
35
|
+
for model, fields in graph.items()
|
|
36
|
+
if any(_model_types(field.annotation) & affected for field in fields.values())
|
|
37
|
+
}
|
|
38
|
+
if parents <= affected:
|
|
39
|
+
break
|
|
40
|
+
affected |= parents
|
|
41
|
+
pending = [model for model in graph if model in affected]
|
|
42
|
+
symbols = {model: f"_pddd_model_{len(self._models) + i}" for i, model in enumerate(pending)}
|
|
43
|
+
replacements: dict[type[BaseModel], object] = {
|
|
44
|
+
**self._models,
|
|
45
|
+
**{model: ForwardRef(symbol) for model, symbol in symbols.items()},
|
|
46
|
+
}
|
|
47
|
+
for model in pending:
|
|
48
|
+
definitions: dict[str, Any] = {
|
|
49
|
+
name: (_replace(field.annotation, replacements), copy(field))
|
|
50
|
+
for name, field in graph[model].items()
|
|
51
|
+
}
|
|
52
|
+
self._models[model] = create_model(
|
|
53
|
+
model.__name__,
|
|
54
|
+
__base__=model,
|
|
55
|
+
__module__=model.__module__,
|
|
56
|
+
__config__=ConfigDict(
|
|
57
|
+
defer_build=True, revalidate_instances="always", validate_default=True
|
|
58
|
+
),
|
|
59
|
+
__validators__={"_pddd_effective_input": _instance_validator(model)},
|
|
60
|
+
**definitions,
|
|
61
|
+
)
|
|
62
|
+
namespace = {symbol: self._models[model] for model, symbol in symbols.items()}
|
|
63
|
+
for model in pending:
|
|
64
|
+
self._models[model].model_config["defer_build"] = False
|
|
65
|
+
for model in pending:
|
|
66
|
+
self._models[model].model_rebuild(_types_namespace=namespace)
|
|
67
|
+
return _replace(annotation, self._models)
|
|
68
|
+
|
|
69
|
+
def _collect(
|
|
70
|
+
self, annotation: object, graph: dict[type[BaseModel], dict[str, FieldInfo]]
|
|
71
|
+
) -> None:
|
|
72
|
+
for model in _model_types(annotation):
|
|
73
|
+
if model in graph or model in self._models:
|
|
74
|
+
continue
|
|
75
|
+
fields = dict(model.model_fields)
|
|
76
|
+
if model in self._properties:
|
|
77
|
+
properties = self._properties[model]
|
|
78
|
+
fields["extra_properties"] = FieldInfo.from_annotated_attribute(
|
|
79
|
+
properties, Field(default_factory=properties)
|
|
80
|
+
)
|
|
81
|
+
graph[model] = fields
|
|
82
|
+
for field in fields.values():
|
|
83
|
+
self._collect(field.annotation, graph)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _model_types(
|
|
87
|
+
annotation: object, aliases: frozenset[TypeAliasType] = frozenset()
|
|
88
|
+
) -> set[type[BaseModel]]:
|
|
89
|
+
if isinstance(annotation, type) and issubclass(annotation, BaseModel):
|
|
90
|
+
return {annotation}
|
|
91
|
+
if isinstance(annotation, TypeAliasType):
|
|
92
|
+
return (
|
|
93
|
+
set()
|
|
94
|
+
if annotation in aliases
|
|
95
|
+
else _model_types(annotation.__value__, aliases | {annotation})
|
|
96
|
+
)
|
|
97
|
+
args = get_args(annotation)
|
|
98
|
+
if get_origin(annotation) is Annotated:
|
|
99
|
+
args = args[:1]
|
|
100
|
+
return {model for arg in args for model in _model_types(arg, aliases)}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _replace(annotation: object, models: Mapping[type[BaseModel], object]) -> Any:
|
|
104
|
+
if isinstance(annotation, type) and issubclass(annotation, BaseModel):
|
|
105
|
+
return models.get(annotation, annotation)
|
|
106
|
+
if isinstance(annotation, TypeAliasType):
|
|
107
|
+
if not _model_types(annotation) & models.keys():
|
|
108
|
+
return annotation
|
|
109
|
+
updated = _replace(annotation.__value__, models)
|
|
110
|
+
return annotation if updated == annotation.__value__ else updated
|
|
111
|
+
args = get_args(annotation)
|
|
112
|
+
if not args:
|
|
113
|
+
return annotation
|
|
114
|
+
origin = get_origin(annotation)
|
|
115
|
+
if origin is Annotated:
|
|
116
|
+
return Annotated[_replace(args[0], models), *args[1:]]
|
|
117
|
+
updated_args = tuple(_replace(item, models) for item in args)
|
|
118
|
+
if updated_args == args:
|
|
119
|
+
return annotation
|
|
120
|
+
if origin in (Union, types.UnionType):
|
|
121
|
+
return reduce(or_, updated_args)
|
|
122
|
+
if isinstance(origin, TypeAliasType):
|
|
123
|
+
return origin[updated_args]
|
|
124
|
+
if isinstance(origin, type):
|
|
125
|
+
return types.GenericAlias(origin, updated_args)
|
|
126
|
+
raise TypeError("unsupported extension annotation container")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _instance_validator(original: type[BaseModel]) -> Any:
|
|
130
|
+
# Pydantic 泛型会接受同一 origin 的未参数化/不同参数化实例,再重验字段。
|
|
131
|
+
origin = original.__pydantic_generic_metadata__["origin"] or original
|
|
132
|
+
|
|
133
|
+
def accept_instance(value: object) -> object:
|
|
134
|
+
if isinstance(value, origin):
|
|
135
|
+
# 保留 Python 原值而非 JSON dump;strict 类型、嵌套实例和未知字段都须继续重验。
|
|
136
|
+
return {
|
|
137
|
+
**{name: getattr(value, name) for name in type(value).model_fields},
|
|
138
|
+
**(value.model_extra or {}),
|
|
139
|
+
}
|
|
140
|
+
return value
|
|
141
|
+
|
|
142
|
+
return model_validator(mode="before")(accept_instance)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""在方法编译前组装有效 DTO;Provider 只在正式 bind 阶段贡献。"""
|
|
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 ExtensionCatalog
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ExtensionsModule(AppModule):
|
|
12
|
+
catalog: ExtensionCatalog
|
|
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 = ExtensionCatalog(context.modules)
|
|
19
|
+
provider = Provider()
|
|
20
|
+
provider.provide(lambda: self.catalog, provides=ExtensionCatalog, scope=Scope.APP)
|
|
21
|
+
context.add_provider(provider)
|
|
@@ -15,6 +15,7 @@ from .filters import ActionFilterContext, ServiceFilter, service_filter
|
|
|
15
15
|
from .http import remote_service
|
|
16
16
|
from .http_router import HttpRouter
|
|
17
17
|
from .middleware import DiMiddleware
|
|
18
|
+
from .module import FastApiModule
|
|
18
19
|
from .realtime import (
|
|
19
20
|
FastApiRealtimeModule,
|
|
20
21
|
FastApiRealtimeOptions,
|
|
@@ -29,6 +30,7 @@ __all__ = (
|
|
|
29
30
|
"DiMiddleware",
|
|
30
31
|
"FastApiAdapter",
|
|
31
32
|
"FastApiAdapterError",
|
|
33
|
+
"FastApiModule",
|
|
32
34
|
"FastApiRealtimeModule",
|
|
33
35
|
"FastApiRealtimeOptions",
|
|
34
36
|
"HttpRouter",
|
|
@@ -14,11 +14,10 @@ from anyio import CancelScope
|
|
|
14
14
|
from dishka import AsyncContainer
|
|
15
15
|
from starlette.requests import Request
|
|
16
16
|
|
|
17
|
-
from ..application_services.contracts import _DEFAULT_CONNECTION_NAME
|
|
18
17
|
from ..application_services.invocation import _InvocationRuntime, _InvocationState, _managed_action
|
|
19
18
|
from ..invocation import ApplicationInvocationContext
|
|
20
19
|
from ..unit_of_work import UnitOfWork, UnitOfWorkManager
|
|
21
|
-
from ..unit_of_work.options import UnitOfWorkDeclaration
|
|
20
|
+
from ..unit_of_work.options import _DEFAULT_CONNECTION_NAME, UnitOfWorkDeclaration
|
|
22
21
|
from .errors import FastApiAdapterError
|
|
23
22
|
from .filters import ActionFilterContext, ServiceFilter, _run_filters
|
|
24
23
|
from .transfer import _manage_stream
|
|
@@ -25,6 +25,7 @@ from .contracts import RouterCatalog
|
|
|
25
25
|
from .errors import FastApiAdapterError
|
|
26
26
|
from .filters import ServiceFilter, _validate_filters
|
|
27
27
|
from .middleware import DiMiddleware, _compile_middlewares
|
|
28
|
+
from .module import FastApiModule
|
|
28
29
|
from .realtime.runtime import _FastApiRealtimeRuntime
|
|
29
30
|
from .request_context import (
|
|
30
31
|
_RequestContextMiddleware,
|
|
@@ -82,6 +83,12 @@ class FastApiAdapter:
|
|
|
82
83
|
self._api_prefix = api_prefix
|
|
83
84
|
if not isinstance(application, Application):
|
|
84
85
|
raise TypeError("application must be python_ddd_framework.Application")
|
|
86
|
+
# 安装只能使用已组成的能力,不能在 build 后临时补注册或隐式启动调用运行时。
|
|
87
|
+
application.modules.require_dependency(
|
|
88
|
+
application.modules.startup.key,
|
|
89
|
+
FastApiModule,
|
|
90
|
+
source=application.modules.startup.declaration_source,
|
|
91
|
+
)
|
|
85
92
|
self._filters = tuple(filters)
|
|
86
93
|
if upload_limits is not None and not isinstance(upload_limits, UploadLimits):
|
|
87
94
|
raise TypeError("upload_limits must be UploadLimits")
|
|
@@ -300,6 +307,15 @@ async def _business_error_handler(request: Request, error: Exception) -> JSONRes
|
|
|
300
307
|
"code": business_error.definition.code,
|
|
301
308
|
"message": business_error.definition.message,
|
|
302
309
|
"fields": [field.name for field in business_error.fields],
|
|
310
|
+
**(
|
|
311
|
+
{
|
|
312
|
+
"data": business_error.data.model_dump(
|
|
313
|
+
mode="json", by_alias=True, warnings="error"
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
if business_error.data is not None
|
|
317
|
+
else {}
|
|
318
|
+
),
|
|
303
319
|
}
|
|
304
320
|
},
|
|
305
321
|
)
|
|
@@ -10,15 +10,18 @@ from dataclasses import dataclass, is_dataclass
|
|
|
10
10
|
from datetime import date, datetime, time
|
|
11
11
|
from decimal import Decimal
|
|
12
12
|
from enum import Enum
|
|
13
|
-
from typing import Annotated, Any, Literal, Union, cast, get_args, get_origin
|
|
13
|
+
from typing import Annotated, Any, Literal, Union, cast, get_args, get_origin
|
|
14
14
|
from uuid import UUID
|
|
15
15
|
|
|
16
16
|
from dishka import AsyncContainer
|
|
17
|
+
from dishka.integrations.base import default_parse_dependency
|
|
17
18
|
from dishka.integrations.fastapi import DishkaRoute, FromDishka
|
|
18
19
|
from fastapi import APIRouter, Depends
|
|
19
|
-
from fastapi.params import Body, Param, Path
|
|
20
|
+
from fastapi.params import Body, Param, Path, Query
|
|
21
|
+
from fastapi.params import Depends as DependsParameter
|
|
20
22
|
from fastapi.security import HTTPBearer
|
|
21
23
|
from pydantic import BaseModel
|
|
24
|
+
from pydantic.fields import FieldInfo
|
|
22
25
|
from starlette.requests import Request
|
|
23
26
|
from starlette.responses import Response
|
|
24
27
|
from starlette.routing import compile_path
|
|
@@ -27,9 +30,11 @@ from ..application import Application
|
|
|
27
30
|
from ..application_services import (
|
|
28
31
|
ApplicationServiceRegistration,
|
|
29
32
|
)
|
|
30
|
-
from ..application_services.catalog import _CompiledApplicationService
|
|
33
|
+
from ..application_services.catalog import _CompiledApplicationService
|
|
31
34
|
from ..application_services.dispatcher import _ApplicationServiceDispatcher
|
|
35
|
+
from ..application_services.module import ApplicationServicesModule
|
|
32
36
|
from ..diagnostics import SourceLocation
|
|
37
|
+
from ..invocation.methods import _CompiledMethod
|
|
33
38
|
from ..modularity import AppModule, ModuleDescriptor, ModuleKey
|
|
34
39
|
from .action import _HttpActionOwner, _request_parameter
|
|
35
40
|
from .contracts import (
|
|
@@ -184,15 +189,19 @@ def _create_exposure_router(
|
|
|
184
189
|
registrations = tuple(
|
|
185
190
|
registration
|
|
186
191
|
for target in target_descriptors
|
|
187
|
-
for registration in application.
|
|
188
|
-
|
|
192
|
+
for registration in application.modules.instance(
|
|
193
|
+
ApplicationServicesModule
|
|
194
|
+
).catalog.registrations
|
|
195
|
+
if registration.owner == target.key
|
|
189
196
|
)
|
|
190
197
|
overrides = _index_overrides(exposure, registrations)
|
|
191
198
|
router = APIRouter(route_class=_ApplicationServiceRoute)
|
|
192
199
|
for registration in registrations:
|
|
193
200
|
if registration.is_integration_service:
|
|
194
201
|
continue
|
|
195
|
-
compiled_service = application.
|
|
202
|
+
compiled_service = application.modules.instance(
|
|
203
|
+
ApplicationServicesModule
|
|
204
|
+
).catalog._for_registration(registration)
|
|
196
205
|
service_override = overrides.get((registration.contract, None))
|
|
197
206
|
if service_override is not None:
|
|
198
207
|
continue
|
|
@@ -218,8 +227,13 @@ def _create_exposure_router(
|
|
|
218
227
|
)
|
|
219
228
|
route_method = _inspect_route_method(
|
|
220
229
|
registration.contract,
|
|
221
|
-
compiled_method.implementation_method,
|
|
222
230
|
method_registration.name,
|
|
231
|
+
compiled_method.implementation_signature,
|
|
232
|
+
)
|
|
233
|
+
verb = projection.pop("verb", _conventional_verb(method_registration.name))
|
|
234
|
+
route_method = _RouteMethod(
|
|
235
|
+
route_method.signature,
|
|
236
|
+
tuple(_query_model_parameter(p, verb) for p in route_method.parameters),
|
|
223
237
|
)
|
|
224
238
|
declared_path = projection.pop("path", None)
|
|
225
239
|
if declared_path is not None and provenance["path"] == "method":
|
|
@@ -239,7 +253,6 @@ def _create_exposure_router(
|
|
|
239
253
|
application._container_for_integration(),
|
|
240
254
|
infer_services=False,
|
|
241
255
|
)
|
|
242
|
-
verb = projection.pop("verb", _conventional_verb(method_registration.name))
|
|
243
256
|
operation_id = projection.pop(
|
|
244
257
|
"operation_id", _operation_id(registration.contract, method_registration.name)
|
|
245
258
|
)
|
|
@@ -362,27 +375,13 @@ def _index_overrides(
|
|
|
362
375
|
|
|
363
376
|
def _inspect_route_method(
|
|
364
377
|
contract: type[object],
|
|
365
|
-
method: Callable[..., Awaitable[object]],
|
|
366
378
|
method_name: str,
|
|
379
|
+
signature: inspect.Signature,
|
|
367
380
|
) -> _RouteMethod:
|
|
368
381
|
"""在真实 FastAPI app 变更前解析并收窄动态 typing boundary。"""
|
|
369
382
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
except (NameError, TypeError) as original_error:
|
|
373
|
-
raise FastApiAdapterError(
|
|
374
|
-
f"HTTP method annotations cannot be resolved for {contract.__module__}."
|
|
375
|
-
f"{contract.__qualname__}.{method_name}: {type(original_error).__name__}"
|
|
376
|
-
) from original_error
|
|
377
|
-
signature = inspect.signature(method)
|
|
378
|
-
resolved_parameters = tuple(
|
|
379
|
-
parameter.replace(annotation=annotations.get(name, parameter.annotation))
|
|
380
|
-
for name, parameter in signature.parameters.items()
|
|
381
|
-
)
|
|
382
|
-
signature = signature.replace(
|
|
383
|
-
parameters=resolved_parameters,
|
|
384
|
-
return_annotation=annotations.get("return", signature.return_annotation),
|
|
385
|
-
)
|
|
383
|
+
# 共享方法编译已解释本 Application 的扩展 DTO;HTTP 不另建字段权威。
|
|
384
|
+
resolved_parameters = tuple(signature.parameters.values())
|
|
386
385
|
parameters = resolved_parameters[1:]
|
|
387
386
|
for parameter in parameters:
|
|
388
387
|
if parameter.kind in (
|
|
@@ -407,6 +406,25 @@ def _inspect_route_method(
|
|
|
407
406
|
return _RouteMethod(signature=signature, parameters=parameters)
|
|
408
407
|
|
|
409
408
|
|
|
409
|
+
def _query_model_parameter(parameter: inspect.Parameter, verb: str) -> inspect.Parameter:
|
|
410
|
+
hint = parameter.annotation
|
|
411
|
+
metadata = get_args(hint)[1:] if get_origin(hint) is Annotated else ()
|
|
412
|
+
model = get_args(hint)[0] if metadata else hint
|
|
413
|
+
if (
|
|
414
|
+
verb == "GET"
|
|
415
|
+
and isinstance(model, type)
|
|
416
|
+
and issubclass(model, BaseModel)
|
|
417
|
+
and not any(
|
|
418
|
+
isinstance(value, (FieldInfo, DependsParameter))
|
|
419
|
+
for value in (*metadata, parameter.default)
|
|
420
|
+
)
|
|
421
|
+
and default_parse_dependency(parameter, hint) is None
|
|
422
|
+
):
|
|
423
|
+
# 仅补原生 Query 元数据;字段展开、校验和 OpenAPI 均交给 FastAPI/Pydantic。
|
|
424
|
+
return parameter.replace(annotation=Annotated[hint, Query()])
|
|
425
|
+
return parameter
|
|
426
|
+
|
|
427
|
+
|
|
410
428
|
def _uses_non_path_binding(parameter: inspect.Parameter) -> bool:
|
|
411
429
|
metadata = (
|
|
412
430
|
get_args(parameter.annotation)[1:] if get_origin(parameter.annotation) is Annotated else ()
|
|
@@ -4,6 +4,7 @@ from ..background_execution.management import BackgroundManagementApplicationSer
|
|
|
4
4
|
from ..background_execution.module import BackgroundManagementModule
|
|
5
5
|
from ..modularity import AppModule
|
|
6
6
|
from .contracts import ApplicationServiceRouteOverride, exposes_application_services
|
|
7
|
+
from .module import FastApiModule
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
@exposes_application_services(
|
|
@@ -39,4 +40,4 @@ from .contracts import ApplicationServiceRouteOverride, exposes_application_serv
|
|
|
39
40
|
),
|
|
40
41
|
)
|
|
41
42
|
class BackgroundManagementHttpApiModule(AppModule):
|
|
42
|
-
dependencies = (
|
|
43
|
+
dependencies = (FastApiModule,)
|
|
@@ -10,7 +10,7 @@ from fastapi import APIRouter
|
|
|
10
10
|
|
|
11
11
|
from ..application_services import ApplicationService, ApplicationServiceContract
|
|
12
12
|
from ..diagnostics import SourceLocation
|
|
13
|
-
from ..modularity import AppModule, ModuleKey
|
|
13
|
+
from ..modularity import AppModule, ModuleDeclaration, ModuleDependency, ModuleKey, ModuleRef
|
|
14
14
|
from .errors import FastApiAdapterError
|
|
15
15
|
|
|
16
16
|
_ROUTER_METADATA_ATTRIBUTE = "__python_ddd_framework_fastapi_routers__"
|
|
@@ -139,11 +139,24 @@ class ApplicationServiceRouteOverride:
|
|
|
139
139
|
|
|
140
140
|
|
|
141
141
|
@dataclass(frozen=True, slots=True)
|
|
142
|
-
class _ApplicationServiceExposure:
|
|
142
|
+
class _ApplicationServiceExposure(ModuleDeclaration):
|
|
143
143
|
target_modules: tuple[type[AppModule], ...]
|
|
144
144
|
root_path: str
|
|
145
145
|
overrides: tuple[ApplicationServiceRouteOverride, ...]
|
|
146
146
|
|
|
147
|
+
@property
|
|
148
|
+
def required_module(self) -> ModuleDependency:
|
|
149
|
+
return ModuleRef("python_ddd_framework.fastapi.module:FastApiModule")
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@dataclass(frozen=True, slots=True)
|
|
153
|
+
class _RouterDeclarations(ModuleDeclaration):
|
|
154
|
+
routers: tuple[APIRouter, ...]
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def required_module(self) -> ModuleDependency:
|
|
158
|
+
return ModuleRef("python_ddd_framework.fastapi.module:FastApiModule")
|
|
159
|
+
|
|
147
160
|
|
|
148
161
|
def contributes_routers(*routers: APIRouter) -> Callable[[_ModuleType], _ModuleType]:
|
|
149
162
|
"""把 immutable router tuple 附着到当前 Module class,不建立 global registry。"""
|
|
@@ -161,7 +174,7 @@ def contributes_routers(*routers: APIRouter) -> Callable[[_ModuleType], _ModuleT
|
|
|
161
174
|
raise FastApiAdapterError(
|
|
162
175
|
f"{module_type.__module__}:{module_type.__qualname__} already declares routers"
|
|
163
176
|
)
|
|
164
|
-
setattr(module_type, _ROUTER_METADATA_ATTRIBUTE, routers)
|
|
177
|
+
setattr(module_type, _ROUTER_METADATA_ATTRIBUTE, _RouterDeclarations(routers))
|
|
165
178
|
return module_type
|
|
166
179
|
|
|
167
180
|
return decorate
|
|
@@ -172,7 +185,7 @@ def exposes_application_services(
|
|
|
172
185
|
root_path: str = "",
|
|
173
186
|
overrides: tuple[ApplicationServiceRouteOverride, ...] = (),
|
|
174
187
|
) -> Callable[[_ModuleType], _ModuleType]:
|
|
175
|
-
"""
|
|
188
|
+
"""选择公开服务并贡献正式 Module 依赖,由统一图处理去重、环和排序。"""
|
|
176
189
|
|
|
177
190
|
if not target_modules:
|
|
178
191
|
raise FastApiAdapterError(
|
|
@@ -208,22 +221,27 @@ def exposes_application_services(
|
|
|
208
221
|
f"{module_type.__module__}:{module_type.__qualname__} already declares "
|
|
209
222
|
"application service exposure"
|
|
210
223
|
)
|
|
224
|
+
if not isinstance(module_type.dependencies, tuple):
|
|
225
|
+
raise FastApiAdapterError("dependencies metadata must be an immutable tuple")
|
|
211
226
|
setattr(module_type, _APPLICATION_SERVICE_EXPOSURE_ATTRIBUTE, exposure)
|
|
227
|
+
module_type.dependencies = (*module_type.dependencies, *target_modules)
|
|
212
228
|
return module_type
|
|
213
229
|
|
|
214
230
|
return decorate
|
|
215
231
|
|
|
216
232
|
|
|
217
233
|
def _routers_for(module_type: type[AppModule]) -> tuple[APIRouter, ...]:
|
|
218
|
-
raw_metadata = vars(module_type).get(_ROUTER_METADATA_ATTRIBUTE
|
|
219
|
-
if
|
|
220
|
-
|
|
234
|
+
raw_metadata = vars(module_type).get(_ROUTER_METADATA_ATTRIBUTE)
|
|
235
|
+
if raw_metadata is None:
|
|
236
|
+
return ()
|
|
237
|
+
if not isinstance(raw_metadata, _RouterDeclarations) or any(
|
|
238
|
+
not isinstance(router, APIRouter) for router in raw_metadata.routers
|
|
221
239
|
):
|
|
222
240
|
raise FastApiAdapterError(
|
|
223
241
|
f"{module_type.__module__}:{module_type.__qualname__} router metadata "
|
|
224
242
|
"must remain an immutable tuple of APIRouter instances"
|
|
225
243
|
)
|
|
226
|
-
return raw_metadata
|
|
244
|
+
return raw_metadata.routers
|
|
227
245
|
|
|
228
246
|
|
|
229
247
|
def _application_service_exposure_for(
|
|
@@ -20,7 +20,7 @@ def _create_health_router(application: Application) -> APIRouter:
|
|
|
20
20
|
return JSONResponse(status_code=200, content={"status": "live"})
|
|
21
21
|
|
|
22
22
|
async def ready() -> JSONResponse:
|
|
23
|
-
is_ready = application.
|
|
23
|
+
is_ready = application.is_ready
|
|
24
24
|
return JSONResponse(
|
|
25
25
|
status_code=200 if is_ready else 503,
|
|
26
26
|
content={"status": "ready" if is_ready else "not_ready"},
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""HTTP 调用能力与原生 FastAPI DI 集成的组装 owner。"""
|
|
2
|
+
|
|
3
|
+
from dishka.integrations.fastapi import FastapiProvider
|
|
4
|
+
|
|
5
|
+
from ..application_services.module import ApplicationServicesModule
|
|
6
|
+
from ..lifecycle import ConfigureContext
|
|
7
|
+
from ..modularity import AppModule
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FastApiModule(AppModule):
|
|
11
|
+
dependencies = (ApplicationServicesModule,)
|
|
12
|
+
|
|
13
|
+
def configure(self, context: ConfigureContext) -> None:
|
|
14
|
+
context.services.contribute(FastapiProvider(), reason="Native FastAPI request context")
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import inspect
|
|
6
|
-
from typing import Annotated,
|
|
6
|
+
from typing import Annotated, cast, get_args, get_origin
|
|
7
7
|
|
|
8
8
|
from dishka.integrations.base import default_parse_dependency
|
|
9
9
|
from fastapi.dependencies.models import Dependant
|
|
@@ -13,23 +13,8 @@ from pydantic import ConfigDict, Field, TypeAdapter, ValidationError
|
|
|
13
13
|
from pydantic.fields import FieldInfo
|
|
14
14
|
from pydantic_core import CoreSchema, SchemaValidator
|
|
15
15
|
|
|
16
|
-
from ..
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def _revalidation_schema(value: Any) -> Any:
|
|
20
|
-
# 只复制 schema 的容器,不复制模型 class、validator 或默认业务对象。
|
|
21
|
-
# 显式覆盖每个嵌套模型/数据类,不能改变业务模型的全局配置。
|
|
22
|
-
if isinstance(value, dict):
|
|
23
|
-
result = {
|
|
24
|
-
key: item if key in ("default", "metadata", "config") else _revalidation_schema(item)
|
|
25
|
-
for key, item in value.items()
|
|
26
|
-
}
|
|
27
|
-
if result.get("type") in ("model", "dataclass"):
|
|
28
|
-
result["revalidate_instances"] = "always"
|
|
29
|
-
return result
|
|
30
|
-
if isinstance(value, list):
|
|
31
|
-
return [_revalidation_schema(item) for item in value]
|
|
32
|
-
return value
|
|
16
|
+
from ..invocation.execution import _InvocationArguments
|
|
17
|
+
from ..validation.models import _revalidation_schema
|
|
33
18
|
|
|
34
19
|
|
|
35
20
|
class _HttpParameterValidator:
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
-
from collections.abc import Awaitable, Callable
|
|
6
|
+
from collections.abc import Awaitable, Callable
|
|
7
7
|
from enum import IntEnum
|
|
8
8
|
from typing import TYPE_CHECKING, Concatenate, ParamSpec, TypeVar, cast
|
|
9
9
|
from uuid import UUID
|
|
@@ -19,18 +19,23 @@ from ...application_services import (
|
|
|
19
19
|
from ...application_services.invocation import _ScopedApplicationServiceInvoker
|
|
20
20
|
from ...authorization import CurrentUser, PermissionDeniedError, UnauthenticatedError
|
|
21
21
|
from ...errors import FrameworkError, ResourceConflictError, ResourceNotFoundError
|
|
22
|
-
from ...realtime import RealtimeDeliveryReport
|
|
22
|
+
from ...realtime import RealtimeDeliveryReport, RealtimeMessage
|
|
23
23
|
|
|
24
24
|
if TYPE_CHECKING:
|
|
25
25
|
from .runtime import _ConnectionState, _FastApiRealtimeRuntime
|
|
26
26
|
|
|
27
|
-
_FRAMEWORK_PROBLEM_MESSAGE_TYPE = "framework.problem.v1"
|
|
28
27
|
_TModel = TypeVar("_TModel", bound=BaseModel)
|
|
29
28
|
_TContract = TypeVar("_TContract", bound=ApplicationServiceContract)
|
|
30
29
|
_P = ParamSpec("_P")
|
|
31
30
|
_R = TypeVar("_R")
|
|
32
31
|
|
|
33
32
|
|
|
33
|
+
class _FrameworkProblemMessage(RealtimeMessage):
|
|
34
|
+
message_type = "framework.problem.v1"
|
|
35
|
+
code: str
|
|
36
|
+
status: int
|
|
37
|
+
|
|
38
|
+
|
|
34
39
|
class _WebSocketCloseCode(IntEnum):
|
|
35
40
|
NORMAL = 1000
|
|
36
41
|
UNSUPPORTED_FRAME = 1003
|
|
@@ -136,13 +141,11 @@ class WebSocketConnection:
|
|
|
136
141
|
|
|
137
142
|
async def send(
|
|
138
143
|
self,
|
|
139
|
-
|
|
140
|
-
payload: Mapping[str, object],
|
|
144
|
+
message: RealtimeMessage,
|
|
141
145
|
) -> RealtimeDeliveryReport:
|
|
142
146
|
return await self._runtime.send_to_connection(
|
|
143
147
|
self.id,
|
|
144
|
-
|
|
145
|
-
payload=payload,
|
|
148
|
+
message,
|
|
146
149
|
)
|
|
147
150
|
|
|
148
151
|
async def send_problem(self, error: FrameworkError) -> RealtimeDeliveryReport:
|
|
@@ -7,11 +7,14 @@ from dishka import Provider, Scope
|
|
|
7
7
|
from ...lifecycle import ConfigureContext, InitializeContext, ShutdownContext
|
|
8
8
|
from ...modularity import AppModule
|
|
9
9
|
from ...realtime import RealtimePublisher
|
|
10
|
+
from ..module import FastApiModule
|
|
10
11
|
from .options import FastApiRealtimeOptions
|
|
11
12
|
from .runtime import _FastApiRealtimeRuntime, _realtime_publisher
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class FastApiRealtimeModule(AppModule):
|
|
16
|
+
dependencies = (FastApiModule,)
|
|
17
|
+
|
|
15
18
|
def __init__(self) -> None:
|
|
16
19
|
self._runtime: _FastApiRealtimeRuntime | None = None
|
|
17
20
|
|