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
@@ -1,67 +1,29 @@
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
6
+ from typing import Never
7
+ from uuid import UUID
10
8
 
11
- from dishka import AsyncContainer, Scope
9
+ from dishka import AsyncContainer
12
10
 
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
27
11
  from ..configuration import ConfigurationRoot
28
12
  from ..diagnostics import DiagnosticSnapshot
29
13
  from ..errors import (
30
14
  ApplicationRuntimeError,
31
15
  ApplicationStartError,
32
- ContainerCleanupError,
33
16
  InvalidApplicationStateError,
34
17
  LifecycleInvocationError,
35
18
  )
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
19
+ from ..errors.lifecycle import _exclude_runtime_failure
47
20
  from ..lifecycle import ApplicationState, ShutdownReason
48
21
  from ..modularity import ModuleRegistry
49
22
  from ..options import OptionsSnapshot
50
23
  from ..services import ServiceCatalog
51
- from ..settings import SettingCatalog
52
24
  from .runtime_hooks import _RuntimeLifecycle
53
25
  from .state import _ApplicationStateRoot
54
26
 
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
27
 
66
28
  class Application:
67
29
  """拥有单次 build 的 Module instances、状态、transition 与 diagnostics。"""
@@ -96,40 +58,10 @@ class Application:
96
58
  return self._root.require_service_catalog()
97
59
 
98
60
  @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()
61
+ def is_ready(self) -> bool:
62
+ return self.state is ApplicationState.RUNNING and all(
63
+ participant.ready is None or participant.ready() for participant in self._root.runtimes
64
+ )
133
65
 
134
66
  @property
135
67
  def diagnostics(self) -> DiagnosticSnapshot:
@@ -148,41 +80,27 @@ class Application:
148
80
  allowed=(ApplicationState.COMPOSED,),
149
81
  )
150
82
  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
83
  lifecycle = _RuntimeLifecycle(self._root)
155
84
  started = False
156
85
  try:
157
86
  try:
87
+ self._root.registry.assert_declarations_unchanged()
88
+ for participant in self._root.runtimes:
89
+ if participant.prepare is not None:
90
+ participant.prepare(run_services)
158
91
  await lifecycle.start_modules()
159
- except LifecycleInvocationError as primary:
92
+ self._root.registry.assert_declarations_unchanged()
93
+ # 全部 Module 初始化屏障完成后才开放运行入口;部分启动也必须逆序清理。
94
+ for participant in self._root.runtimes:
95
+ self._root.touched_runtimes.append(participant)
96
+ await participant.start()
97
+ except BaseException as primary:
160
98
  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
99
  self._root.state = ApplicationState.RUNNING
182
100
  started = True
183
101
  finally:
184
102
  self._root.release_transition(token)
185
- if started and (self._root.require_background_coordinator().has_runtime_failure_source):
103
+ if started and any(item.failure is not None for item in self._root.runtimes):
186
104
  self._root.runtime_monitor_task = asyncio.create_task(
187
105
  self._monitor_runtime_failure(),
188
106
  name=f"python-ddd-framework-runtime-monitor:{self.id}",
@@ -192,9 +110,7 @@ class Application:
192
110
  self,
193
111
  lifecycle: _RuntimeLifecycle,
194
112
  *,
195
- primary: LifecycleInvocationError
196
- | HostedServiceLifecycleError
197
- | _BackgroundExecutionStartFailure,
113
+ primary: BaseException,
198
114
  ) -> Never:
199
115
  self._root.state = ApplicationState.ROLLING_BACK
200
116
  # 回滚 task 由本次 start 强持有;重复取消只能在所有资源实际退出后返回。
@@ -217,64 +133,50 @@ class Application:
217
133
  self,
218
134
  lifecycle: _RuntimeLifecycle,
219
135
  *,
220
- primary: LifecycleInvocationError
221
- | HostedServiceLifecycleError
222
- | _BackgroundExecutionStartFailure,
136
+ primary: BaseException,
223
137
  ) -> ApplicationStartError:
138
+ runtime_failures = await self._stop_runtimes(ShutdownReason.START_FAILURE)
139
+ module_failures = await lifecycle.shutdown_touched(reason=ShutdownReason.START_FAILURE)
140
+ container_failures = await lifecycle.close_container(
141
+ exception=primary.original_error
142
+ if isinstance(primary, LifecycleInvocationError)
143
+ else primary
144
+ )
224
145
  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
146
  self._root.journal.complete_failure(
232
147
  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,
148
+ cleanup_failure_ids=tuple(failure.failure_id for failure in module_failures)
149
+ + tuple(failure.failure_id for failure in container_failures),
238
150
  )
239
151
  return ApplicationStartError(
240
152
  primary=primary,
241
- cleanup_failures=await self._rollback_runtime_start(lifecycle, primary=primary),
153
+ cleanup_failures=(*runtime_failures, *module_failures, *container_failures),
242
154
  )
243
155
 
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)
259
- )
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
- )
269
- )
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
- )
276
- cleanup_failures.extend(await lifecycle.close_container(exception=primary))
277
- return tuple(cleanup_failures)
156
+ async def _stop_runtimes(self, reason: ShutdownReason) -> tuple[BaseException, ...]:
157
+ failures: list[BaseException] = []
158
+ # 先统一关闭外部准入,再逆序排空依赖;一个能力失败不能阻止其余能力释放资源。
159
+ # stop 只交给触达过 start 的参与者;预启动入口已接受的资源由独立 drain 收口。
160
+ participants = tuple(reversed(self._root.touched_runtimes))
161
+ for participant in participants:
162
+ if participant.close_admission is not None:
163
+ try:
164
+ participant.close_admission()
165
+ except BaseException as error:
166
+ failures.append(error)
167
+ for participant in participants:
168
+ try:
169
+ failures.extend(await participant.stop(reason))
170
+ except BaseException as error:
171
+ failures.append(error)
172
+ self._root.touched_runtimes.clear()
173
+ for participant in reversed(self._root.runtimes):
174
+ if participant.drain is not None:
175
+ try:
176
+ await participant.drain()
177
+ except BaseException as error:
178
+ failures.append(error)
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,37 @@ 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}"
487
- )
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
513
-
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
-
522
- runtime = self._root.require_invocation_runtime()
523
- token = runtime.bind_caller(current_user, correlation_id)
321
+ async def _wait_for_runtime_failure(self) -> ApplicationRuntimeError | None:
322
+ watchers = []
524
323
  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}"
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__
539
348
  )
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, (), {})
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)
@@ -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,
@@ -1,9 +1,4 @@
1
- from ..invocation import ApplicationInvocationContext
2
- from .catalog import (
3
- ApplicationServiceCatalog,
4
- ApplicationServiceMethod,
5
- ApplicationServiceRegistration,
6
- )
1
+ from .catalog import ApplicationServiceCatalog, ApplicationServiceRegistration
7
2
  from .contracts import (
8
3
  ApplicationService,
9
4
  ApplicationServiceContract,
@@ -20,35 +15,24 @@ from .errors import (
20
15
  CrossTaskInvocationError,
21
16
  NestedConnectionError,
22
17
  )
23
- from .execution import MethodInvocation
24
- from .interceptors import InterceptionStage, Interceptor, InterceptorRegistration
25
- from .policies import disable_validation, enable_validation
26
- from .seeding import DataSeedContributor, DataSeeder, DataSeedError
18
+ from .pagination import PagedAndSortedResultRequestDto, PagedResult, PagedResultRequestDto
27
19
 
28
20
  __all__ = (
29
21
  "ApplicationInvocationCleanupError",
30
- "ApplicationInvocationContext",
31
22
  "ApplicationInvocationRejectedError",
32
23
  "ApplicationService",
33
24
  "ApplicationServiceCatalog",
34
25
  "ApplicationServiceContract",
35
26
  "ApplicationServiceDefinitionError",
36
- "ApplicationServiceMethod",
37
27
  "ApplicationServiceMethodError",
38
28
  "ApplicationServiceRegistration",
39
29
  "ApplicationServiceValidationError",
40
30
  "ApplicationServiceValidationIssue",
41
31
  "CrossTaskInvocationError",
42
- "DataSeedContributor",
43
- "DataSeedError",
44
- "DataSeeder",
45
32
  "IntegrationService",
46
- "InterceptionStage",
47
- "Interceptor",
48
- "InterceptorRegistration",
49
- "MethodInvocation",
50
33
  "NestedConnectionError",
34
+ "PagedAndSortedResultRequestDto",
35
+ "PagedResult",
36
+ "PagedResultRequestDto",
51
37
  "application_service",
52
- "disable_validation",
53
- "enable_validation",
54
38
  )