python-ddd-framework 0.3.2__py3-none-any.whl → 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (334) hide show
  1. python_ddd_framework/__init__.py +139 -21
  2. python_ddd_framework/application/builder.py +23 -78
  3. python_ddd_framework/application/composition.py +4 -22
  4. python_ddd_framework/application/runtime.py +96 -291
  5. python_ddd_framework/application/state.py +7 -75
  6. python_ddd_framework/application_services/__init__.py +5 -21
  7. python_ddd_framework/{services/application_bindings.py → application_services/bindings.py} +52 -9
  8. python_ddd_framework/application_services/catalog.py +36 -288
  9. python_ddd_framework/application_services/contracts.py +14 -13
  10. python_ddd_framework/application_services/dispatcher.py +19 -269
  11. python_ddd_framework/application_services/invocation.py +126 -7
  12. python_ddd_framework/application_services/module.py +168 -0
  13. python_ddd_framework/application_services/pagination.py +21 -0
  14. python_ddd_framework/auditing/control.py +1 -1
  15. python_ddd_framework/auditing/module.py +15 -0
  16. python_ddd_framework/authorization/__init__.py +2 -0
  17. python_ddd_framework/authorization/contracts.py +7 -2
  18. python_ddd_framework/authorization/definitions.py +5 -0
  19. python_ddd_framework/authorization/module.py +40 -0
  20. python_ddd_framework/authorization/options.py +7 -0
  21. python_ddd_framework/background_execution/child.py +13 -3
  22. python_ddd_framework/background_execution/declarations.py +45 -0
  23. python_ddd_framework/background_execution/lifecycle.py +12 -0
  24. python_ddd_framework/background_execution/module.py +107 -1
  25. python_ddd_framework/background_jobs/catalog.py +59 -42
  26. python_ddd_framework/background_jobs/contracts.py +16 -19
  27. python_ddd_framework/background_jobs/pgqueuer/enqueue.py +4 -0
  28. python_ddd_framework/background_jobs/pgqueuer/module.py +2 -1
  29. python_ddd_framework/background_jobs/pgqueuer/runtime.py +7 -7
  30. python_ddd_framework/background_workers/catalog.py +23 -36
  31. python_ddd_framework/background_workers/contracts.py +3 -0
  32. python_ddd_framework/background_workers/execution.py +1 -1
  33. python_ddd_framework/background_workers/runtime.py +38 -1
  34. python_ddd_framework/caching/__init__.py +7 -6
  35. python_ddd_framework/caching/contracts.py +65 -67
  36. python_ddd_framework/caching/errors.py +0 -7
  37. python_ddd_framework/caching/module.py +18 -0
  38. python_ddd_framework/caching/naming.py +29 -0
  39. python_ddd_framework/caching/options.py +38 -0
  40. python_ddd_framework/caching/unit_of_work.py +46 -0
  41. python_ddd_framework/cli/__init__.py +31 -6
  42. python_ddd_framework/cli/project.py +68 -6
  43. python_ddd_framework/cli/runtime.py +1 -1
  44. python_ddd_framework/developer_kit/generation.py +75 -22
  45. python_ddd_framework/developer_kit/project_metadata.py +2 -4
  46. python_ddd_framework/developer_kit/templates/basic/cookiecutter.json +4 -0
  47. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/README.md +45 -0
  48. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/__init__.py.jinja +0 -0
  49. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/contracts/__init__.py.jinja +0 -0
  50. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/contracts/conversion_service.py.jinja +7 -0
  51. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/module.py.jinja +38 -0
  52. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/services/__init__.py.jinja +0 -0
  53. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/services/default_conversion_service.py.jinja +10 -0
  54. python_ddd_framework/developer_kit/templates/module/cookiecutter.json +1 -1
  55. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/README.md +41 -49
  56. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/__init__.py.jinja +1 -0
  57. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/__init__.py.jinja +1 -0
  58. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/handler.py.jinja +45 -0
  59. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/payload.py.jinja +8 -0
  60. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/__init__.py.jinja +1 -0
  61. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/handler.py.jinja +35 -0
  62. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/payload.py.jinja +5 -0
  63. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/schedule.py.jinja +15 -0
  64. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/__init__.py.jinja +1 -0
  65. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/order_maintenance_worker.py.jinja +28 -0
  66. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/order_statistics_worker.py.jinja +26 -0
  67. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/__init__.py.jinja +1 -0
  68. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/order_cache.py.jinja +9 -0
  69. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/statistics_cache.py.jinja +9 -0
  70. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/event_handlers/__init__.py.jinja +1 -0
  71. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/event_handlers/order_changed_handler.py.jinja +23 -0
  72. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/__init__.py.jinja +1 -0
  73. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/{integration.py.jinja → hosted_services/order_integration_service.py.jinja} +8 -2
  74. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/order_observation_handler.py.jinja +16 -0
  75. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration_services/__init__.py.jinja +1 -0
  76. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration_services/order_reporting_service.py.jinja +16 -0
  77. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/interceptors/__init__.py.jinja +1 -0
  78. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/interceptors/order_timing_interceptor.py.jinja +17 -0
  79. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/module.py.jinja +105 -10
  80. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/options/__init__.py.jinja +1 -0
  81. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/{options.py.jinja → options/order_options.py.jinja} +1 -1
  82. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/__init__.py.jinja +1 -0
  83. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_approval_service.py.jinja +65 -0
  84. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_management_service.py.jinja +35 -0
  85. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_query_service.py.jinja +41 -0
  86. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/__init__.py.jinja +1 -0
  87. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/approval_setting_observer.py.jinja +17 -0
  88. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/__init__.py.jinja +1 -0
  89. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/approve_order.py.jinja +6 -0
  90. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/create_order.py.jinja +7 -0
  91. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/integration_services/__init__.py.jinja +1 -0
  92. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/integration_services/order_reporting_service.py.jinja +7 -0
  93. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/module.py.jinja +39 -2
  94. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/__init__.py.jinja +1 -0
  95. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_approval_service.py.jinja +13 -0
  96. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_management_service.py.jinja +10 -0
  97. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_query_service.py.jinja +12 -0
  98. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/__init__.py.jinja +1 -0
  99. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/order_statistics_snapshot.py.jinja +9 -0
  100. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/order_view.py.jinja +12 -0
  101. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/entities/__init__.py.jinja +1 -0
  102. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/{orders.py.jinja → entities/order.py.jinja} +10 -18
  103. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/events/__init__.py.jinja +1 -0
  104. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/events/order_changed.py.jinja +13 -0
  105. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/module.py.jinja +42 -3
  106. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repositories/__init__.py.jinja +1 -0
  107. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repositories/order_repository.py.jinja +17 -0
  108. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding/__init__.py.jinja +1 -0
  109. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding/order_seed_contributor.py.jinja +22 -0
  110. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/services/__init__.py.jinja +1 -0
  111. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/services/order_approval_service.py.jinja +23 -0
  112. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/settings/__init__.py.jinja +1 -0
  113. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/{settings.py.jinja → settings/approval_settings.py.jinja} +3 -2
  114. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/value_objects/__init__.py.jinja +1 -0
  115. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/value_objects/order_title.py.jinja +10 -0
  116. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/constants/__init__.py.jinja +1 -0
  117. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/constants/order_constants.py.jinja +3 -0
  118. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/enums/__init__.py.jinja +1 -0
  119. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/enums/order_status.py.jinja +6 -0
  120. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/errors/__init__.py.jinja +1 -0
  121. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/errors/order_errors.py.jinja +11 -0
  122. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/__init__.py.jinja +1 -0
  123. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/order_messages.py.jinja +26 -0
  124. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/order_observation.py.jinja +13 -0
  125. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/module.py.jinja +40 -1
  126. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions/__init__.py.jinja +1 -0
  127. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/{permissions.py.jinja → permissions/order_permission_provider.py.jinja} +6 -2
  128. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions/order_permissions.py.jinja +7 -0
  129. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/value_objects/__init__.py.jinja +1 -0
  130. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/value_objects/money.py.jinja +18 -0
  131. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/filters/__init__.py.jinja +1 -0
  132. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/filters/export_filter.py.jinja +15 -0
  133. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/models/__init__.py.jinja +1 -0
  134. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/models/refresh_orders.py.jinja +5 -0
  135. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/module.py.jinja +85 -7
  136. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/routers/__init__.py.jinja +1 -0
  137. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/{files.py.jinja → routers/order_files.py.jinja} +23 -20
  138. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/websockets/__init__.py.jinja +1 -0
  139. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/websockets/order_socket.py.jinja +44 -0
  140. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/migrations/__init__.py.jinja +1 -1
  141. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/__init__.py.jinja +1 -7
  142. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/base.py.jinja +7 -0
  143. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/{orders.py.jinja → order_model.py.jinja} +7 -3
  144. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/module.py.jinja +42 -5
  145. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/order_repository.py.jinja +79 -0
  146. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/__init__.py.jinja +1 -0
  147. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/test_domain.py.jinja +4 -3
  148. python_ddd_framework/developer_kit/templates/project/cookiecutter.json +1 -1
  149. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/AGENTS.md +16 -3
  150. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/README.md +29 -22
  151. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{app.development.yaml.jinja → backend/app.development.yaml.jinja} +2 -0
  152. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{pyproject.toml.jinja → backend/pyproject.toml.jinja} +1 -1
  153. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/main.py.jinja +2 -0
  154. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/module.py.jinja +40 -3
  155. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/architecture.md +40 -10
  156. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/development.md +134 -23
  157. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/scripts/.gitkeep +0 -0
  158. python_ddd_framework/distributed_lock/contracts.py +4 -1
  159. python_ddd_framework/domain/__init__.py +2 -0
  160. python_ddd_framework/domain/services.py +11 -0
  161. python_ddd_framework/errors/business.py +10 -1
  162. python_ddd_framework/errors/lifecycle.py +3 -31
  163. python_ddd_framework/events/__init__.py +5 -2
  164. python_ddd_framework/events/aggregate.py +3 -3
  165. python_ddd_framework/events/catalog.py +12 -11
  166. python_ddd_framework/events/contracts.py +6 -1
  167. python_ddd_framework/events/contribution.py +1 -52
  168. python_ddd_framework/events/entities.py +23 -0
  169. python_ddd_framework/events/module.py +97 -0
  170. python_ddd_framework/events/runtime.py +80 -29
  171. python_ddd_framework/events/subscription.py +36 -0
  172. python_ddd_framework/events/unit_of_work.py +25 -0
  173. python_ddd_framework/extensions/__init__.py +22 -0
  174. python_ddd_framework/extensions/catalog.py +198 -0
  175. python_ddd_framework/extensions/contracts.py +85 -0
  176. python_ddd_framework/extensions/models.py +142 -0
  177. python_ddd_framework/extensions/module.py +21 -0
  178. python_ddd_framework/fastapi/__init__.py +2 -0
  179. python_ddd_framework/fastapi/action.py +1 -2
  180. python_ddd_framework/fastapi/adapter.py +16 -0
  181. python_ddd_framework/fastapi/application_services.py +43 -25
  182. python_ddd_framework/fastapi/background.py +2 -1
  183. python_ddd_framework/fastapi/contracts.py +26 -8
  184. python_ddd_framework/fastapi/health.py +1 -1
  185. python_ddd_framework/fastapi/module.py +14 -0
  186. python_ddd_framework/fastapi/parameters.py +3 -18
  187. python_ddd_framework/fastapi/realtime/connection.py +10 -7
  188. python_ddd_framework/fastapi/realtime/module.py +3 -0
  189. python_ddd_framework/fastapi/realtime/runtime.py +10 -24
  190. python_ddd_framework/fastapi/request_context.py +2 -1
  191. python_ddd_framework/fastapi/routing.py +2 -1
  192. python_ddd_framework/fastapi/server.py +8 -0
  193. python_ddd_framework/fastapi/settings.py +2 -1
  194. python_ddd_framework/hosted_services/bridge.py +20 -1
  195. python_ddd_framework/hosted_services/catalog.py +6 -16
  196. python_ddd_framework/hosted_services/contracts.py +4 -0
  197. python_ddd_framework/hosted_services/declarations.py +15 -0
  198. python_ddd_framework/hosted_services/module.py +69 -0
  199. python_ddd_framework/hosted_services/runtime.py +30 -1
  200. python_ddd_framework/identity/__init__.py +24 -0
  201. python_ddd_framework/identity/application.py +38 -90
  202. python_ddd_framework/identity/contracts.py +181 -16
  203. python_ddd_framework/identity/http_api.py +79 -2
  204. python_ddd_framework/identity/management.py +166 -0
  205. python_ddd_framework/identity/module.py +12 -4
  206. python_ddd_framework/identity/services.py +29 -16
  207. python_ddd_framework/identity/sqlalchemy/migrations/0003_extra_properties.py +26 -0
  208. python_ddd_framework/identity/sqlalchemy/migrations/0004_concurrency_version.py +29 -0
  209. python_ddd_framework/identity/sqlalchemy/models.py +21 -2
  210. python_ddd_framework/identity/sqlalchemy/module.py +11 -2
  211. python_ddd_framework/identity/sqlalchemy/repository.py +458 -0
  212. python_ddd_framework/identity/sqlalchemy/session_security.py +88 -0
  213. python_ddd_framework/identity/sqlalchemy/stores.py +69 -290
  214. python_ddd_framework/identity/tokens.py +2 -5
  215. python_ddd_framework/{application_services/validation.py → invocation/arguments.py} +8 -6
  216. python_ddd_framework/invocation/contribution.py +61 -0
  217. python_ddd_framework/invocation/dispatcher.py +291 -0
  218. python_ddd_framework/invocation/entries.py +2 -25
  219. python_ddd_framework/invocation/entrypoints.py +126 -0
  220. python_ddd_framework/{application_services → invocation}/execution.py +8 -5
  221. python_ddd_framework/invocation/function_runtime.py +3 -1
  222. python_ddd_framework/invocation/interception.py +113 -42
  223. python_ddd_framework/{application_services → invocation}/interceptors.py +2 -2
  224. python_ddd_framework/invocation/managed_proxy.py +116 -0
  225. python_ddd_framework/invocation/managed_services.py +250 -0
  226. python_ddd_framework/invocation/methods.py +345 -0
  227. python_ddd_framework/invocation/module.py +113 -0
  228. python_ddd_framework/{application_services → invocation}/policies.py +8 -1
  229. python_ddd_framework/invocation/validation.py +25 -0
  230. python_ddd_framework/lifecycle/__init__.py +2 -0
  231. python_ddd_framework/lifecycle/composition.py +5 -2
  232. python_ddd_framework/lifecycle/participants.py +33 -0
  233. python_ddd_framework/messaging/__init__.py +25 -0
  234. python_ddd_framework/messaging/channel.py +205 -0
  235. python_ddd_framework/messaging/contracts.py +69 -0
  236. python_ddd_framework/messaging/module.py +101 -0
  237. python_ddd_framework/messaging/options.py +13 -0
  238. python_ddd_framework/messaging/receipt.py +27 -0
  239. python_ddd_framework/messaging/runtime.py +124 -0
  240. python_ddd_framework/modularity/__init__.py +11 -1
  241. python_ddd_framework/modularity/contracts.py +47 -16
  242. python_ddd_framework/modularity/discovery.py +21 -2
  243. python_ddd_framework/modularity/graph.py +45 -1
  244. python_ddd_framework/modularity/registry.py +83 -3
  245. python_ddd_framework/notifications/__init__.py +2 -0
  246. python_ddd_framework/notifications/catalog.py +3 -2
  247. python_ddd_framework/notifications/declarations.py +18 -0
  248. python_ddd_framework/notifications/module.py +21 -0
  249. python_ddd_framework/observability/logging.py +3 -0
  250. python_ddd_framework/observability/tracing.py +3 -0
  251. python_ddd_framework/options/contribution.py +12 -0
  252. python_ddd_framework/options/registry.py +44 -11
  253. python_ddd_framework/realtime/__init__.py +2 -1
  254. python_ddd_framework/realtime/contracts.py +3 -7
  255. python_ddd_framework/realtime/messages.py +26 -1
  256. python_ddd_framework/redis/cache.py +243 -0
  257. python_ddd_framework/redis/cache_scripts.py +40 -0
  258. python_ddd_framework/redis/distributed_lock.py +10 -2
  259. python_ddd_framework/redis/module.py +13 -4
  260. python_ddd_framework/redis/notifications.py +5 -2
  261. python_ddd_framework/redis/runtime.py +1 -79
  262. python_ddd_framework/seeding/__init__.py +13 -0
  263. python_ddd_framework/seeding/contracts.py +45 -0
  264. python_ddd_framework/seeding/module.py +73 -0
  265. python_ddd_framework/seeding/runtime.py +63 -0
  266. python_ddd_framework/services/arbitration.py +23 -0
  267. python_ddd_framework/services/binding.py +2 -3
  268. python_ddd_framework/services/composition.py +108 -0
  269. python_ddd_framework/services/contribution.py +44 -54
  270. python_ddd_framework/services/convention.py +53 -181
  271. python_ddd_framework/services/fixed_lifetime.py +12 -15
  272. python_ddd_framework/services/framework_provider.py +3 -158
  273. python_ddd_framework/services/native_graph.py +35 -60
  274. python_ddd_framework/services/provider.py +59 -4
  275. python_ddd_framework/services/repository.py +6 -0
  276. python_ddd_framework/services/runtime.py +129 -215
  277. python_ddd_framework/settings/catalog.py +5 -4
  278. python_ddd_framework/settings/declarations.py +18 -0
  279. python_ddd_framework/settings/definitions.py +5 -0
  280. python_ddd_framework/settings/manager.py +1 -1
  281. python_ddd_framework/settings/module.py +50 -1
  282. python_ddd_framework/settings/notifications.py +4 -2
  283. python_ddd_framework/settings/refresh_module.py +9 -2
  284. python_ddd_framework/settings/sqlalchemy/module.py +2 -1
  285. python_ddd_framework/sqlalchemy/__init__.py +12 -0
  286. python_ddd_framework/sqlalchemy/alembic_runtime/env.py +1 -0
  287. python_ddd_framework/sqlalchemy/auditing.py +7 -1
  288. python_ddd_framework/sqlalchemy/extensions.py +186 -0
  289. python_ddd_framework/sqlalchemy/metadata.py +110 -6
  290. python_ddd_framework/sqlalchemy/migration.py +61 -8
  291. python_ddd_framework/sqlalchemy/migration_operations.py +86 -0
  292. python_ddd_framework/sqlalchemy/module.py +14 -11
  293. python_ddd_framework/sqlalchemy/module_migration.py +71 -3
  294. python_ddd_framework/sqlalchemy/repository.py +217 -13
  295. python_ddd_framework/sqlalchemy/session_provider.py +14 -1
  296. python_ddd_framework/testing/runtime.py +5 -4
  297. python_ddd_framework/unit_of_work/__init__.py +2 -1
  298. python_ddd_framework/unit_of_work/contracts.py +6 -7
  299. python_ddd_framework/unit_of_work/errors.py +11 -0
  300. python_ddd_framework/unit_of_work/lifecycle.py +19 -0
  301. python_ddd_framework/unit_of_work/manager.py +86 -32
  302. python_ddd_framework/unit_of_work/module.py +61 -0
  303. python_ddd_framework/unit_of_work/options.py +7 -1
  304. python_ddd_framework/validation/__init__.py +1 -0
  305. python_ddd_framework/validation/models.py +37 -0
  306. {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/METADATA +139 -44
  307. python_ddd_framework-0.4.0.dist-info/RECORD +488 -0
  308. {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/WHEEL +1 -1
  309. python_ddd_framework/application_services/seeding.py +0 -72
  310. python_ddd_framework/caching/catalog.py +0 -73
  311. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/cache.py.jinja +0 -10
  312. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/events.py.jinja +0 -24
  313. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/orders.py.jinja +0 -83
  314. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/tasks.py.jinja +0 -84
  315. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/orders.py.jinja +0 -35
  316. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repository.py.jinja +0 -14
  317. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding.py.jinja +0 -19
  318. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/definitions.py.jinja +0 -24
  319. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/realtime.py.jinja +0 -36
  320. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/orders.py.jinja +0 -49
  321. python_ddd_framework-0.3.2.dist-info/RECORD +0 -355
  322. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.dockerignore → backend/.dockerignore} +0 -0
  323. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.gitignore → backend/.gitignore} +0 -0
  324. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.python-version → backend/.python-version} +0 -0
  325. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{Dockerfile → backend/Dockerfile} +0 -0
  326. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{compose.dev.yaml.jinja → backend/compose.dev.yaml.jinja} +0 -0
  327. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{compose.production.yaml.jinja → backend/compose.production.yaml.jinja} +0 -0
  328. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/__init__.py.jinja +0 -0
  329. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{tests → backend/tests}/conftest.py.jinja +0 -0
  330. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{tests → backend/tests}/host/test_http.py.jinja +0 -0
  331. /python_ddd_framework/{application_services → invocation}/signature.py +0 -0
  332. {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/entry_points.txt +0 -0
  333. {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/licenses/LICENSE +0 -0
  334. {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/licenses/src/python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt +0 -0
@@ -0,0 +1,291 @@
1
+ """受管理服务代理与业务管线编排;不拥有 Application admission 或 Session。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import time
7
+ from collections.abc import Awaitable, Callable
8
+ from datetime import UTC, datetime
9
+ from uuid import uuid4
10
+
11
+ from dishka import AsyncContainer
12
+ from dishka.exceptions import NoFactoryError
13
+
14
+ from ..application_services.errors import (
15
+ ApplicationServiceValidationError,
16
+ ApplicationServiceValidationIssue,
17
+ CrossTaskInvocationError,
18
+ NestedConnectionError,
19
+ )
20
+ from ..application_services.invocation import _current_task, _InvocationRuntime
21
+ from ..auditing import ApplicationServiceAuditRecord, AuditSink
22
+ from ..auditing.control import AuditingControl, AuditingOptions
23
+ from ..authorization import AuthorizationOptions, PermissionChecker
24
+ from ..authorization.contracts import _AuthorizationRequirement
25
+ from ..authorization.errors import (
26
+ PermissionCheckerUnavailableError,
27
+ PermissionDeniedError,
28
+ UnauthenticatedError,
29
+ )
30
+ from ..invocation.arguments import _validate_arguments
31
+ from ..invocation.execution import _InterceptorPipeline
32
+ from ..invocation.interceptors import InterceptionStage
33
+ from ..observability.context import invocation_span
34
+ from ..options import Options
35
+ from ..unit_of_work import UnitOfWorkManager
36
+ from .methods import _CompiledMethod
37
+
38
+ _LOGGER = logging.getLogger(__name__)
39
+
40
+ _MethodTarget = Callable[[AsyncContainer, tuple[object, ...], dict[str, object]], Awaitable[object]]
41
+
42
+
43
+ class _MethodDispatcher:
44
+ """共享调用阶段;实例解析和 HTTP 身份由对应入口拥有。"""
45
+
46
+ __slots__ = ("_auditing_enabled", "_container", "_runtime", "_unit_of_work_enabled")
47
+
48
+ def __init__(
49
+ self,
50
+ container: AsyncContainer,
51
+ runtime: _InvocationRuntime,
52
+ *,
53
+ unit_of_work_enabled: bool = True,
54
+ auditing_enabled: bool = True,
55
+ ) -> None:
56
+ self._container = container
57
+ self._runtime = runtime
58
+ self._unit_of_work_enabled = unit_of_work_enabled
59
+ self._auditing_enabled = auditing_enabled
60
+
61
+ async def invoke_method(
62
+ self,
63
+ method: _CompiledMethod,
64
+ args: tuple[object, ...],
65
+ kwargs: dict[str, object],
66
+ target: _MethodTarget,
67
+ connection_name: str,
68
+ ) -> object:
69
+ pipeline = _InterceptorPipeline(method.descriptor, method.interceptor_steps, args, kwargs)
70
+ return await self._invoke_entry(
71
+ method,
72
+ pipeline,
73
+ lambda: self._invoke_validated(method, pipeline, target, connection_name),
74
+ )
75
+
76
+ async def _invoke_validated(
77
+ self,
78
+ method: _CompiledMethod,
79
+ pipeline: _InterceptorPipeline,
80
+ target: _MethodTarget,
81
+ connection_name: str,
82
+ ) -> object:
83
+ async def validate() -> object:
84
+ await self._validate_pipeline(method, pipeline)
85
+ return await pipeline.run(
86
+ InterceptionStage.UNIT_OF_WORK,
87
+ self._container,
88
+ lambda: self._invoke_work(method, pipeline, target, connection_name),
89
+ )
90
+
91
+ return await pipeline.run(InterceptionStage.VALIDATION, self._container, validate)
92
+
93
+ async def _invoke_work(
94
+ self,
95
+ method: _CompiledMethod,
96
+ pipeline: _InterceptorPipeline,
97
+ target: _MethodTarget,
98
+ connection_name: str,
99
+ ) -> object:
100
+ async def invoke(container: AsyncContainer) -> object:
101
+ async def execute() -> object:
102
+ # METHOD 扩展改写参数后仍须通过同一个验证边界。
103
+ await self._validate_pipeline(method, pipeline)
104
+ return await target(container, pipeline.arguments.args, pipeline.arguments.kwargs)
105
+
106
+ result = await pipeline.run(InterceptionStage.METHOD, container, execute)
107
+ return (
108
+ method.result_adapter.validate_python(result) if method.result_adapter else result
109
+ )
110
+
111
+ ambient = self._runtime.current()
112
+ if ambient is not None:
113
+ if _current_task() is not ambient.owner_task:
114
+ raise CrossTaskInvocationError
115
+ if connection_name != ambient.context.connection_name:
116
+ raise NestedConnectionError(
117
+ outer=ambient.context.connection_name, requested=connection_name
118
+ )
119
+ if not self._unit_of_work_enabled:
120
+ return await invoke(ambient.action_container)
121
+ manager = await self._container.get(UnitOfWorkManager)
122
+ if method.descriptor.unit_of_work.disabled:
123
+ manager._check_current(connection_name)
124
+ return await invoke(ambient.action_container)
125
+ options = manager._resolve_options(
126
+ connection_name, method.descriptor.unit_of_work, method.name
127
+ )
128
+ async with manager._begin(options) as work:
129
+ result = await invoke(ambient.action_container)
130
+ await work.complete()
131
+ return result
132
+ return await self._runtime.run_action(
133
+ self._container,
134
+ connection_name=connection_name,
135
+ callback=invoke,
136
+ declaration=method.descriptor.unit_of_work,
137
+ method_name=method.name,
138
+ unit_of_work_enabled=self._unit_of_work_enabled,
139
+ )
140
+
141
+ async def _invoke_entry(
142
+ self,
143
+ method: _CompiledMethod,
144
+ pipeline: _InterceptorPipeline,
145
+ callback: Callable[[], Awaitable[object]],
146
+ *,
147
+ defer_audit: Callable[[Callable[[BaseException | None], Awaitable[None]]], None]
148
+ | None = None,
149
+ ) -> object:
150
+ async def authorize() -> object:
151
+ await self._authorize(method.authorization)
152
+ return await callback()
153
+
154
+ async def audit() -> object:
155
+ occurred_at = datetime.now(UTC)
156
+ started_ns = time.perf_counter_ns()
157
+ failure: BaseException | None = None
158
+ try:
159
+ return await pipeline.run(
160
+ InterceptionStage.AUTHORIZATION, self._container, authorize
161
+ )
162
+ except BaseException as error:
163
+ failure = error
164
+ raise
165
+ finally:
166
+
167
+ async def write(final_failure: BaseException | None) -> None:
168
+ await self._write_audit(
169
+ method,
170
+ occurred_at=occurred_at,
171
+ duration_ns=time.perf_counter_ns() - started_ns,
172
+ failure=final_failure,
173
+ )
174
+
175
+ if defer_audit is not None:
176
+ defer_audit(write)
177
+ else:
178
+ await write(failure)
179
+
180
+ async def trace() -> object:
181
+ with invocation_span(self._runtime.tracer, method.contract_method.__qualname__):
182
+ return await pipeline.run(InterceptionStage.AUDITING, self._container, audit)
183
+
184
+ return await pipeline.run(InterceptionStage.TRACING, self._container, trace)
185
+
186
+ @staticmethod
187
+ async def _validate_pipeline(
188
+ method: _CompiledMethod,
189
+ pipeline: _InterceptorPipeline,
190
+ ) -> None:
191
+ arguments = pipeline.arguments
192
+ if not arguments.needs_validation:
193
+ return
194
+ if method.descriptor.validation_enabled:
195
+ arguments.args, arguments.kwargs = await _validate_arguments(
196
+ method, arguments.args, arguments.kwargs
197
+ )
198
+ else:
199
+ # 可选类型验证关闭后,必需参数与参数名仍属于公开调用契约。
200
+ try:
201
+ method.signature.bind(None, *arguments.args, **arguments.kwargs)
202
+ except TypeError:
203
+ raise ApplicationServiceValidationError(
204
+ service=method.descriptor.service,
205
+ method_name=method.name,
206
+ issues=(
207
+ ApplicationServiceValidationIssue(
208
+ location=(), error_type="arguments", message="Invalid method arguments"
209
+ ),
210
+ ),
211
+ ) from None
212
+ arguments.mark_validated()
213
+
214
+ async def _write_audit(
215
+ self,
216
+ method: _CompiledMethod,
217
+ *,
218
+ occurred_at: datetime,
219
+ duration_ns: int,
220
+ failure: BaseException | None,
221
+ ) -> None:
222
+ if not self._auditing_enabled or not method.descriptor.auditing_enabled:
223
+ return
224
+ options = (await self._container.get(Options[AuditingOptions])).value
225
+ control = await self._container.get(AuditingControl)
226
+ if not options.is_enabled or not control.is_enabled:
227
+ return
228
+ try:
229
+ sink = await self._container.get(AuditSink)
230
+ except NoFactoryError:
231
+ return
232
+ except Exception as audit_error:
233
+ _LOGGER.error(
234
+ "application service audit write failed",
235
+ exc_info=audit_error,
236
+ extra={
237
+ "audit_exception_type": type(audit_error).__name__,
238
+ "audit_contract": method.descriptor.service.__qualname__,
239
+ "audit_method": method.name,
240
+ },
241
+ )
242
+ return
243
+ try:
244
+ current_user = self._runtime.current_user()
245
+ correlation_id = self._runtime.correlation_id() or str(uuid4())
246
+ await sink.write_application_service(
247
+ ApplicationServiceAuditRecord(
248
+ occurred_at=occurred_at,
249
+ user_id=current_user.user_id,
250
+ session_id=current_user.session_id,
251
+ correlation_id=correlation_id,
252
+ contract=(
253
+ f"{method.descriptor.service.__module__}.{method.descriptor.service.__qualname__}"
254
+ ),
255
+ method=method.name,
256
+ succeeded=failure is None,
257
+ duration_ns=duration_ns,
258
+ exception_type=None if failure is None else type(failure).__name__,
259
+ )
260
+ )
261
+ except Exception as audit_error:
262
+ # sink resolution/context/write 都属于独立观测边界;失败不能覆盖业务结果。
263
+ _LOGGER.error(
264
+ "application service audit write failed",
265
+ exc_info=audit_error,
266
+ extra={
267
+ "audit_exception_type": type(audit_error).__name__,
268
+ "audit_contract": method.descriptor.service.__qualname__,
269
+ "audit_method": method.name,
270
+ },
271
+ )
272
+
273
+ async def _authorize(self, requirement: _AuthorizationRequirement) -> None:
274
+ if requirement.allows_anonymous or not requirement.requires_authenticated_user:
275
+ return
276
+ # 所有受管理入口共用此阶段;放行不改变认证器或 CurrentUser。
277
+ options = await self._container.get(Options[AuthorizationOptions])
278
+ if options.value.always_allow:
279
+ return
280
+ current_user = self._runtime.current_user()
281
+ if not current_user.is_authenticated:
282
+ raise UnauthenticatedError
283
+ if not requirement.permissions:
284
+ return
285
+ try:
286
+ checker = await self._container.get(PermissionChecker)
287
+ except NoFactoryError:
288
+ raise PermissionCheckerUnavailableError from None
289
+ for permission in requirement.permissions:
290
+ if not await checker.is_granted(current_user, permission):
291
+ raise PermissionDeniedError(permission=str(permission))
@@ -2,37 +2,14 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from collections.abc import Awaitable, Callable, Mapping
5
+ from collections.abc import Awaitable, Callable
6
6
  from typing import cast
7
7
 
8
- from dishka import AsyncContainer, BaseScope, Scope
8
+ from dishka import AsyncContainer, BaseScope
9
9
 
10
- from ..background_jobs import BackgroundJobCatalog
11
- from ..background_workers import BackgroundWorkerCatalog
12
- from ..events import LocalEventCatalog, LocalEventPhase
13
10
  from .callables import _CallablePlan
14
11
 
15
12
 
16
- def _validate_callable_entries(
17
- container: AsyncContainer,
18
- jobs: BackgroundJobCatalog,
19
- workers: BackgroundWorkerCatalog,
20
- events: LocalEventCatalog,
21
- *,
22
- event_implementations: Mapping[type[object], type[object]],
23
- ) -> None:
24
- for job in jobs.definitions:
25
- _validate_entry(container, job.handler_type, "execute", 2, Scope.ACTION)
26
- for worker in workers.definitions:
27
- _validate_entry(container, worker.worker_type, "run_iteration", 1, Scope.ACTION)
28
- for subscription in events.subscriptions:
29
- scope = Scope.ACTION if subscription.phase is LocalEventPhase.DOMAIN else Scope.REQUEST
30
- target = subscription.handler_type
31
- if isinstance(target, type):
32
- target = event_implementations[target]
33
- _validate_entry(container, target, "handle", 1, scope)
34
-
35
-
36
13
  def _validate_entry(
37
14
  container: AsyncContainer,
38
15
  target: type[object] | Callable[..., Awaitable[None]],
@@ -0,0 +1,126 @@
1
+ """Host 的正式调用入口;作用域和服务解析始终复用 Application 的原生容器。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import AsyncIterator, Awaitable, Callable
6
+ from contextlib import asynccontextmanager
7
+ from typing import Concatenate, ParamSpec, TypeVar, cast
8
+ from uuid import uuid4
9
+
10
+ from ..application import Application
11
+ from ..application_services import (
12
+ ApplicationInvocationRejectedError,
13
+ ApplicationServiceContract,
14
+ )
15
+ from ..application_services.invocation import _InvocationRuntime, _ScopedApplicationServiceInvoker
16
+ from ..authorization import CurrentUser
17
+ from ..lifecycle import ApplicationState
18
+ from .function_runtime import _invoke_callable
19
+
20
+ _P = ParamSpec("_P")
21
+ _R = TypeVar("_R")
22
+ _TContract = TypeVar("_TContract", bound=ApplicationServiceContract)
23
+
24
+
25
+ async def call(
26
+ application: Application,
27
+ function: Callable[..., Awaitable[_R]],
28
+ /,
29
+ *args: object,
30
+ **kwargs: object,
31
+ ) -> _R:
32
+ if application.state not in (ApplicationState.STARTING, ApplicationState.RUNNING):
33
+ raise ApplicationInvocationRejectedError(
34
+ reason=f"Application must be running, got {application.state.value}",
35
+ )
36
+ runtime = await application._container_for_integration().get(_InvocationRuntime)
37
+ return await call_as(application, runtime.current_user(), function, *args, **kwargs)
38
+
39
+
40
+ async def call_as(
41
+ application: Application,
42
+ current_user: CurrentUser,
43
+ function: Callable[..., Awaitable[_R]],
44
+ /,
45
+ *args: object,
46
+ **kwargs: object,
47
+ ) -> _R:
48
+ if application.state not in (ApplicationState.STARTING, ApplicationState.RUNNING):
49
+ raise ApplicationInvocationRejectedError(
50
+ reason=f"Application must be running, got {application.state.value}",
51
+ )
52
+ container = application._container_for_integration()
53
+ runtime = await container.get(_InvocationRuntime)
54
+ return await _invoke_callable(
55
+ container,
56
+ runtime,
57
+ current_user,
58
+ str(uuid4()),
59
+ function,
60
+ args,
61
+ kwargs,
62
+ )
63
+
64
+
65
+ async def invoke(
66
+ application: Application,
67
+ method: Callable[Concatenate[_TContract, _P], Awaitable[_R]],
68
+ /,
69
+ *args: _P.args,
70
+ **kwargs: _P.kwargs,
71
+ ) -> _R:
72
+ return await invoke_as(application, CurrentUser.anonymous(), method, *args, **kwargs)
73
+
74
+
75
+ async def invoke_as(
76
+ application: Application,
77
+ current_user: CurrentUser,
78
+ method: Callable[Concatenate[_TContract, _P], Awaitable[_R]],
79
+ /,
80
+ *args: _P.args,
81
+ **kwargs: _P.kwargs,
82
+ ) -> _R:
83
+ if application.state not in (ApplicationState.STARTING, ApplicationState.RUNNING):
84
+ raise ApplicationInvocationRejectedError(
85
+ reason=f"Application must be running, got {application.state.value}",
86
+ )
87
+ container = application._container_for_integration()
88
+ invoker = await container.get(_ScopedApplicationServiceInvoker)
89
+ result = await invoker.invoke(
90
+ container,
91
+ current_user,
92
+ str(uuid4()),
93
+ cast(Callable[..., Awaitable[object]], method),
94
+ cast(tuple[object, ...], args),
95
+ cast(dict[str, object], kwargs),
96
+ )
97
+ return cast(_R, result)
98
+
99
+
100
+ @asynccontextmanager
101
+ async def invocation_lease(application: Application) -> AsyncIterator[None]:
102
+ runtime = await application._container_for_integration().get(_InvocationRuntime)
103
+ async with runtime.outer_lease():
104
+ yield
105
+
106
+
107
+ @asynccontextmanager
108
+ async def http_request_lease(application: Application) -> AsyncIterator[None]:
109
+ runtime = await application._container_for_integration().get(_InvocationRuntime)
110
+ # composed/pre-start 健康探测仍参与 drain,业务调用的 RUNNING 限制由 dispatcher 保证。
111
+ async with runtime.outer_lease(allow_composed_request=True):
112
+ yield
113
+
114
+
115
+ @asynccontextmanager
116
+ async def caller_context(
117
+ application: Application,
118
+ current_user: CurrentUser,
119
+ correlation_id: str,
120
+ ) -> AsyncIterator[None]:
121
+ runtime = await application._container_for_integration().get(_InvocationRuntime)
122
+ token = runtime.bind_caller(current_user, correlation_id)
123
+ try:
124
+ yield
125
+ finally:
126
+ runtime.reset_caller(token)
@@ -12,8 +12,11 @@ from uuid import UUID
12
12
  from dishka import AsyncContainer
13
13
  from pydantic import BaseModel
14
14
 
15
- from .catalog import ApplicationServiceMethod
16
- from .errors import ApplicationInvocationRejectedError, CrossTaskInvocationError
15
+ from ..application_services.errors import (
16
+ ApplicationInvocationRejectedError,
17
+ CrossTaskInvocationError,
18
+ )
19
+ from ..invocation.methods import ServiceMethod
17
20
  from .interceptors import InterceptionStage, InterceptorRegistration
18
21
 
19
22
  _ARGUMENT_SNAPSHOT_MAX_DEPTH = 64
@@ -22,7 +25,7 @@ _ARGUMENT_SNAPSHOT_MAX_DEPTH = 64
22
25
  class _InterceptorPipeline:
23
26
  def __init__(
24
27
  self,
25
- method: ApplicationServiceMethod,
28
+ method: ServiceMethod,
26
29
  steps: Mapping[InterceptionStage, tuple[InterceptorRegistration, ...]],
27
30
  args: tuple[object, ...],
28
31
  kwargs: dict[str, object],
@@ -155,7 +158,7 @@ class MethodInvocation:
155
158
 
156
159
  def __init__(
157
160
  self,
158
- method: ApplicationServiceMethod,
161
+ method: ServiceMethod,
159
162
  arguments: _InvocationArguments,
160
163
  continuation: Callable[[], Awaitable[object]],
161
164
  ) -> None:
@@ -167,7 +170,7 @@ class MethodInvocation:
167
170
  self._active = True
168
171
 
169
172
  @property
170
- def method(self) -> ApplicationServiceMethod:
173
+ def method(self) -> ServiceMethod:
171
174
  return self._method
172
175
 
173
176
  @property
@@ -26,6 +26,8 @@ async def _invoke_callable(
26
26
  args: tuple[object, ...],
27
27
  kwargs: dict[str, object],
28
28
  ) -> _R:
29
+ from ..application_services.invocation import _managed_request
30
+
29
31
  async with runtime.outer_lease():
30
32
  ambient = runtime.current()
31
33
  existing = ambient.action_container if ambient is not None else runtime.current_call_scope()
@@ -41,7 +43,7 @@ async def _invoke_callable(
41
43
  token = runtime.bind_caller(current_user, correlation_id)
42
44
  try:
43
45
  plan = _CallablePlan.compile(function, container, Scope.REQUEST)
44
- async with container(scope=Scope.REQUEST) as request:
46
+ async with _managed_request(container) as request:
45
47
  with runtime.call_scope(request):
46
48
  return await plan.invoke(function, request, args, kwargs)
47
49
  finally: