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.
Files changed (334) hide show
  1. python_ddd_framework/__init__.py +139 -21
  2. python_ddd_framework/application/builder.py +23 -78
  3. python_ddd_framework/application/composition.py +4 -22
  4. python_ddd_framework/application/runtime.py +96 -291
  5. python_ddd_framework/application/state.py +7 -75
  6. python_ddd_framework/application_services/__init__.py +5 -21
  7. python_ddd_framework/{services/application_bindings.py → application_services/bindings.py} +52 -9
  8. python_ddd_framework/application_services/catalog.py +36 -288
  9. python_ddd_framework/application_services/contracts.py +14 -13
  10. python_ddd_framework/application_services/dispatcher.py +19 -269
  11. python_ddd_framework/application_services/invocation.py +126 -7
  12. python_ddd_framework/application_services/module.py +168 -0
  13. python_ddd_framework/application_services/pagination.py +21 -0
  14. python_ddd_framework/auditing/control.py +1 -1
  15. python_ddd_framework/auditing/module.py +15 -0
  16. python_ddd_framework/authorization/__init__.py +2 -0
  17. python_ddd_framework/authorization/contracts.py +7 -2
  18. python_ddd_framework/authorization/definitions.py +5 -0
  19. python_ddd_framework/authorization/module.py +40 -0
  20. python_ddd_framework/authorization/options.py +7 -0
  21. python_ddd_framework/background_execution/child.py +13 -3
  22. python_ddd_framework/background_execution/declarations.py +45 -0
  23. python_ddd_framework/background_execution/lifecycle.py +12 -0
  24. python_ddd_framework/background_execution/module.py +107 -1
  25. python_ddd_framework/background_jobs/catalog.py +59 -42
  26. python_ddd_framework/background_jobs/contracts.py +16 -19
  27. python_ddd_framework/background_jobs/pgqueuer/enqueue.py +4 -0
  28. python_ddd_framework/background_jobs/pgqueuer/module.py +2 -1
  29. python_ddd_framework/background_jobs/pgqueuer/runtime.py +7 -7
  30. python_ddd_framework/background_workers/catalog.py +23 -36
  31. python_ddd_framework/background_workers/contracts.py +3 -0
  32. python_ddd_framework/background_workers/execution.py +1 -1
  33. python_ddd_framework/background_workers/runtime.py +38 -1
  34. python_ddd_framework/caching/__init__.py +7 -6
  35. python_ddd_framework/caching/contracts.py +65 -67
  36. python_ddd_framework/caching/errors.py +0 -7
  37. python_ddd_framework/caching/module.py +18 -0
  38. python_ddd_framework/caching/naming.py +29 -0
  39. python_ddd_framework/caching/options.py +38 -0
  40. python_ddd_framework/caching/unit_of_work.py +46 -0
  41. python_ddd_framework/cli/__init__.py +31 -6
  42. python_ddd_framework/cli/project.py +68 -6
  43. python_ddd_framework/cli/runtime.py +1 -1
  44. python_ddd_framework/developer_kit/generation.py +75 -22
  45. python_ddd_framework/developer_kit/project_metadata.py +2 -4
  46. python_ddd_framework/developer_kit/templates/basic/cookiecutter.json +4 -0
  47. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/README.md +45 -0
  48. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/__init__.py.jinja +0 -0
  49. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/contracts/__init__.py.jinja +0 -0
  50. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/contracts/conversion_service.py.jinja +7 -0
  51. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/module.py.jinja +38 -0
  52. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/services/__init__.py.jinja +0 -0
  53. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/services/default_conversion_service.py.jinja +10 -0
  54. python_ddd_framework/developer_kit/templates/module/cookiecutter.json +1 -1
  55. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/README.md +41 -49
  56. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/__init__.py.jinja +1 -0
  57. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/__init__.py.jinja +1 -0
  58. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/handler.py.jinja +45 -0
  59. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/payload.py.jinja +8 -0
  60. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/__init__.py.jinja +1 -0
  61. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/handler.py.jinja +35 -0
  62. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/payload.py.jinja +5 -0
  63. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/schedule.py.jinja +15 -0
  64. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/__init__.py.jinja +1 -0
  65. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/order_maintenance_worker.py.jinja +28 -0
  66. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/order_statistics_worker.py.jinja +26 -0
  67. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/__init__.py.jinja +1 -0
  68. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/order_cache.py.jinja +9 -0
  69. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/statistics_cache.py.jinja +9 -0
  70. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/event_handlers/__init__.py.jinja +1 -0
  71. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/event_handlers/order_changed_handler.py.jinja +23 -0
  72. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/__init__.py.jinja +1 -0
  73. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/{integration.py.jinja → hosted_services/order_integration_service.py.jinja} +8 -2
  74. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/order_observation_handler.py.jinja +16 -0
  75. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration_services/__init__.py.jinja +1 -0
  76. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration_services/order_reporting_service.py.jinja +16 -0
  77. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/interceptors/__init__.py.jinja +1 -0
  78. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/interceptors/order_timing_interceptor.py.jinja +17 -0
  79. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/module.py.jinja +105 -10
  80. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/options/__init__.py.jinja +1 -0
  81. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/{options.py.jinja → options/order_options.py.jinja} +1 -1
  82. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/__init__.py.jinja +1 -0
  83. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_approval_service.py.jinja +65 -0
  84. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_management_service.py.jinja +35 -0
  85. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_query_service.py.jinja +41 -0
  86. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/__init__.py.jinja +1 -0
  87. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/approval_setting_observer.py.jinja +17 -0
  88. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/__init__.py.jinja +1 -0
  89. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/approve_order.py.jinja +6 -0
  90. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/create_order.py.jinja +7 -0
  91. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/integration_services/__init__.py.jinja +1 -0
  92. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/integration_services/order_reporting_service.py.jinja +7 -0
  93. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/module.py.jinja +39 -2
  94. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/__init__.py.jinja +1 -0
  95. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_approval_service.py.jinja +13 -0
  96. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_management_service.py.jinja +10 -0
  97. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_query_service.py.jinja +12 -0
  98. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/__init__.py.jinja +1 -0
  99. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/order_statistics_snapshot.py.jinja +9 -0
  100. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/order_view.py.jinja +12 -0
  101. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/entities/__init__.py.jinja +1 -0
  102. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/{orders.py.jinja → entities/order.py.jinja} +10 -18
  103. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/events/__init__.py.jinja +1 -0
  104. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/events/order_changed.py.jinja +13 -0
  105. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/module.py.jinja +42 -3
  106. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repositories/__init__.py.jinja +1 -0
  107. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repositories/order_repository.py.jinja +17 -0
  108. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding/__init__.py.jinja +1 -0
  109. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding/order_seed_contributor.py.jinja +22 -0
  110. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/services/__init__.py.jinja +1 -0
  111. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/services/order_approval_service.py.jinja +23 -0
  112. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/settings/__init__.py.jinja +1 -0
  113. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/{settings.py.jinja → settings/approval_settings.py.jinja} +3 -2
  114. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/value_objects/__init__.py.jinja +1 -0
  115. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/value_objects/order_title.py.jinja +10 -0
  116. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/constants/__init__.py.jinja +1 -0
  117. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/constants/order_constants.py.jinja +3 -0
  118. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/enums/__init__.py.jinja +1 -0
  119. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/enums/order_status.py.jinja +6 -0
  120. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/errors/__init__.py.jinja +1 -0
  121. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/errors/order_errors.py.jinja +11 -0
  122. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/__init__.py.jinja +1 -0
  123. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/order_messages.py.jinja +26 -0
  124. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/order_observation.py.jinja +13 -0
  125. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/module.py.jinja +40 -1
  126. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions/__init__.py.jinja +1 -0
  127. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/{permissions.py.jinja → permissions/order_permission_provider.py.jinja} +6 -2
  128. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions/order_permissions.py.jinja +7 -0
  129. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/value_objects/__init__.py.jinja +1 -0
  130. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/value_objects/money.py.jinja +18 -0
  131. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/filters/__init__.py.jinja +1 -0
  132. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/filters/export_filter.py.jinja +15 -0
  133. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/models/__init__.py.jinja +1 -0
  134. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/models/refresh_orders.py.jinja +5 -0
  135. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/module.py.jinja +85 -7
  136. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/routers/__init__.py.jinja +1 -0
  137. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/{files.py.jinja → routers/order_files.py.jinja} +23 -20
  138. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/websockets/__init__.py.jinja +1 -0
  139. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/websockets/order_socket.py.jinja +44 -0
  140. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/migrations/__init__.py.jinja +1 -1
  141. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/__init__.py.jinja +1 -7
  142. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/base.py.jinja +7 -0
  143. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/{orders.py.jinja → order_model.py.jinja} +7 -3
  144. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/module.py.jinja +42 -5
  145. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/order_repository.py.jinja +79 -0
  146. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/__init__.py.jinja +1 -0
  147. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/test_domain.py.jinja +4 -3
  148. python_ddd_framework/developer_kit/templates/project/cookiecutter.json +1 -1
  149. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/AGENTS.md +16 -3
  150. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/README.md +29 -22
  151. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{app.development.yaml.jinja → backend/app.development.yaml.jinja} +2 -0
  152. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{pyproject.toml.jinja → backend/pyproject.toml.jinja} +1 -1
  153. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/main.py.jinja +2 -0
  154. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/module.py.jinja +40 -3
  155. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/architecture.md +40 -10
  156. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/development.md +134 -23
  157. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/scripts/.gitkeep +0 -0
  158. python_ddd_framework/distributed_lock/contracts.py +4 -1
  159. python_ddd_framework/domain/__init__.py +2 -0
  160. python_ddd_framework/domain/services.py +11 -0
  161. python_ddd_framework/errors/business.py +10 -1
  162. python_ddd_framework/errors/lifecycle.py +3 -31
  163. python_ddd_framework/events/__init__.py +5 -2
  164. python_ddd_framework/events/aggregate.py +3 -3
  165. python_ddd_framework/events/catalog.py +12 -11
  166. python_ddd_framework/events/contracts.py +6 -1
  167. python_ddd_framework/events/contribution.py +1 -52
  168. python_ddd_framework/events/entities.py +23 -0
  169. python_ddd_framework/events/module.py +97 -0
  170. python_ddd_framework/events/runtime.py +80 -29
  171. python_ddd_framework/events/subscription.py +36 -0
  172. python_ddd_framework/events/unit_of_work.py +25 -0
  173. python_ddd_framework/extensions/__init__.py +22 -0
  174. python_ddd_framework/extensions/catalog.py +198 -0
  175. python_ddd_framework/extensions/contracts.py +85 -0
  176. python_ddd_framework/extensions/models.py +142 -0
  177. python_ddd_framework/extensions/module.py +21 -0
  178. python_ddd_framework/fastapi/__init__.py +2 -0
  179. python_ddd_framework/fastapi/action.py +1 -2
  180. python_ddd_framework/fastapi/adapter.py +16 -0
  181. python_ddd_framework/fastapi/application_services.py +43 -25
  182. python_ddd_framework/fastapi/background.py +2 -1
  183. python_ddd_framework/fastapi/contracts.py +26 -8
  184. python_ddd_framework/fastapi/health.py +1 -1
  185. python_ddd_framework/fastapi/module.py +14 -0
  186. python_ddd_framework/fastapi/parameters.py +3 -18
  187. python_ddd_framework/fastapi/realtime/connection.py +10 -7
  188. python_ddd_framework/fastapi/realtime/module.py +3 -0
  189. python_ddd_framework/fastapi/realtime/runtime.py +10 -24
  190. python_ddd_framework/fastapi/request_context.py +2 -1
  191. python_ddd_framework/fastapi/routing.py +2 -1
  192. python_ddd_framework/fastapi/server.py +8 -0
  193. python_ddd_framework/fastapi/settings.py +2 -1
  194. python_ddd_framework/hosted_services/bridge.py +20 -1
  195. python_ddd_framework/hosted_services/catalog.py +6 -16
  196. python_ddd_framework/hosted_services/contracts.py +4 -0
  197. python_ddd_framework/hosted_services/declarations.py +15 -0
  198. python_ddd_framework/hosted_services/module.py +69 -0
  199. python_ddd_framework/hosted_services/runtime.py +30 -1
  200. python_ddd_framework/identity/__init__.py +24 -0
  201. python_ddd_framework/identity/application.py +38 -90
  202. python_ddd_framework/identity/contracts.py +181 -16
  203. python_ddd_framework/identity/http_api.py +79 -2
  204. python_ddd_framework/identity/management.py +166 -0
  205. python_ddd_framework/identity/module.py +12 -4
  206. python_ddd_framework/identity/services.py +29 -16
  207. python_ddd_framework/identity/sqlalchemy/migrations/0003_extra_properties.py +26 -0
  208. python_ddd_framework/identity/sqlalchemy/migrations/0004_concurrency_version.py +29 -0
  209. python_ddd_framework/identity/sqlalchemy/models.py +21 -2
  210. python_ddd_framework/identity/sqlalchemy/module.py +11 -2
  211. python_ddd_framework/identity/sqlalchemy/repository.py +458 -0
  212. python_ddd_framework/identity/sqlalchemy/session_security.py +88 -0
  213. python_ddd_framework/identity/sqlalchemy/stores.py +69 -290
  214. python_ddd_framework/identity/tokens.py +2 -5
  215. python_ddd_framework/{application_services/validation.py → invocation/arguments.py} +8 -6
  216. python_ddd_framework/invocation/contribution.py +61 -0
  217. python_ddd_framework/invocation/dispatcher.py +291 -0
  218. python_ddd_framework/invocation/entries.py +2 -25
  219. python_ddd_framework/invocation/entrypoints.py +126 -0
  220. python_ddd_framework/{application_services → invocation}/execution.py +8 -5
  221. python_ddd_framework/invocation/function_runtime.py +3 -1
  222. python_ddd_framework/invocation/interception.py +113 -42
  223. python_ddd_framework/{application_services → invocation}/interceptors.py +2 -2
  224. python_ddd_framework/invocation/managed_proxy.py +116 -0
  225. python_ddd_framework/invocation/managed_services.py +250 -0
  226. python_ddd_framework/invocation/methods.py +345 -0
  227. python_ddd_framework/invocation/module.py +113 -0
  228. python_ddd_framework/{application_services → invocation}/policies.py +8 -1
  229. python_ddd_framework/invocation/validation.py +25 -0
  230. python_ddd_framework/lifecycle/__init__.py +2 -0
  231. python_ddd_framework/lifecycle/composition.py +5 -2
  232. python_ddd_framework/lifecycle/participants.py +33 -0
  233. python_ddd_framework/messaging/__init__.py +25 -0
  234. python_ddd_framework/messaging/channel.py +205 -0
  235. python_ddd_framework/messaging/contracts.py +69 -0
  236. python_ddd_framework/messaging/module.py +101 -0
  237. python_ddd_framework/messaging/options.py +13 -0
  238. python_ddd_framework/messaging/receipt.py +27 -0
  239. python_ddd_framework/messaging/runtime.py +124 -0
  240. python_ddd_framework/modularity/__init__.py +11 -1
  241. python_ddd_framework/modularity/contracts.py +47 -16
  242. python_ddd_framework/modularity/discovery.py +21 -2
  243. python_ddd_framework/modularity/graph.py +45 -1
  244. python_ddd_framework/modularity/registry.py +83 -3
  245. python_ddd_framework/notifications/__init__.py +2 -0
  246. python_ddd_framework/notifications/catalog.py +3 -2
  247. python_ddd_framework/notifications/declarations.py +18 -0
  248. python_ddd_framework/notifications/module.py +21 -0
  249. python_ddd_framework/observability/logging.py +3 -0
  250. python_ddd_framework/observability/tracing.py +3 -0
  251. python_ddd_framework/options/contribution.py +12 -0
  252. python_ddd_framework/options/registry.py +44 -11
  253. python_ddd_framework/realtime/__init__.py +2 -1
  254. python_ddd_framework/realtime/contracts.py +3 -7
  255. python_ddd_framework/realtime/messages.py +26 -1
  256. python_ddd_framework/redis/cache.py +243 -0
  257. python_ddd_framework/redis/cache_scripts.py +40 -0
  258. python_ddd_framework/redis/distributed_lock.py +10 -2
  259. python_ddd_framework/redis/module.py +13 -4
  260. python_ddd_framework/redis/notifications.py +5 -2
  261. python_ddd_framework/redis/runtime.py +1 -79
  262. python_ddd_framework/seeding/__init__.py +13 -0
  263. python_ddd_framework/seeding/contracts.py +45 -0
  264. python_ddd_framework/seeding/module.py +73 -0
  265. python_ddd_framework/seeding/runtime.py +63 -0
  266. python_ddd_framework/services/arbitration.py +23 -0
  267. python_ddd_framework/services/binding.py +2 -3
  268. python_ddd_framework/services/composition.py +108 -0
  269. python_ddd_framework/services/contribution.py +44 -54
  270. python_ddd_framework/services/convention.py +53 -181
  271. python_ddd_framework/services/fixed_lifetime.py +12 -15
  272. python_ddd_framework/services/framework_provider.py +3 -158
  273. python_ddd_framework/services/native_graph.py +35 -60
  274. python_ddd_framework/services/provider.py +59 -4
  275. python_ddd_framework/services/repository.py +6 -0
  276. python_ddd_framework/services/runtime.py +129 -215
  277. python_ddd_framework/settings/catalog.py +5 -4
  278. python_ddd_framework/settings/declarations.py +18 -0
  279. python_ddd_framework/settings/definitions.py +5 -0
  280. python_ddd_framework/settings/manager.py +1 -1
  281. python_ddd_framework/settings/module.py +50 -1
  282. python_ddd_framework/settings/notifications.py +4 -2
  283. python_ddd_framework/settings/refresh_module.py +9 -2
  284. python_ddd_framework/settings/sqlalchemy/module.py +2 -1
  285. python_ddd_framework/sqlalchemy/__init__.py +12 -0
  286. python_ddd_framework/sqlalchemy/alembic_runtime/env.py +1 -0
  287. python_ddd_framework/sqlalchemy/auditing.py +7 -1
  288. python_ddd_framework/sqlalchemy/extensions.py +186 -0
  289. python_ddd_framework/sqlalchemy/metadata.py +110 -6
  290. python_ddd_framework/sqlalchemy/migration.py +61 -8
  291. python_ddd_framework/sqlalchemy/migration_operations.py +86 -0
  292. python_ddd_framework/sqlalchemy/module.py +14 -11
  293. python_ddd_framework/sqlalchemy/module_migration.py +71 -3
  294. python_ddd_framework/sqlalchemy/repository.py +217 -13
  295. python_ddd_framework/sqlalchemy/session_provider.py +14 -1
  296. python_ddd_framework/testing/runtime.py +5 -4
  297. python_ddd_framework/unit_of_work/__init__.py +2 -1
  298. python_ddd_framework/unit_of_work/contracts.py +6 -7
  299. python_ddd_framework/unit_of_work/errors.py +11 -0
  300. python_ddd_framework/unit_of_work/lifecycle.py +19 -0
  301. python_ddd_framework/unit_of_work/manager.py +86 -32
  302. python_ddd_framework/unit_of_work/module.py +61 -0
  303. python_ddd_framework/unit_of_work/options.py +7 -1
  304. python_ddd_framework/validation/__init__.py +1 -0
  305. python_ddd_framework/validation/models.py +37 -0
  306. {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/METADATA +139 -44
  307. python_ddd_framework-0.4.0.dist-info/RECORD +488 -0
  308. {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/WHEEL +1 -1
  309. python_ddd_framework/application_services/seeding.py +0 -72
  310. python_ddd_framework/caching/catalog.py +0 -73
  311. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/cache.py.jinja +0 -10
  312. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/events.py.jinja +0 -24
  313. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/orders.py.jinja +0 -83
  314. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/tasks.py.jinja +0 -84
  315. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/orders.py.jinja +0 -35
  316. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repository.py.jinja +0 -14
  317. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding.py.jinja +0 -19
  318. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/definitions.py.jinja +0 -24
  319. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/realtime.py.jinja +0 -36
  320. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/orders.py.jinja +0 -49
  321. python_ddd_framework-0.3.2.dist-info/RECORD +0 -355
  322. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.dockerignore → backend/.dockerignore} +0 -0
  323. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.gitignore → backend/.gitignore} +0 -0
  324. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.python-version → backend/.python-version} +0 -0
  325. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{Dockerfile → backend/Dockerfile} +0 -0
  326. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{compose.dev.yaml.jinja → backend/compose.dev.yaml.jinja} +0 -0
  327. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{compose.production.yaml.jinja → backend/compose.production.yaml.jinja} +0 -0
  328. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/__init__.py.jinja +0 -0
  329. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{tests → backend/tests}/conftest.py.jinja +0 -0
  330. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{tests → backend/tests}/host/test_http.py.jinja +0 -0
  331. /python_ddd_framework/{application_services → invocation}/signature.py +0 -0
  332. {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/entry_points.txt +0 -0
  333. {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/licenses/LICENSE +0 -0
  334. {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,168 @@
1
+ """应用服务发现、最终实现策略和 REQUEST 到 ACTION 代理的组装 owner。"""
2
+
3
+ from typing import cast
4
+
5
+ from dishka import DEFAULT_COMPONENT, DependencyKey, Provider, Scope
6
+
7
+ from ..auditing.module import AuditingModule
8
+ from ..authorization.module import AuthorizationModule
9
+ from ..extensions.module import ExtensionsModule
10
+ from ..invocation.interceptors import InterceptionStage
11
+ from ..invocation.module import InvocationModule
12
+ from ..invocation.validation import _validate_interceptor_scopes
13
+ from ..lifecycle import ConfigureContext
14
+ from ..modularity import AppModule
15
+ from ..modularity.discovery import _is_concrete_implementation
16
+ from ..services.composition import (
17
+ ServiceBindingContext,
18
+ ServiceComposition,
19
+ ServiceConvention,
20
+ ServiceDiscoveryContext,
21
+ ServiceValidationContext,
22
+ )
23
+ from ..services.convention_contracts import (
24
+ _SERVICE_DEFINITION_ATTRIBUTE,
25
+ ConventionDiscoveryKind,
26
+ _ServiceDefinition,
27
+ )
28
+ from ..settings.module import SettingsModule
29
+ from ..unit_of_work.module import UnitOfWorkModule
30
+ from .bindings import (
31
+ _bind_application_services,
32
+ _runtime_providers,
33
+ _validate_application_service_bindings,
34
+ _validate_application_service_dependencies,
35
+ )
36
+ from .catalog import (
37
+ ApplicationServiceCatalog,
38
+ _build_application_service_catalog,
39
+ _DiscoveredApplicationService,
40
+ )
41
+ from .contracts import (
42
+ _APPLICATION_SERVICE_DEFINITION_ATTRIBUTE,
43
+ ApplicationService,
44
+ _definition_for,
45
+ )
46
+ from .dispatcher import _ApplicationServiceDispatcher, _proxy_factory
47
+ from .errors import ApplicationServiceDefinitionError
48
+ from .invocation import _ScopedApplicationServiceInvoker
49
+
50
+
51
+ class ApplicationServicesModule(AppModule):
52
+ dependencies = (
53
+ InvocationModule,
54
+ UnitOfWorkModule,
55
+ AuthorizationModule,
56
+ AuditingModule,
57
+ SettingsModule,
58
+ )
59
+ catalog: ApplicationServiceCatalog
60
+
61
+ def __init__(self) -> None:
62
+ self._discoveries: list[_DiscoveredApplicationService] = []
63
+ self._proxy_factories: dict[DependencyKey, object] = {}
64
+
65
+ def configure(self, context: ConfigureContext) -> None:
66
+ context.services.add_composition(
67
+ ServiceComposition(
68
+ discover=self._discover,
69
+ bind=self._bind,
70
+ validate=self._validate,
71
+ )
72
+ )
73
+
74
+ def _discover(self, context: ServiceDiscoveryContext) -> None:
75
+ seen: set[tuple[object, type[object]]] = set()
76
+ for discovery in context.discoveries:
77
+ candidate = discovery.candidate
78
+ identity = (discovery.owner, candidate)
79
+ if identity in seen or not _is_concrete_implementation(candidate):
80
+ continue
81
+ seen.add(identity)
82
+ definition = _definition_for(candidate)
83
+ if definition is None:
84
+ continue
85
+ if _SERVICE_DEFINITION_ATTRIBUTE in candidate.__dict__:
86
+ raise ApplicationServiceDefinitionError(
87
+ symbol=f"{candidate.__module__}.{candidate.__qualname__}",
88
+ reason="application_service and service metadata cannot be combined",
89
+ )
90
+ decorated = _APPLICATION_SERVICE_DEFINITION_ATTRIBUTE in candidate.__dict__
91
+ context.conventions[discovery.owner, candidate] = ServiceConvention(
92
+ _ServiceDefinition(provides=definition.contract or candidate, scope=Scope.ACTION),
93
+ ConventionDiscoveryKind.APPLICATION_SERVICE_DECORATOR
94
+ if decorated
95
+ else ConventionDiscoveryKind.FRAMEWORK_MARKER,
96
+ "accepted from immutable application_service decorator metadata"
97
+ if decorated
98
+ else "accepted from unique inherited ApplicationService contract",
99
+ )
100
+ # 公开契约仍由扫描该契约的 Module 拥有;实现所在模块不是第二个契约 owner。
101
+ declarations = {
102
+ item.owner: item
103
+ for item in context.discoveries
104
+ if item.candidate is definition.contract
105
+ }
106
+ if len(declarations) > 1:
107
+ raise ApplicationServiceDefinitionError(
108
+ symbol=str(definition.contract),
109
+ reason="application service contract is scanned by multiple Modules",
110
+ )
111
+ declaration = next(iter(declarations.values()), discovery)
112
+ self._discoveries.append(
113
+ _DiscoveredApplicationService(
114
+ service_type=cast(type[ApplicationService], candidate),
115
+ definition=definition,
116
+ owner=declaration.owner,
117
+ source=declaration.source,
118
+ declaration_index=len(self._discoveries),
119
+ )
120
+ )
121
+
122
+ def _bind(self, context: ServiceBindingContext) -> None:
123
+ bound = _bind_application_services(tuple(self._discoveries), context.arbitration.drafts)
124
+ _validate_application_service_dependencies(
125
+ context.modules, bound, context.arbitration.drafts
126
+ )
127
+ self.catalog = _build_application_service_catalog(
128
+ tuple(item.binding for item in bound),
129
+ context.modules.instance(AuthorizationModule).catalog,
130
+ context.modules.instance(InvocationModule).rules,
131
+ context.modules.instance(ExtensionsModule).catalog,
132
+ )
133
+ # 只重定位仲裁后的原生绑定,保留其他能力已经生成的目录 Provider。
134
+ for provider in context.arbitration.providers:
135
+ context._providers.remove(provider)
136
+ context._providers[0:0] = _runtime_providers(context.arbitration, bound)
137
+ provider = Provider()
138
+ provider.provide(lambda: self.catalog, provides=ApplicationServiceCatalog, scope=Scope.APP)
139
+ provider.provide(_ApplicationServiceDispatcher, scope=Scope.REQUEST)
140
+ provider.provide(_ScopedApplicationServiceInvoker, scope=Scope.APP)
141
+ for registration in self.catalog.registrations:
142
+ factory = _proxy_factory(self.catalog._for_registration(registration))
143
+ self._proxy_factories[DependencyKey(registration.contract, DEFAULT_COMPONENT)] = factory
144
+ provider.provide(
145
+ factory,
146
+ provides=registration.contract,
147
+ scope=Scope.REQUEST,
148
+ )
149
+ context.add_provider(provider)
150
+
151
+ def _validate(self, context: ServiceValidationContext) -> None:
152
+ _validate_application_service_bindings(
153
+ context.native_graph, self.catalog, self._proxy_factories
154
+ )
155
+ _validate_interceptor_scopes(
156
+ context.native_graph,
157
+ (
158
+ (
159
+ registration,
160
+ Scope.ACTION
161
+ if registration.before is InterceptionStage.METHOD
162
+ else Scope.REQUEST,
163
+ )
164
+ for service in self.catalog.registrations
165
+ for method in service.methods
166
+ for registration in method.interceptors
167
+ ),
168
+ )
@@ -0,0 +1,21 @@
1
+ """标准分页 DTO;默认值只在输入契约声明。"""
2
+
3
+ from typing import Generic, TypeVar
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+ _T = TypeVar("_T")
8
+
9
+
10
+ class PagedResultRequestDto(BaseModel):
11
+ skip_count: int = Field(default=0, ge=0)
12
+ max_result_count: int = Field(default=10, ge=1, le=1000)
13
+
14
+
15
+ class PagedAndSortedResultRequestDto(PagedResultRequestDto):
16
+ sorting: str | None = None
17
+
18
+
19
+ class PagedResult(BaseModel, Generic[_T]):
20
+ items: list[_T]
21
+ total_count: int = Field(ge=0)
@@ -9,7 +9,7 @@ from typing import TypeVar
9
9
 
10
10
  from pydantic import ConfigDict
11
11
 
12
- from ..application_services.policies import _declare_switch
12
+ from ..invocation.policies import _declare_switch
13
13
  from ..options import BaseOptions
14
14
 
15
15
  _T = TypeVar("_T")
@@ -0,0 +1,15 @@
1
+ """审计默认配置与 Application-local 控制状态的 owner。"""
2
+
3
+ from dishka import Provider, Scope
4
+
5
+ from ..lifecycle import ConfigureContext
6
+ from ..modularity import AppModule
7
+ from .control import AuditingControl, AuditingOptions
8
+
9
+
10
+ class AuditingModule(AppModule):
11
+ def configure(self, context: ConfigureContext) -> None:
12
+ context.configure.default(AuditingOptions, section="auditing")
13
+ provider = Provider()
14
+ provider.provide(AuditingControl, scope=Scope.APP)
15
+ context.services.contribute(provider, reason="Application-local auditing control")
@@ -17,10 +17,12 @@ from .errors import (
17
17
  PermissionDeniedError,
18
18
  UnauthenticatedError,
19
19
  )
20
+ from .options import AuthorizationOptions
20
21
 
21
22
  __all__ = (
22
23
  "AccessTokenAuthenticator",
23
24
  "AuthorizationDefinitionError",
25
+ "AuthorizationOptions",
24
26
  "CurrentUser",
25
27
  "PermissionCatalog",
26
28
  "PermissionChecker",
@@ -7,6 +7,7 @@ from dataclasses import dataclass
7
7
  from typing import Protocol, TypeVar, runtime_checkable
8
8
  from uuid import UUID
9
9
 
10
+ from ..modularity import ModuleDeclaration, ModuleRef
10
11
  from .errors import AuthorizationDefinitionError, PermissionDefinitionError
11
12
 
12
13
  _AUTHORIZATION_ATTRIBUTE = "__python_ddd_framework_authorization__"
@@ -129,9 +130,13 @@ class AccessTokenAuthenticator(Protocol):
129
130
 
130
131
 
131
132
  @dataclass(frozen=True, slots=True)
132
- class _AuthorizationMetadata:
133
+ class _AuthorizationMetadata(ModuleDeclaration):
133
134
  permissions: tuple[PermissionName, ...]
134
135
 
136
+ @property
137
+ def required_module(self) -> ModuleRef:
138
+ return ModuleRef("python_ddd_framework.authorization.module:AuthorizationModule")
139
+
135
140
 
136
141
  @dataclass(frozen=True, slots=True)
137
142
  class _AuthorizationRequirement:
@@ -228,7 +233,7 @@ def _metadata_for(target: object) -> _AuthorizationMetadata | None:
228
233
 
229
234
 
230
235
  def _validate_authorization_target(target: object) -> None:
231
- if not isinstance(target, type) and not callable(target):
236
+ if not isinstance(target, (type, staticmethod, classmethod)) and not callable(target):
232
237
  raise AuthorizationDefinitionError(
233
238
  symbol=repr(target),
234
239
  reason="authorization decorator target must be a class or callable",
@@ -5,11 +5,16 @@ from __future__ import annotations
5
5
  from abc import ABC, abstractmethod
6
6
 
7
7
  from ..diagnostics import SourceLocation
8
+ from ..modularity import ModuleRef, ModuleRequirement
8
9
  from .contracts import PermissionDefinition
9
10
  from .errors import PermissionDefinitionError
10
11
 
11
12
 
12
13
  class PermissionDefinitionProvider(ABC):
14
+ __authorization_module__ = ModuleRequirement(
15
+ ModuleRef("python_ddd_framework.authorization.module:AuthorizationModule")
16
+ )
17
+
13
18
  @abstractmethod
14
19
  def define(self, context: PermissionDefinitionContext) -> None:
15
20
  """同步贡献静态权限定义;无构造注入、I/O 或运行期服务解析。"""
@@ -0,0 +1,40 @@
1
+ """权限声明与当前身份的注册 owner;认证和存储实现由 Host 选择。"""
2
+
3
+ from dishka import Provider, Scope
4
+
5
+ from ..application_services.invocation import _InvocationRuntime
6
+ from ..invocation.module import InvocationModule
7
+ from ..lifecycle import ConfigureContext
8
+ from ..modularity import AppModule
9
+ from ..services.composition import (
10
+ ServiceBindingContext,
11
+ ServiceComposition,
12
+ ServiceDiscoveryContext,
13
+ )
14
+ from .catalog import PermissionCatalog, _build_permission_catalog
15
+ from .contracts import CurrentUser
16
+ from .options import AuthorizationOptions
17
+
18
+
19
+ class AuthorizationModule(AppModule):
20
+ dependencies = (InvocationModule,)
21
+ catalog: PermissionCatalog
22
+
23
+ def configure(self, context: ConfigureContext) -> None:
24
+ context.configure.default(AuthorizationOptions, section="authorization")
25
+ context.services.add_composition(
26
+ ServiceComposition(discover=self._discover, bind=self._bind)
27
+ )
28
+
29
+ def _discover(self, context: ServiceDiscoveryContext) -> None:
30
+ self.catalog = _build_permission_catalog(context.modules, context.discoveries)
31
+
32
+ def _bind(self, context: ServiceBindingContext) -> None:
33
+ provider = Provider()
34
+ provider.provide(lambda: self.catalog, provides=PermissionCatalog, scope=Scope.APP)
35
+ provider.provide(_current_user, provides=CurrentUser, scope=Scope.REQUEST)
36
+ context.add_provider(provider)
37
+
38
+
39
+ def _current_user(runtime: _InvocationRuntime) -> CurrentUser:
40
+ return runtime.current_user()
@@ -0,0 +1,7 @@
1
+ """Host-owned 授权策略;不改变认证和业务身份。"""
2
+
3
+ from ..options import BaseOptions
4
+
5
+
6
+ class AuthorizationOptions(BaseOptions):
7
+ always_allow: bool = False
@@ -5,6 +5,9 @@ from __future__ import annotations
5
5
  import asyncio
6
6
  from typing import TYPE_CHECKING
7
7
 
8
+ from ..background_workers.runtime import _BackgroundCoordinator
9
+ from ..hosted_services.runtime import _HostedServiceCoordinator
10
+
8
11
  if TYPE_CHECKING:
9
12
  from multiprocessing.connection import _ConnectionBase as Connection
10
13
 
@@ -51,8 +54,10 @@ async def _serve_child(
51
54
  pipe: Connection,
52
55
  ) -> None:
53
56
  application = spec.build()
54
- application._root.require_hosted_service_coordinator().select_child()
55
- coordinator = application._root.require_background_coordinator()
57
+ container = application._container_for_integration()
58
+ hosted = await container.get(_HostedServiceCoordinator)
59
+ hosted.select_child()
60
+ coordinator = await container.get(_BackgroundCoordinator)
56
61
  coordinator.select_child(tasks, initial)
57
62
  try:
58
63
  await _start_while_parent_present(application, pipe)
@@ -91,7 +96,12 @@ async def _start_while_parent_present(application: Application, pipe: Connection
91
96
  await application.start()
92
97
  # 启动确认与 start 返回处于同一 task 连续段;首轮业务崩溃不能被误判为初始化失败。
93
98
  pipe.send(
94
- (_Message.SNAPSHOTS, application._root.require_background_coordinator().snapshots())
99
+ (
100
+ _Message.SNAPSHOTS,
101
+ (
102
+ await application._container_for_integration().get(_BackgroundCoordinator)
103
+ ).snapshots(),
104
+ )
95
105
  )
96
106
  pipe.send((_Message.READY,))
97
107
 
@@ -0,0 +1,45 @@
1
+ """后台执行的模块声明;任务身份和调度事实仍由原有定义契约校验。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ from ..modularity import ModuleDeclaration, ModuleDependency, ModuleRef
9
+
10
+ if TYPE_CHECKING:
11
+ from ..background_jobs import BackgroundJobDefinition, BackgroundJobSchedule
12
+ from ..background_workers.contracts import BackgroundWorker, BackgroundWorkerDefinition
13
+
14
+
15
+ @dataclass(frozen=True, slots=True)
16
+ class BackgroundWorkers(ModuleDeclaration):
17
+ workers: tuple[type[BackgroundWorker] | BackgroundWorkerDefinition, ...]
18
+
19
+ @property
20
+ def required_module(self) -> ModuleDependency:
21
+ return ModuleRef(
22
+ "python_ddd_framework.background_execution.module:BackgroundExecutionModule"
23
+ )
24
+
25
+
26
+ @dataclass(frozen=True, slots=True)
27
+ class BackgroundJobs(ModuleDeclaration):
28
+ definitions: tuple[BackgroundJobDefinition[Any], ...]
29
+
30
+ @property
31
+ def required_module(self) -> ModuleDependency:
32
+ return ModuleRef(
33
+ "python_ddd_framework.background_execution.module:BackgroundExecutionModule"
34
+ )
35
+
36
+
37
+ @dataclass(frozen=True, slots=True)
38
+ class BackgroundSchedules(ModuleDeclaration):
39
+ schedules: tuple[BackgroundJobSchedule[Any], ...]
40
+
41
+ @property
42
+ def required_module(self) -> ModuleDependency:
43
+ return ModuleRef(
44
+ "python_ddd_framework.background_execution.module:BackgroundExecutionModule"
45
+ )
@@ -5,6 +5,18 @@ from __future__ import annotations
5
5
  from collections.abc import Awaitable, Callable
6
6
  from dataclasses import dataclass, field
7
7
 
8
+ from ..errors import FrameworkError
9
+
10
+
11
+ class _BackgroundExecutionStartFailure(FrameworkError):
12
+ """后台启动错误归能力 owner;不伪造 Module hook 的诊断位置。"""
13
+
14
+ def __init__(self, *, original_error: BaseException) -> None:
15
+ self.original_error = original_error
16
+ self.__cause__ = original_error
17
+ self.__suppress_context__ = True
18
+ super().__init__(f"Background execution start failed with {type(original_error).__name__}")
19
+
8
20
 
9
21
  @dataclass(frozen=True, slots=True)
10
22
  class _BackgroundRuntimeFailure:
@@ -1,9 +1,115 @@
1
- """后台管理由 Host 显式选择,业务任务声明保持独立。"""
1
+ """后台 Worker/Job 的组装与运行 owner;执行模式仍由 Host 配置决定。"""
2
2
 
3
+ from dishka import Provider, Scope
4
+
5
+ from ..background_jobs import BackgroundJobCatalog
6
+ from ..background_jobs.catalog import _build_background_job_catalog
7
+ from ..background_workers import BackgroundWorkerCatalog
8
+ from ..background_workers.catalog import _build_background_worker_catalog
9
+ from ..background_workers.contracts import BackgroundExecutionOptions
10
+ from ..background_workers.runtime import _BackgroundCoordinator
11
+ from ..hosted_services.module import HostedServicesModule
12
+ from ..invocation import BackgroundSystemCaller
13
+ from ..invocation.interception import InvocationEntry
14
+ from ..invocation.module import InvocationModule
15
+ from ..lifecycle import ConfigureContext
3
16
  from ..modularity import AppModule
17
+ from ..services.composition import (
18
+ ServiceBindingContext,
19
+ ServiceComposition,
20
+ ServiceDiscoveryContext,
21
+ ServiceValidationContext,
22
+ )
23
+ from ..unit_of_work.options import UnitOfWorkDeclaration
24
+
25
+
26
+ class BackgroundExecutionModule(AppModule):
27
+ dependencies = (HostedServicesModule,)
28
+ workers: BackgroundWorkerCatalog
29
+ jobs: BackgroundJobCatalog
30
+ coordinator: _BackgroundCoordinator
31
+
32
+ def configure(self, context: ConfigureContext) -> None:
33
+ context.configure.default(BackgroundExecutionOptions, section="background_execution")
34
+ context.services.add_composition(
35
+ ServiceComposition(
36
+ discover=self._discover,
37
+ bind=self._bind,
38
+ validate=self._validate,
39
+ )
40
+ )
41
+
42
+ def _discover(self, context: ServiceDiscoveryContext) -> None:
43
+ self.workers = _build_background_worker_catalog(context.modules)
44
+ self.jobs = _build_background_job_catalog(context.modules, context.discoveries)
45
+ # 默认实现也经过统一仲裁,显式替换后只保留最终获选的原生 binding。
46
+ targets = dict.fromkeys(
47
+ [
48
+ (item.worker_type, self.workers.owner_for(item.name))
49
+ for item in self.workers.definitions
50
+ ]
51
+ + [(item.handler_type, self.jobs.owner_for(item)) for item in self.jobs.definitions]
52
+ )
53
+ for target, owner in targets:
54
+ if not isinstance(target, type):
55
+ continue
56
+ provider = Provider()
57
+ provider.provide(target, scope=Scope.ACTION)
58
+ context.services._add_framework_default(
59
+ provider=provider,
60
+ owner=owner,
61
+ source=context.modules.descriptors[owner].declaration_source,
62
+ reason="Background entry default implementation",
63
+ )
64
+
65
+ def _bind(self, context: ServiceBindingContext) -> None:
66
+ invocation = context.modules.instance(InvocationModule)
67
+ self.coordinator = _BackgroundCoordinator(
68
+ worker_catalog=self.workers,
69
+ job_catalog=self.jobs,
70
+ options=context.options.get(BackgroundExecutionOptions),
71
+ invocation_runtime=invocation.invocation_runtime,
72
+ build_spec=context.build_spec,
73
+ )
74
+ self.runtime = self.coordinator.runtime_participant()
75
+ for worker in self.workers.definitions:
76
+ invocation.entries.append(
77
+ InvocationEntry(
78
+ worker.worker_type,
79
+ "run_iteration",
80
+ self.workers.owner_for(worker.name),
81
+ 1,
82
+ unit_of_work_default=UnitOfWorkDeclaration(disabled=True),
83
+ )
84
+ )
85
+ for job in self.jobs.definitions:
86
+ invocation.entries.append(
87
+ InvocationEntry(
88
+ job.handler_type,
89
+ "execute",
90
+ self.jobs.owner_for(job),
91
+ 2,
92
+ )
93
+ )
94
+ provider = Provider()
95
+ provider.provide(lambda: self.workers, provides=BackgroundWorkerCatalog, scope=Scope.APP)
96
+ provider.provide(lambda: self.jobs, provides=BackgroundJobCatalog, scope=Scope.APP)
97
+ provider.provide(lambda: self.coordinator, provides=_BackgroundCoordinator, scope=Scope.APP)
98
+ provider.provide(
99
+ lambda: self.coordinator.system_caller,
100
+ provides=BackgroundSystemCaller,
101
+ scope=Scope.APP,
102
+ )
103
+ context.add_provider(provider)
104
+
105
+ def _validate(self, context: ServiceValidationContext) -> None:
106
+ self.coordinator.attach_container(context.container)
4
107
 
5
108
 
6
109
  class BackgroundManagementModule(AppModule):
110
+ """管理接口由 Host 选择;任务声明不因此自动暴露管理入口。"""
111
+
112
+ dependencies = (BackgroundExecutionModule,)
7
113
  scan_packages = (
8
114
  "python_ddd_framework.background_execution.application",
9
115
  "python_ddd_framework.background_execution.permissions",