python-ddd-framework 0.3.2__py3-none-any.whl → 0.5.1__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 (337) 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 +120 -288
  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 +197 -15
  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/local.py +20 -1
  25. python_ddd_framework/background_execution/module.py +107 -1
  26. python_ddd_framework/background_execution/processes.py +11 -1
  27. python_ddd_framework/background_jobs/catalog.py +59 -42
  28. python_ddd_framework/background_jobs/contracts.py +16 -19
  29. python_ddd_framework/background_jobs/pgqueuer/enqueue.py +4 -0
  30. python_ddd_framework/background_jobs/pgqueuer/module.py +2 -1
  31. python_ddd_framework/background_jobs/pgqueuer/runtime.py +7 -7
  32. python_ddd_framework/background_workers/catalog.py +23 -36
  33. python_ddd_framework/background_workers/contracts.py +3 -0
  34. python_ddd_framework/background_workers/execution.py +1 -1
  35. python_ddd_framework/background_workers/runtime.py +47 -1
  36. python_ddd_framework/caching/__init__.py +7 -6
  37. python_ddd_framework/caching/contracts.py +65 -67
  38. python_ddd_framework/caching/errors.py +0 -7
  39. python_ddd_framework/caching/module.py +18 -0
  40. python_ddd_framework/caching/naming.py +29 -0
  41. python_ddd_framework/caching/options.py +38 -0
  42. python_ddd_framework/caching/unit_of_work.py +46 -0
  43. python_ddd_framework/cli/__init__.py +31 -6
  44. python_ddd_framework/cli/project.py +68 -6
  45. python_ddd_framework/cli/runtime.py +1 -1
  46. python_ddd_framework/developer_kit/generation.py +75 -22
  47. python_ddd_framework/developer_kit/project_metadata.py +2 -4
  48. python_ddd_framework/developer_kit/templates/basic/cookiecutter.json +4 -0
  49. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/README.md +45 -0
  50. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/__init__.py.jinja +0 -0
  51. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/contracts/__init__.py.jinja +0 -0
  52. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/contracts/conversion_service.py.jinja +7 -0
  53. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/module.py.jinja +38 -0
  54. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/services/__init__.py.jinja +0 -0
  55. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/services/default_conversion_service.py.jinja +10 -0
  56. python_ddd_framework/developer_kit/templates/module/cookiecutter.json +1 -1
  57. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/README.md +42 -49
  58. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/__init__.py.jinja +1 -0
  59. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/__init__.py.jinja +1 -0
  60. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/handler.py.jinja +45 -0
  61. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/payload.py.jinja +8 -0
  62. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/__init__.py.jinja +1 -0
  63. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/handler.py.jinja +35 -0
  64. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/payload.py.jinja +5 -0
  65. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/schedule.py.jinja +15 -0
  66. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/__init__.py.jinja +1 -0
  67. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/order_maintenance_worker.py.jinja +28 -0
  68. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/order_statistics_worker.py.jinja +26 -0
  69. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/__init__.py.jinja +1 -0
  70. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/order_cache.py.jinja +9 -0
  71. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/statistics_cache.py.jinja +9 -0
  72. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/event_handlers/__init__.py.jinja +1 -0
  73. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/event_handlers/order_changed_handler.py.jinja +23 -0
  74. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/__init__.py.jinja +1 -0
  75. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/order_integration_service.py.jinja +71 -0
  76. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/order_observation_handler.py.jinja +16 -0
  77. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration_services/__init__.py.jinja +1 -0
  78. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration_services/order_reporting_service.py.jinja +16 -0
  79. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/interceptors/__init__.py.jinja +1 -0
  80. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/interceptors/order_timing_interceptor.py.jinja +17 -0
  81. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/module.py.jinja +105 -10
  82. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/options/__init__.py.jinja +1 -0
  83. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/{options.py.jinja → options/order_options.py.jinja} +1 -1
  84. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/__init__.py.jinja +1 -0
  85. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_approval_service.py.jinja +65 -0
  86. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_management_service.py.jinja +35 -0
  87. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_query_service.py.jinja +41 -0
  88. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/__init__.py.jinja +1 -0
  89. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/approval_setting_observer.py.jinja +17 -0
  90. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/__init__.py.jinja +1 -0
  91. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/approve_order.py.jinja +6 -0
  92. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/create_order.py.jinja +7 -0
  93. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/integration_services/__init__.py.jinja +1 -0
  94. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/integration_services/order_reporting_service.py.jinja +7 -0
  95. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/module.py.jinja +39 -2
  96. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/__init__.py.jinja +1 -0
  97. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_approval_service.py.jinja +13 -0
  98. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_management_service.py.jinja +10 -0
  99. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_query_service.py.jinja +12 -0
  100. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/__init__.py.jinja +1 -0
  101. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/order_statistics_snapshot.py.jinja +9 -0
  102. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/order_view.py.jinja +12 -0
  103. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/entities/__init__.py.jinja +1 -0
  104. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/{orders.py.jinja → entities/order.py.jinja} +10 -18
  105. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/events/__init__.py.jinja +1 -0
  106. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/events/order_changed.py.jinja +13 -0
  107. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/module.py.jinja +42 -3
  108. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repositories/__init__.py.jinja +1 -0
  109. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repositories/order_repository.py.jinja +17 -0
  110. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding/__init__.py.jinja +1 -0
  111. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding/order_seed_contributor.py.jinja +22 -0
  112. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/services/__init__.py.jinja +1 -0
  113. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/services/order_approval_service.py.jinja +23 -0
  114. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/settings/__init__.py.jinja +1 -0
  115. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/{settings.py.jinja → settings/approval_settings.py.jinja} +3 -2
  116. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/value_objects/__init__.py.jinja +1 -0
  117. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/value_objects/order_title.py.jinja +10 -0
  118. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/constants/__init__.py.jinja +1 -0
  119. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/constants/order_constants.py.jinja +3 -0
  120. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/enums/__init__.py.jinja +1 -0
  121. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/enums/order_status.py.jinja +6 -0
  122. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/errors/__init__.py.jinja +1 -0
  123. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/errors/order_errors.py.jinja +11 -0
  124. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/__init__.py.jinja +1 -0
  125. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/order_messages.py.jinja +26 -0
  126. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/order_observation.py.jinja +13 -0
  127. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/module.py.jinja +40 -1
  128. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions/__init__.py.jinja +1 -0
  129. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/{permissions.py.jinja → permissions/order_permission_provider.py.jinja} +6 -2
  130. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions/order_permissions.py.jinja +7 -0
  131. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/value_objects/__init__.py.jinja +1 -0
  132. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/value_objects/money.py.jinja +18 -0
  133. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/filters/__init__.py.jinja +1 -0
  134. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/filters/export_filter.py.jinja +15 -0
  135. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/models/__init__.py.jinja +1 -0
  136. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/models/refresh_orders.py.jinja +5 -0
  137. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/module.py.jinja +85 -7
  138. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/routers/__init__.py.jinja +1 -0
  139. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/{files.py.jinja → routers/order_files.py.jinja} +23 -20
  140. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/websockets/__init__.py.jinja +1 -0
  141. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/websockets/order_socket.py.jinja +44 -0
  142. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/migrations/__init__.py.jinja +1 -1
  143. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/__init__.py.jinja +1 -7
  144. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/base.py.jinja +7 -0
  145. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/{orders.py.jinja → order_model.py.jinja} +7 -3
  146. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/module.py.jinja +42 -5
  147. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/order_repository.py.jinja +79 -0
  148. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/__init__.py.jinja +1 -0
  149. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/test_domain.py.jinja +4 -3
  150. python_ddd_framework/developer_kit/templates/project/cookiecutter.json +1 -1
  151. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/AGENTS.md +23 -3
  152. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/README.md +29 -22
  153. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{app.development.yaml.jinja → backend/app.development.yaml.jinja} +2 -0
  154. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{pyproject.toml.jinja → backend/pyproject.toml.jinja} +1 -1
  155. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/main.py.jinja +2 -0
  156. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/module.py.jinja +40 -3
  157. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/architecture.md +42 -11
  158. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/development.md +144 -23
  159. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/scripts/.gitkeep +0 -0
  160. python_ddd_framework/distributed_lock/contracts.py +4 -1
  161. python_ddd_framework/domain/__init__.py +2 -0
  162. python_ddd_framework/domain/services.py +11 -0
  163. python_ddd_framework/errors/business.py +10 -1
  164. python_ddd_framework/errors/lifecycle.py +3 -31
  165. python_ddd_framework/events/__init__.py +5 -2
  166. python_ddd_framework/events/aggregate.py +3 -3
  167. python_ddd_framework/events/catalog.py +12 -11
  168. python_ddd_framework/events/contracts.py +6 -1
  169. python_ddd_framework/events/contribution.py +1 -52
  170. python_ddd_framework/events/entities.py +23 -0
  171. python_ddd_framework/events/module.py +97 -0
  172. python_ddd_framework/events/runtime.py +80 -29
  173. python_ddd_framework/events/subscription.py +36 -0
  174. python_ddd_framework/events/unit_of_work.py +25 -0
  175. python_ddd_framework/extensions/__init__.py +22 -0
  176. python_ddd_framework/extensions/catalog.py +198 -0
  177. python_ddd_framework/extensions/contracts.py +85 -0
  178. python_ddd_framework/extensions/models.py +142 -0
  179. python_ddd_framework/extensions/module.py +21 -0
  180. python_ddd_framework/fastapi/__init__.py +2 -0
  181. python_ddd_framework/fastapi/action.py +1 -2
  182. python_ddd_framework/fastapi/adapter.py +16 -0
  183. python_ddd_framework/fastapi/application_services.py +43 -25
  184. python_ddd_framework/fastapi/background.py +2 -1
  185. python_ddd_framework/fastapi/contracts.py +26 -8
  186. python_ddd_framework/fastapi/health.py +1 -1
  187. python_ddd_framework/fastapi/module.py +14 -0
  188. python_ddd_framework/fastapi/parameters.py +3 -18
  189. python_ddd_framework/fastapi/realtime/connection.py +10 -7
  190. python_ddd_framework/fastapi/realtime/module.py +3 -0
  191. python_ddd_framework/fastapi/realtime/runtime.py +10 -24
  192. python_ddd_framework/fastapi/request_context.py +2 -1
  193. python_ddd_framework/fastapi/routing.py +2 -1
  194. python_ddd_framework/fastapi/server.py +8 -0
  195. python_ddd_framework/fastapi/settings.py +2 -1
  196. python_ddd_framework/hosted_services/bridge.py +95 -17
  197. python_ddd_framework/hosted_services/catalog.py +6 -16
  198. python_ddd_framework/hosted_services/contracts.py +9 -1
  199. python_ddd_framework/hosted_services/declarations.py +15 -0
  200. python_ddd_framework/hosted_services/module.py +69 -0
  201. python_ddd_framework/hosted_services/runtime.py +101 -18
  202. python_ddd_framework/identity/__init__.py +24 -0
  203. python_ddd_framework/identity/application.py +38 -90
  204. python_ddd_framework/identity/contracts.py +181 -16
  205. python_ddd_framework/identity/http_api.py +79 -2
  206. python_ddd_framework/identity/management.py +166 -0
  207. python_ddd_framework/identity/module.py +12 -4
  208. python_ddd_framework/identity/services.py +29 -16
  209. python_ddd_framework/identity/sqlalchemy/migrations/0003_extra_properties.py +26 -0
  210. python_ddd_framework/identity/sqlalchemy/migrations/0004_concurrency_version.py +29 -0
  211. python_ddd_framework/identity/sqlalchemy/models.py +21 -2
  212. python_ddd_framework/identity/sqlalchemy/module.py +11 -2
  213. python_ddd_framework/identity/sqlalchemy/repository.py +458 -0
  214. python_ddd_framework/identity/sqlalchemy/session_security.py +88 -0
  215. python_ddd_framework/identity/sqlalchemy/stores.py +69 -290
  216. python_ddd_framework/identity/tokens.py +2 -5
  217. python_ddd_framework/{application_services/validation.py → invocation/arguments.py} +8 -6
  218. python_ddd_framework/invocation/contribution.py +61 -0
  219. python_ddd_framework/invocation/dispatcher.py +291 -0
  220. python_ddd_framework/invocation/entries.py +2 -25
  221. python_ddd_framework/invocation/entrypoints.py +126 -0
  222. python_ddd_framework/{application_services → invocation}/execution.py +8 -5
  223. python_ddd_framework/invocation/function_runtime.py +3 -1
  224. python_ddd_framework/invocation/interception.py +113 -42
  225. python_ddd_framework/{application_services → invocation}/interceptors.py +2 -2
  226. python_ddd_framework/invocation/managed_proxy.py +116 -0
  227. python_ddd_framework/invocation/managed_services.py +250 -0
  228. python_ddd_framework/invocation/methods.py +345 -0
  229. python_ddd_framework/invocation/module.py +113 -0
  230. python_ddd_framework/{application_services → invocation}/policies.py +8 -1
  231. python_ddd_framework/invocation/validation.py +25 -0
  232. python_ddd_framework/lifecycle/__init__.py +2 -0
  233. python_ddd_framework/lifecycle/composition.py +5 -2
  234. python_ddd_framework/lifecycle/participants.py +37 -0
  235. python_ddd_framework/messaging/__init__.py +25 -0
  236. python_ddd_framework/messaging/channel.py +232 -0
  237. python_ddd_framework/messaging/contracts.py +78 -0
  238. python_ddd_framework/messaging/module.py +114 -0
  239. python_ddd_framework/messaging/options.py +13 -0
  240. python_ddd_framework/messaging/receipt.py +27 -0
  241. python_ddd_framework/messaging/runtime.py +139 -0
  242. python_ddd_framework/modularity/__init__.py +11 -1
  243. python_ddd_framework/modularity/contracts.py +47 -16
  244. python_ddd_framework/modularity/discovery.py +21 -2
  245. python_ddd_framework/modularity/graph.py +45 -1
  246. python_ddd_framework/modularity/registry.py +83 -3
  247. python_ddd_framework/notifications/__init__.py +2 -0
  248. python_ddd_framework/notifications/catalog.py +3 -2
  249. python_ddd_framework/notifications/declarations.py +18 -0
  250. python_ddd_framework/notifications/module.py +21 -0
  251. python_ddd_framework/observability/logging.py +3 -0
  252. python_ddd_framework/observability/tracing.py +3 -0
  253. python_ddd_framework/options/contribution.py +12 -0
  254. python_ddd_framework/options/registry.py +44 -11
  255. python_ddd_framework/realtime/__init__.py +2 -1
  256. python_ddd_framework/realtime/contracts.py +3 -7
  257. python_ddd_framework/realtime/messages.py +26 -1
  258. python_ddd_framework/redis/cache.py +243 -0
  259. python_ddd_framework/redis/cache_scripts.py +40 -0
  260. python_ddd_framework/redis/distributed_lock.py +10 -2
  261. python_ddd_framework/redis/module.py +13 -4
  262. python_ddd_framework/redis/notifications.py +5 -2
  263. python_ddd_framework/redis/runtime.py +1 -79
  264. python_ddd_framework/seeding/__init__.py +13 -0
  265. python_ddd_framework/seeding/contracts.py +45 -0
  266. python_ddd_framework/seeding/module.py +73 -0
  267. python_ddd_framework/seeding/runtime.py +63 -0
  268. python_ddd_framework/services/arbitration.py +23 -0
  269. python_ddd_framework/services/binding.py +2 -3
  270. python_ddd_framework/services/composition.py +108 -0
  271. python_ddd_framework/services/contribution.py +44 -54
  272. python_ddd_framework/services/convention.py +53 -181
  273. python_ddd_framework/services/fixed_lifetime.py +12 -15
  274. python_ddd_framework/services/framework_provider.py +3 -158
  275. python_ddd_framework/services/native_graph.py +35 -60
  276. python_ddd_framework/services/provider.py +59 -4
  277. python_ddd_framework/services/repository.py +6 -0
  278. python_ddd_framework/services/runtime.py +129 -215
  279. python_ddd_framework/settings/catalog.py +5 -4
  280. python_ddd_framework/settings/declarations.py +18 -0
  281. python_ddd_framework/settings/definitions.py +5 -0
  282. python_ddd_framework/settings/manager.py +1 -1
  283. python_ddd_framework/settings/module.py +50 -1
  284. python_ddd_framework/settings/notifications.py +4 -2
  285. python_ddd_framework/settings/refresh_module.py +9 -2
  286. python_ddd_framework/settings/sqlalchemy/module.py +2 -1
  287. python_ddd_framework/sqlalchemy/__init__.py +12 -0
  288. python_ddd_framework/sqlalchemy/alembic_runtime/env.py +1 -0
  289. python_ddd_framework/sqlalchemy/auditing.py +7 -1
  290. python_ddd_framework/sqlalchemy/extensions.py +186 -0
  291. python_ddd_framework/sqlalchemy/metadata.py +110 -6
  292. python_ddd_framework/sqlalchemy/migration.py +61 -8
  293. python_ddd_framework/sqlalchemy/migration_operations.py +86 -0
  294. python_ddd_framework/sqlalchemy/module.py +14 -11
  295. python_ddd_framework/sqlalchemy/module_migration.py +71 -3
  296. python_ddd_framework/sqlalchemy/repository.py +217 -13
  297. python_ddd_framework/sqlalchemy/session_provider.py +14 -1
  298. python_ddd_framework/testing/runtime.py +5 -4
  299. python_ddd_framework/unit_of_work/__init__.py +2 -1
  300. python_ddd_framework/unit_of_work/contracts.py +6 -7
  301. python_ddd_framework/unit_of_work/errors.py +11 -0
  302. python_ddd_framework/unit_of_work/lifecycle.py +19 -0
  303. python_ddd_framework/unit_of_work/manager.py +86 -32
  304. python_ddd_framework/unit_of_work/module.py +61 -0
  305. python_ddd_framework/unit_of_work/options.py +7 -1
  306. python_ddd_framework/validation/__init__.py +1 -0
  307. python_ddd_framework/validation/models.py +37 -0
  308. {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.5.1.dist-info}/METADATA +147 -44
  309. python_ddd_framework-0.5.1.dist-info/RECORD +488 -0
  310. {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.5.1.dist-info}/WHEEL +1 -1
  311. python_ddd_framework/application_services/seeding.py +0 -72
  312. python_ddd_framework/caching/catalog.py +0 -73
  313. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/cache.py.jinja +0 -10
  314. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/events.py.jinja +0 -24
  315. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration.py.jinja +0 -41
  316. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/orders.py.jinja +0 -83
  317. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/tasks.py.jinja +0 -84
  318. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/orders.py.jinja +0 -35
  319. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repository.py.jinja +0 -14
  320. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding.py.jinja +0 -19
  321. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/definitions.py.jinja +0 -24
  322. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/realtime.py.jinja +0 -36
  323. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/orders.py.jinja +0 -49
  324. python_ddd_framework-0.3.2.dist-info/RECORD +0 -355
  325. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.dockerignore → backend/.dockerignore} +0 -0
  326. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.gitignore → backend/.gitignore} +0 -0
  327. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.python-version → backend/.python-version} +0 -0
  328. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{Dockerfile → backend/Dockerfile} +0 -0
  329. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{compose.dev.yaml.jinja → backend/compose.dev.yaml.jinja} +0 -0
  330. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{compose.production.yaml.jinja → backend/compose.production.yaml.jinja} +0 -0
  331. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/__init__.py.jinja +0 -0
  332. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{tests → backend/tests}/conftest.py.jinja +0 -0
  333. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{tests → backend/tests}/host/test_http.py.jinja +0 -0
  334. /python_ddd_framework/{application_services → invocation}/signature.py +0 -0
  335. {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.5.1.dist-info}/entry_points.txt +0 -0
  336. {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.5.1.dist-info}/licenses/LICENSE +0 -0
  337. {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.5.1.dist-info}/licenses/src/python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt +0 -0
@@ -1,67 +1,31 @@
1
- """公开 Application 状态机、生命周期与非 DI 调用入口。"""
1
+ """Application 状态、通用生命周期和容器所有权。"""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
5
  import asyncio
6
- from collections.abc import AsyncIterator, Awaitable, Callable, Iterator
7
- from contextlib import asynccontextmanager, contextmanager
8
- from typing import Concatenate, Never, ParamSpec, TypeAlias, TypeVar, cast
9
- from uuid import UUID, uuid4
10
-
11
- from dishka import AsyncContainer, Scope
12
-
13
- from ..application_services import (
14
- ApplicationInvocationRejectedError,
15
- ApplicationServiceCatalog,
16
- ApplicationServiceContract,
17
- ApplicationServiceRegistration,
18
- )
19
- from ..application_services.dispatcher import _ApplicationServiceDispatcher
20
- from ..application_services.invocation import (
21
- _ScopedApplicationServiceInvoker,
22
- )
23
- from ..authorization import CurrentUser, PermissionCatalog
24
- from ..background_jobs import BackgroundJobCatalog
25
- from ..background_workers import BackgroundWorkerCatalog
26
- from ..caching import CacheCatalog
6
+ from collections.abc import Awaitable, Callable, Iterable
7
+ from functools import partial
8
+ from typing import Never
9
+ from uuid import UUID
10
+
11
+ from dishka import AsyncContainer
12
+
27
13
  from ..configuration import ConfigurationRoot
28
14
  from ..diagnostics import DiagnosticSnapshot
29
15
  from ..errors import (
30
16
  ApplicationRuntimeError,
31
17
  ApplicationStartError,
32
- ContainerCleanupError,
33
18
  InvalidApplicationStateError,
34
19
  LifecycleInvocationError,
35
20
  )
36
- from ..errors.lifecycle import (
37
- _BackgroundExecutionStartFailure,
38
- _exclude_runtime_failure,
39
- )
40
- from ..events import LocalEventCatalog
41
- from ..hosted_services import (
42
- ApplicationReadinessSnapshot,
43
- HostedServiceCatalog,
44
- HostedServiceLifecycleError,
45
- )
46
- from ..invocation.function_runtime import _invoke_callable
21
+ from ..errors.lifecycle import _exclude_runtime_failure
47
22
  from ..lifecycle import ApplicationState, ShutdownReason
48
23
  from ..modularity import ModuleRegistry
49
24
  from ..options import OptionsSnapshot
50
25
  from ..services import ServiceCatalog
51
- from ..settings import SettingCatalog
52
26
  from .runtime_hooks import _RuntimeLifecycle
53
27
  from .state import _ApplicationStateRoot
54
28
 
55
- _P = ParamSpec("_P")
56
- _R = TypeVar("_R")
57
- _TContract = TypeVar("_TContract", bound=ApplicationServiceContract)
58
- _StartCleanupFailure: TypeAlias = (
59
- LifecycleInvocationError
60
- | ContainerCleanupError
61
- | _BackgroundExecutionStartFailure
62
- | HostedServiceLifecycleError
63
- )
64
-
65
29
 
66
30
  class Application:
67
31
  """拥有单次 build 的 Module instances、状态、transition 与 diagnostics。"""
@@ -96,40 +60,10 @@ class Application:
96
60
  return self._root.require_service_catalog()
97
61
 
98
62
  @property
99
- def application_service_catalog(self) -> ApplicationServiceCatalog:
100
- return self._root.require_application_service_catalog()
101
-
102
- @property
103
- def event_catalog(self) -> LocalEventCatalog:
104
- return self._root.require_event_catalog()
105
-
106
- @property
107
- def permission_catalog(self) -> PermissionCatalog:
108
- return self._root.require_permission_catalog()
109
-
110
- @property
111
- def cache_catalog(self) -> CacheCatalog:
112
- return self._root.require_cache_catalog()
113
-
114
- @property
115
- def setting_catalog(self) -> SettingCatalog:
116
- return self._root.require_setting_catalog()
117
-
118
- @property
119
- def background_worker_catalog(self) -> BackgroundWorkerCatalog:
120
- return self._root.require_background_worker_catalog()
121
-
122
- @property
123
- def background_job_catalog(self) -> BackgroundJobCatalog:
124
- return self._root.require_background_job_catalog()
125
-
126
- @property
127
- def hosted_service_catalog(self) -> HostedServiceCatalog:
128
- return self._root.require_hosted_service_catalog()
129
-
130
- @property
131
- def readiness(self) -> ApplicationReadinessSnapshot:
132
- return self._root.readiness()
63
+ def is_ready(self) -> bool:
64
+ return self.state is ApplicationState.RUNNING and all(
65
+ participant.ready is None or participant.ready() for participant in self._root.runtimes
66
+ )
133
67
 
134
68
  @property
135
69
  def diagnostics(self) -> DiagnosticSnapshot:
@@ -148,41 +82,27 @@ class Application:
148
82
  allowed=(ApplicationState.COMPOSED,),
149
83
  )
150
84
  self._root.state = ApplicationState.STARTING
151
- if not run_services:
152
- # 数据库 CLI 只需要 Module 初始化及正式 invocation;不打开后台生产者或外部 SDK。
153
- self._root.require_background_coordinator().disable_for_database()
154
85
  lifecycle = _RuntimeLifecycle(self._root)
155
86
  started = False
156
87
  try:
157
88
  try:
89
+ self._root.registry.assert_declarations_unchanged()
90
+ for participant in self._root.runtimes:
91
+ if participant.prepare is not None:
92
+ participant.prepare(run_services)
158
93
  await lifecycle.start_modules()
159
- except LifecycleInvocationError as primary:
94
+ self._root.registry.assert_declarations_unchanged()
95
+ # 全部 Module 初始化屏障完成后才开放运行入口;部分启动也必须逆序清理。
96
+ for participant in self._root.runtimes:
97
+ self._root.touched_runtimes.append(participant)
98
+ await participant.start()
99
+ except BaseException as primary:
160
100
  await self._fail_start(lifecycle, primary=primary)
161
- self._root.require_invocation_runtime().start_accepting()
162
- try:
163
- if run_services:
164
- await self._root.require_hosted_service_coordinator().start()
165
- except BaseException as original_error:
166
- hosted_failure = (
167
- original_error
168
- if isinstance(original_error, HostedServiceLifecycleError)
169
- else HostedServiceLifecycleError(
170
- service="hosted-service-coordinator",
171
- operation="start",
172
- error_type=type(original_error).__name__,
173
- )
174
- )
175
- await self._fail_start(lifecycle, primary=hosted_failure)
176
- try:
177
- await self._root.require_background_coordinator().start()
178
- except BaseException as original_error:
179
- background_failure = _BackgroundExecutionStartFailure(original_error=original_error)
180
- await self._fail_start(lifecycle, primary=background_failure)
181
101
  self._root.state = ApplicationState.RUNNING
182
102
  started = True
183
103
  finally:
184
104
  self._root.release_transition(token)
185
- if started and (self._root.require_background_coordinator().has_runtime_failure_source):
105
+ if started and any(item.failure is not None for item in self._root.runtimes):
186
106
  self._root.runtime_monitor_task = asyncio.create_task(
187
107
  self._monitor_runtime_failure(),
188
108
  name=f"python-ddd-framework-runtime-monitor:{self.id}",
@@ -192,9 +112,7 @@ class Application:
192
112
  self,
193
113
  lifecycle: _RuntimeLifecycle,
194
114
  *,
195
- primary: LifecycleInvocationError
196
- | HostedServiceLifecycleError
197
- | _BackgroundExecutionStartFailure,
115
+ primary: BaseException,
198
116
  ) -> Never:
199
117
  self._root.state = ApplicationState.ROLLING_BACK
200
118
  # 回滚 task 由本次 start 强持有;重复取消只能在所有资源实际退出后返回。
@@ -217,64 +135,48 @@ class Application:
217
135
  self,
218
136
  lifecycle: _RuntimeLifecycle,
219
137
  *,
220
- primary: LifecycleInvocationError
221
- | HostedServiceLifecycleError
222
- | _BackgroundExecutionStartFailure,
138
+ primary: BaseException,
223
139
  ) -> ApplicationStartError:
140
+ runtime_failures = await self._stop_runtimes(ShutdownReason.START_FAILURE)
141
+ module_failures = await lifecycle.shutdown_touched(reason=ShutdownReason.START_FAILURE)
142
+ container_failures = await lifecycle.close_container(
143
+ exception=primary.original_error
144
+ if isinstance(primary, LifecycleInvocationError)
145
+ else primary
146
+ )
224
147
  if isinstance(primary, LifecycleInvocationError):
225
- module_failures = await lifecycle.shutdown_touched(reason=ShutdownReason.START_FAILURE)
226
- container_failures = await lifecycle.close_container(exception=primary.original_error)
227
- cleanup_failures: tuple[LifecycleInvocationError | ContainerCleanupError, ...] = (
228
- *module_failures,
229
- *container_failures,
230
- )
231
148
  self._root.journal.complete_failure(
232
149
  primary.failure_id,
233
- cleanup_failure_ids=tuple(failure.failure_id for failure in cleanup_failures),
234
- )
235
- return ApplicationStartError(
236
- primary=primary,
237
- cleanup_failures=cleanup_failures,
150
+ cleanup_failure_ids=tuple(failure.failure_id for failure in module_failures)
151
+ + tuple(failure.failure_id for failure in container_failures),
238
152
  )
239
153
  return ApplicationStartError(
240
154
  primary=primary,
241
- cleanup_failures=await self._rollback_runtime_start(lifecycle, primary=primary),
155
+ cleanup_failures=(*runtime_failures, *module_failures, *container_failures),
242
156
  )
243
157
 
244
- async def _rollback_runtime_start(
245
- self,
246
- lifecycle: _RuntimeLifecycle,
247
- *,
248
- primary: BaseException,
249
- ) -> tuple[_StartCleanupFailure, ...]:
250
- hosted = self._root.require_hosted_service_coordinator()
251
- hosted.close_admission()
252
- cleanup_failures: list[_StartCleanupFailure] = []
253
- # 消费者必须先实际退出,才可释放它们依赖的 HostedService。
254
- try:
255
- await self._root.require_background_coordinator().stop()
256
- except BaseException as background_error:
257
- cleanup_failures.append(
258
- _BackgroundExecutionStartFailure(original_error=background_error)
158
+ async def _stop_runtimes(self, reason: ShutdownReason) -> tuple[BaseException, ...]:
159
+ failures: list[BaseException] = []
160
+ # 每个阶段都是全局屏障;收尾与排空完成前,不释放任何参与者的依赖资源。
161
+ # 用户钩子只交给触达过 start 的参与者;预启动请求仍由独立 drain 收口。
162
+ participants = tuple(reversed(self._root.touched_runtimes))
163
+ failures.extend(_call_shutdown_callbacks(item.quiesce for item in participants))
164
+ failures.extend(
165
+ await _await_shutdown_callbacks(
166
+ partial(item.stopping, reason) for item in participants if item.stopping is not None
259
167
  )
260
- try:
261
- hosted_failures = await hosted.stop(ShutdownReason.START_FAILURE)
262
- except BaseException as hosted_error:
263
- cleanup_failures.append(
264
- HostedServiceLifecycleError(
265
- service="hosted-service-coordinator",
266
- operation="stop",
267
- error_type=type(hosted_error).__name__,
268
- )
168
+ )
169
+ failures.extend(_call_shutdown_callbacks(item.close_admission for item in participants))
170
+ failures.extend(
171
+ await _await_shutdown_callbacks(
172
+ item.drain for item in reversed(self._root.runtimes) if item.drain is not None
269
173
  )
270
- else:
271
- cleanup_failures.extend(hosted_failures)
272
- await self._root.require_invocation_runtime().stop_accepting_and_wait()
273
- cleanup_failures.extend(
274
- await lifecycle.shutdown_touched(reason=ShutdownReason.START_FAILURE)
275
174
  )
276
- cleanup_failures.extend(await lifecycle.close_container(exception=primary))
277
- return tuple(cleanup_failures)
175
+ failures.extend(
176
+ await _await_shutdown_callbacks(partial(item.stop, reason) for item in participants)
177
+ )
178
+ self._root.touched_runtimes.clear()
179
+ return tuple(failures)
278
180
 
279
181
  async def stop(self) -> None:
280
182
  if self._root.state in (
@@ -318,21 +220,7 @@ class Application:
318
220
  if self._root.runtime_failure is not None
319
221
  else ShutdownReason.NORMAL
320
222
  )
321
- hosted = self._root.require_hosted_service_coordinator()
322
- # 关闭新工作入口,等待消费者退出后再逆序停止 HostedService。
323
- hosted.close_admission()
324
- infrastructure_failures: list[BaseException] = []
325
- try:
326
- await self._root.require_background_coordinator().stop()
327
- except BaseException as background_error:
328
- infrastructure_failures.append(background_error)
329
- try:
330
- hosted_failures = await hosted.stop(reason)
331
- except BaseException as hosted_error:
332
- infrastructure_failures.append(hosted_error)
333
- else:
334
- infrastructure_failures.extend(hosted_failures)
335
- await self._root.require_invocation_runtime().stop_accepting_and_wait()
223
+ infrastructure_failures = list(await self._stop_runtimes(reason))
336
224
  module_failures = await lifecycle.shutdown_touched(
337
225
  reason=reason,
338
226
  )
@@ -398,6 +286,10 @@ class Application:
398
286
  allowed=allowed,
399
287
  )
400
288
  await self._root.termination_event.wait()
289
+ monitor = self._root.runtime_monitor_task
290
+ if monitor is not None and monitor is not asyncio.current_task():
291
+ # 终态发布与监督器退出相邻;调用者取消等待不能取消仍在收口的监督任务。
292
+ await asyncio.shield(asyncio.gather(monitor, return_exceptions=True))
401
293
  failure = self._root.termination_failure
402
294
  if failure is not None:
403
295
  raise failure
@@ -405,8 +297,9 @@ class Application:
405
297
  async def _monitor_runtime_failure(self) -> None:
406
298
  current = asyncio.current_task()
407
299
  try:
408
- # HostedService 只拥有 start/stop;持久任务等其他能力仍保留自身故障传播。
409
- failure = await self._root.require_background_coordinator().wait_for_runtime_failure()
300
+ failure = await self._wait_for_runtime_failure()
301
+ if failure is None:
302
+ return
410
303
  if self._root.runtime_failure is None:
411
304
  self._root.runtime_failure = ApplicationRuntimeError(
412
305
  component=failure.component,
@@ -425,125 +318,64 @@ class Application:
425
318
  if self._root.runtime_monitor_task is current:
426
319
  self._root.runtime_monitor_task = None
427
320
 
428
- async def call(
429
- self,
430
- function: Callable[..., Awaitable[_R]],
431
- /,
432
- *args: object,
433
- **kwargs: object,
434
- ) -> _R:
435
- """调用异步入口,业务实参显式传入,服务参数由最终 Dishka 图补齐。"""
436
- return await self.call_as(
437
- self._root.require_invocation_runtime().current_user(),
438
- function,
439
- *args,
440
- **kwargs,
441
- )
442
-
443
- async def call_as(
444
- self,
445
- current_user: CurrentUser,
446
- function: Callable[..., Awaitable[_R]],
447
- /,
448
- *args: object,
449
- **kwargs: object,
450
- ) -> _R:
451
- """可信身份与调用许可覆盖 REQUEST 创建、业务执行和最终资源释放。"""
452
- runtime = self._root.require_invocation_runtime()
453
- return await _invoke_callable(
454
- self._root.require_container(),
455
- runtime,
456
- current_user,
457
- str(uuid4()),
458
- function,
459
- args,
460
- kwargs,
461
- )
462
-
463
- async def invoke(
464
- self,
465
- method: Callable[Concatenate[_TContract, _P], Awaitable[_R]],
466
- /,
467
- *args: _P.args,
468
- **kwargs: _P.kwargs,
469
- ) -> _R:
470
- """匿名调用公开 Protocol method;可信 caller 需显式使用 invoke_as。"""
471
-
472
- return await self.invoke_as(CurrentUser.anonymous(), method, *args, **kwargs)
473
-
474
- async def invoke_as(
475
- self,
476
- current_user: CurrentUser,
477
- method: Callable[Concatenate[_TContract, _P], Awaitable[_R]],
478
- /,
479
- *args: _P.args,
480
- **kwargs: _P.kwargs,
481
- ) -> _R:
482
- """由可信 Host/test 传入已验证身份,身份只绑定当前 Application 与 asyncio Task。"""
483
-
484
- if self._root.state not in (ApplicationState.STARTING, ApplicationState.RUNNING):
485
- raise ApplicationInvocationRejectedError(
486
- reason=f"Application must be running, got {self._root.state.value}"
321
+ async def _wait_for_runtime_failure(self) -> ApplicationRuntimeError | None:
322
+ watchers = []
323
+ try:
324
+ for participant in self._root.runtimes:
325
+ if participant.failure is not None:
326
+ pending = participant.failure()
327
+ if pending is not None:
328
+ watchers.append(asyncio.ensure_future(pending))
329
+ if not watchers:
330
+ return None
331
+ done, _ = await asyncio.wait(watchers, return_when=asyncio.FIRST_COMPLETED)
332
+ failure = next(task.result() for task in watchers if task in done)
333
+ if not isinstance(failure, ApplicationRuntimeError):
334
+ raise TypeError("runtime failure callback must return ApplicationRuntimeError")
335
+ return failure
336
+ except (Exception, asyncio.CancelledError) as error:
337
+ monitor = asyncio.current_task()
338
+ if (
339
+ isinstance(error, asyncio.CancelledError)
340
+ and monitor is not None
341
+ and monitor.cancelling()
342
+ ):
343
+ # 正式 stop 取消的是 monitor;子等待者取消不能冒充应用的停止请求。
344
+ raise
345
+ # 扩展监督器自身失效也必须终止应用;错误正文不跨越公共终态边界。
346
+ return ApplicationRuntimeError(
347
+ component="runtime-supervision", error_type=type(error).__name__
487
348
  )
488
- container = self._root.require_container()
489
- invoker = await container.get(_ScopedApplicationServiceInvoker)
490
- result = await invoker.invoke(
491
- container,
492
- current_user,
493
- str(uuid4()),
494
- cast(Callable[..., Awaitable[object]], method),
495
- cast(tuple[object, ...], args),
496
- cast(dict[str, object], kwargs),
497
- )
498
- return cast(_R, result)
499
-
500
- @asynccontextmanager
501
- async def _invocation_lease_for_integration(self) -> AsyncIterator[None]:
502
- """Web/internal entry 共用的 Application-local drain lease。"""
503
-
504
- async with self._root.require_invocation_runtime().outer_lease():
505
- yield
506
-
507
- @asynccontextmanager
508
- async def _http_request_lease_for_integration(self) -> AsyncIterator[None]:
509
- # Phase 2 health contract 允许 composed/pre-start probe,但该 request 仍参与 stop drain;
510
- # lease 中的 capability 会让随后进入 dispatcher 的业务 route 继续 fail closed。
511
- async with self._root.require_invocation_runtime().outer_lease(allow_composed_request=True):
512
- yield
349
+ finally:
350
+ # 每个监督任务都归当前 Application;正常 stop 和 fatal 竞争也不遗留等待者。
351
+ for watcher in watchers:
352
+ if not watcher.done():
353
+ watcher.cancel()
354
+ await asyncio.gather(*watchers, return_exceptions=True)
355
+
356
+
357
+ def _call_shutdown_callbacks(
358
+ callbacks: Iterable[Callable[[], None] | None],
359
+ ) -> tuple[BaseException, ...]:
360
+ failures: list[BaseException] = []
361
+ for callback in callbacks:
362
+ if callback is not None:
363
+ try:
364
+ callback()
365
+ except BaseException as error:
366
+ failures.append(error)
367
+ return tuple(failures)
513
368
 
514
- @contextmanager
515
- def _caller_context_for_integration(
516
- self,
517
- current_user: CurrentUser,
518
- correlation_id: str,
519
- ) -> Iterator[None]:
520
- """HTTP bridge 的窄化入口;不公开 mutable principal 或 process-global current app。"""
521
369
 
522
- runtime = self._root.require_invocation_runtime()
523
- token = runtime.bind_caller(current_user, correlation_id)
370
+ async def _await_shutdown_callbacks(
371
+ callbacks: Iterable[Callable[[], Awaitable[tuple[BaseException, ...] | None]]],
372
+ ) -> tuple[BaseException, ...]:
373
+ failures: list[BaseException] = []
374
+ for callback in callbacks:
524
375
  try:
525
- yield
526
- finally:
527
- runtime.reset_caller(token)
528
-
529
- async def _invoke_registration(
530
- self,
531
- registration: ApplicationServiceRegistration,
532
- method_name: str,
533
- ) -> object:
534
- """框架内部 contributor 仍走 dispatcher,但不发布 public single Contract。"""
535
-
536
- if self._root.state not in (ApplicationState.STARTING, ApplicationState.RUNNING):
537
- raise ApplicationInvocationRejectedError(
538
- reason=f"Application must be running, got {self._root.state.value}"
539
- )
540
- service = self.application_service_catalog._for_registration(registration)
541
- method = service.methods.get(method_name)
542
- if method is None:
543
- raise AssertionError("internal contributor method is absent from its catalog entry")
544
- container = self._root.require_container()
545
- async with self._invocation_lease_for_integration():
546
- with self._caller_context_for_integration(CurrentUser.anonymous(), str(uuid4())):
547
- async with container(scope=Scope.REQUEST) as request_container:
548
- dispatcher = await request_container.get(_ApplicationServiceDispatcher)
549
- return await dispatcher.invoke(service, method, (), {})
376
+ result = await callback()
377
+ if result is not None:
378
+ failures.extend(result)
379
+ except BaseException as error:
380
+ failures.append(error)
381
+ return tuple(failures)
@@ -1,4 +1,4 @@
1
- """Single-Application mutable root、read-only views 与 transition ownership。"""
1
+ """Single-Application 核心状态、转换 ownership 与诊断视图。"""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -10,58 +10,36 @@ from uuid import UUID
10
10
 
11
11
  from dishka import AsyncContainer
12
12
 
13
- from ..application_services import ApplicationServiceCatalog
14
- from ..application_services.invocation import _InvocationRuntime
15
- from ..authorization import PermissionCatalog
16
- from ..background_jobs import BackgroundJobCatalog
17
- from ..background_workers import BackgroundWorkerCatalog
18
- from ..background_workers.runtime import _BackgroundCoordinator
19
- from ..caching import CacheCatalog
20
13
  from ..configuration import ConfigurationRoot
21
14
  from ..diagnostics import LifecyclePhase, LifecycleStage
22
15
  from ..diagnostics.journal import _DiagnosticJournal
23
16
  from ..errors import ApplicationRuntimeError, InvalidApplicationStateError, LifecycleInvocationError
24
- from ..events import LocalEventCatalog
25
- from ..events.contribution import EventContributionRegistry
26
- from ..hosted_services import ApplicationReadinessSnapshot, HostedServiceCatalog
27
- from ..hosted_services.runtime import _HostedServiceCoordinator
28
- from ..lifecycle import ApplicationState, ApplicationView
17
+ from ..lifecycle import ApplicationState, ApplicationView, RuntimeParticipant
29
18
  from ..modularity import ModuleDescriptor, ModuleKey, ModuleRegistry
19
+ from ..modularity.discovery import _DiscoveredType
30
20
  from ..options import OptionsSnapshot, PreOptionsSnapshot
31
21
  from ..options.registry import _OptionsRegistry
32
22
  from ..services import ServiceCatalog
33
23
  from ..services.contribution import ServiceContributionRegistry
34
- from ..settings import SettingCatalog
35
24
 
36
25
  LIFECYCLE_ERROR_TYPE = LifecycleInvocationError.__name__
37
26
 
38
27
 
39
28
  @dataclass(slots=True)
40
29
  class _ApplicationStateRoot:
41
- """所有可变事实的唯一 owner;Application 之间不共享容器、状态或 journal。"""
30
+ """只拥有核心状态;能力的 Catalog、实例和协调器分别归图内的 Module。"""
42
31
 
43
32
  application_id: UUID
44
33
  registry: ModuleRegistry
34
+ discoveries: tuple[_DiscoveredType, ...]
45
35
  configuration: ConfigurationRoot
46
36
  pre_options_registry: _OptionsRegistry
47
37
  options_registry: _OptionsRegistry
48
38
  service_registry: ServiceContributionRegistry
49
- event_registry: EventContributionRegistry
50
- permission_catalog: PermissionCatalog
51
- cache_catalog: CacheCatalog
52
- setting_catalog: SettingCatalog
53
- background_worker_catalog: BackgroundWorkerCatalog
54
- background_job_catalog: BackgroundJobCatalog
55
- hosted_service_catalog: HostedServiceCatalog
56
39
  journal: _DiagnosticJournal
57
40
  pre_options: PreOptionsSnapshot | None = None
58
41
  options: OptionsSnapshot | None = None
59
42
  service_catalog: ServiceCatalog | None = None
60
- application_service_catalog: ApplicationServiceCatalog | None = None
61
- event_catalog: LocalEventCatalog | None = None
62
- invocation_runtime: _InvocationRuntime | None = None
63
- background_coordinator: _BackgroundCoordinator | None = None
64
- hosted_service_coordinator: _HostedServiceCoordinator | None = None
65
43
  container: AsyncContainer | None = None
66
44
  state: ApplicationState = ApplicationState.COMPOSED
67
45
  transition_token: object | None = None
@@ -72,6 +50,8 @@ class _ApplicationStateRoot:
72
50
  runtime_failure: ApplicationRuntimeError | None = None
73
51
  termination_failure: BaseException | None = None
74
52
  runtime_monitor_task: asyncio.Task[None] | None = None
53
+ runtimes: tuple[RuntimeParticipant, ...] = ()
54
+ touched_runtimes: list[RuntimeParticipant] = field(default_factory=list)
75
55
 
76
56
  def require_pre_options(self) -> PreOptionsSnapshot:
77
57
  if self.pre_options is None:
@@ -95,54 +75,6 @@ class _ApplicationStateRoot:
95
75
  raise RuntimeError("Dishka container is unavailable after Application cleanup")
96
76
  return self.container
97
77
 
98
- def require_application_service_catalog(self) -> ApplicationServiceCatalog:
99
- if self.application_service_catalog is None:
100
- raise RuntimeError(
101
- "Application Service catalog is unavailable before composition completes"
102
- )
103
- return self.application_service_catalog
104
-
105
- def require_event_catalog(self) -> LocalEventCatalog:
106
- if self.event_catalog is None:
107
- raise RuntimeError("Local Event catalog is unavailable before composition completes")
108
- return self.event_catalog
109
-
110
- def require_permission_catalog(self) -> PermissionCatalog:
111
- return self.permission_catalog
112
-
113
- def require_cache_catalog(self) -> CacheCatalog:
114
- return self.cache_catalog
115
-
116
- def require_setting_catalog(self) -> SettingCatalog:
117
- return self.setting_catalog
118
-
119
- def require_background_worker_catalog(self) -> BackgroundWorkerCatalog:
120
- return self.background_worker_catalog
121
-
122
- def require_background_job_catalog(self) -> BackgroundJobCatalog:
123
- return self.background_job_catalog
124
-
125
- def require_invocation_runtime(self) -> _InvocationRuntime:
126
- if self.invocation_runtime is None:
127
- raise RuntimeError("Application invocation runtime is unavailable")
128
- return self.invocation_runtime
129
-
130
- def require_background_coordinator(self) -> _BackgroundCoordinator:
131
- if self.background_coordinator is None:
132
- raise RuntimeError("Application background coordinator is unavailable")
133
- return self.background_coordinator
134
-
135
- def require_hosted_service_catalog(self) -> HostedServiceCatalog:
136
- return self.hosted_service_catalog
137
-
138
- def require_hosted_service_coordinator(self) -> _HostedServiceCoordinator:
139
- if self.hosted_service_coordinator is None:
140
- raise RuntimeError("Hosted Service coordinator is unavailable")
141
- return self.hosted_service_coordinator
142
-
143
- def readiness(self) -> ApplicationReadinessSnapshot:
144
- return self.require_hosted_service_coordinator().readiness()
145
-
146
78
  def view(self, state: ApplicationState | None) -> ApplicationView:
147
79
  return ApplicationView(
148
80
  application_id=self.application_id,