python-ddd-framework 0.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (355) hide show
  1. python_ddd_framework/__init__.py +449 -0
  2. python_ddd_framework/application/__init__.py +6 -0
  3. python_ddd_framework/application/build_spec.py +37 -0
  4. python_ddd_framework/application/builder.py +260 -0
  5. python_ddd_framework/application/composition.py +219 -0
  6. python_ddd_framework/application/runtime.py +549 -0
  7. python_ddd_framework/application/runtime_hooks.py +327 -0
  8. python_ddd_framework/application/state.py +216 -0
  9. python_ddd_framework/application_services/__init__.py +54 -0
  10. python_ddd_framework/application_services/catalog.py +500 -0
  11. python_ddd_framework/application_services/contracts.py +190 -0
  12. python_ddd_framework/application_services/dispatcher.py +423 -0
  13. python_ddd_framework/application_services/errors.py +89 -0
  14. python_ddd_framework/application_services/execution.py +199 -0
  15. python_ddd_framework/application_services/interceptors.py +44 -0
  16. python_ddd_framework/application_services/invocation.py +431 -0
  17. python_ddd_framework/application_services/policies.py +46 -0
  18. python_ddd_framework/application_services/seeding.py +72 -0
  19. python_ddd_framework/application_services/signature.py +49 -0
  20. python_ddd_framework/application_services/validation.py +84 -0
  21. python_ddd_framework/auditing/__init__.py +12 -0
  22. python_ddd_framework/auditing/contracts.py +39 -0
  23. python_ddd_framework/auditing/control.py +48 -0
  24. python_ddd_framework/auditing/sqlalchemy/__init__.py +6 -0
  25. python_ddd_framework/auditing/sqlalchemy/migrations/0001_auditing.py +45 -0
  26. python_ddd_framework/auditing/sqlalchemy/migrations/__init__.py +1 -0
  27. python_ddd_framework/auditing/sqlalchemy/models.py +31 -0
  28. python_ddd_framework/auditing/sqlalchemy/module.py +32 -0
  29. python_ddd_framework/auditing/sqlalchemy/store.py +63 -0
  30. python_ddd_framework/authorization/__init__.py +38 -0
  31. python_ddd_framework/authorization/catalog.py +63 -0
  32. python_ddd_framework/authorization/contracts.py +241 -0
  33. python_ddd_framework/authorization/definition_discovery.py +81 -0
  34. python_ddd_framework/authorization/definitions.py +43 -0
  35. python_ddd_framework/authorization/errors.py +40 -0
  36. python_ddd_framework/background_execution/__init__.py +17 -0
  37. python_ddd_framework/background_execution/application.py +41 -0
  38. python_ddd_framework/background_execution/child.py +123 -0
  39. python_ddd_framework/background_execution/contracts.py +48 -0
  40. python_ddd_framework/background_execution/lifecycle.py +22 -0
  41. python_ddd_framework/background_execution/local.py +127 -0
  42. python_ddd_framework/background_execution/locks.py +33 -0
  43. python_ddd_framework/background_execution/management.py +17 -0
  44. python_ddd_framework/background_execution/module.py +10 -0
  45. python_ddd_framework/background_execution/permissions.py +14 -0
  46. python_ddd_framework/background_execution/processes.py +331 -0
  47. python_ddd_framework/background_jobs/__init__.py +31 -0
  48. python_ddd_framework/background_jobs/catalog.py +340 -0
  49. python_ddd_framework/background_jobs/contracts.py +208 -0
  50. python_ddd_framework/background_jobs/declaration.py +67 -0
  51. python_ddd_framework/background_jobs/errors.py +28 -0
  52. python_ddd_framework/background_jobs/execution.py +203 -0
  53. python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt +21 -0
  54. python_ddd_framework/background_jobs/pgqueuer/__init__.py +6 -0
  55. python_ddd_framework/background_jobs/pgqueuer/enqueue.py +86 -0
  56. python_ddd_framework/background_jobs/pgqueuer/migrations/0001_pgqueuer_1_3_2.py +95 -0
  57. python_ddd_framework/background_jobs/pgqueuer/migrations/__init__.py +1 -0
  58. python_ddd_framework/background_jobs/pgqueuer/module.py +106 -0
  59. python_ddd_framework/background_jobs/pgqueuer/options.py +82 -0
  60. python_ddd_framework/background_jobs/pgqueuer/runtime.py +479 -0
  61. python_ddd_framework/background_jobs/pgqueuer/sql/pgqueuer_1_3_2_install.sql +118 -0
  62. python_ddd_framework/background_jobs/pgqueuer/supervision.py +201 -0
  63. python_ddd_framework/background_workers/__init__.py +22 -0
  64. python_ddd_framework/background_workers/catalog.py +118 -0
  65. python_ddd_framework/background_workers/contracts.py +152 -0
  66. python_ddd_framework/background_workers/errors.py +20 -0
  67. python_ddd_framework/background_workers/execution.py +133 -0
  68. python_ddd_framework/background_workers/runtime.py +219 -0
  69. python_ddd_framework/caching/__init__.py +11 -0
  70. python_ddd_framework/caching/catalog.py +73 -0
  71. python_ddd_framework/caching/contracts.py +73 -0
  72. python_ddd_framework/caching/errors.py +20 -0
  73. python_ddd_framework/cli/__init__.py +105 -0
  74. python_ddd_framework/cli/development.py +85 -0
  75. python_ddd_framework/cli/errors.py +5 -0
  76. python_ddd_framework/cli/inspection.py +96 -0
  77. python_ddd_framework/cli/project.py +69 -0
  78. python_ddd_framework/cli/runtime.py +56 -0
  79. python_ddd_framework/configuration/__init__.py +21 -0
  80. python_ddd_framework/configuration/composition.py +65 -0
  81. python_ddd_framework/configuration/contracts.py +68 -0
  82. python_ddd_framework/configuration/dotenv_source.py +51 -0
  83. python_ddd_framework/configuration/environment_source.py +38 -0
  84. python_ddd_framework/configuration/immutability.py +124 -0
  85. python_ddd_framework/configuration/input_shape.py +58 -0
  86. python_ddd_framework/configuration/merge.py +103 -0
  87. python_ddd_framework/configuration/root.py +215 -0
  88. python_ddd_framework/configuration/sources.py +519 -0
  89. python_ddd_framework/configuration/values.py +427 -0
  90. python_ddd_framework/configuration/yaml_source.py +51 -0
  91. python_ddd_framework/developer_kit/__init__.py +1 -0
  92. python_ddd_framework/developer_kit/generation.py +182 -0
  93. python_ddd_framework/developer_kit/project_metadata.py +45 -0
  94. python_ddd_framework/developer_kit/source.py +53 -0
  95. python_ddd_framework/developer_kit/templates/module/cookiecutter.json +1 -0
  96. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/README.md +70 -0
  97. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/__init__.py.jinja +1 -0
  98. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/__init__.py.jinja +1 -0
  99. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/cache.py.jinja +10 -0
  100. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/events.py.jinja +24 -0
  101. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration.py.jinja +41 -0
  102. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/module.py.jinja +20 -0
  103. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/options.py.jinja +7 -0
  104. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/orders.py.jinja +83 -0
  105. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/tasks.py.jinja +84 -0
  106. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/__init__.py.jinja +1 -0
  107. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/module.py.jinja +10 -0
  108. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/orders.py.jinja +35 -0
  109. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/__init__.py.jinja +1 -0
  110. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/module.py.jinja +10 -0
  111. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/orders.py.jinja +56 -0
  112. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repository.py.jinja +14 -0
  113. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding.py.jinja +19 -0
  114. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/settings.py.jinja +16 -0
  115. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/__init__.py.jinja +1 -0
  116. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/definitions.py.jinja +24 -0
  117. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/module.py.jinja +7 -0
  118. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions.py.jinja +15 -0
  119. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/__init__.py.jinja +1 -0
  120. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/files.py.jinja +73 -0
  121. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/module.py.jinja +25 -0
  122. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/realtime.py.jinja +36 -0
  123. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/__init__.py.jinja +1 -0
  124. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/migrations/__init__.py.jinja +1 -0
  125. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/__init__.py.jinja +7 -0
  126. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/orders.py.jinja +25 -0
  127. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/module.py.jinja +26 -0
  128. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/__init__.py.jinja +1 -0
  129. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/orders.py.jinja +49 -0
  130. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/test_domain.py.jinja +18 -0
  131. python_ddd_framework/developer_kit/templates/project/cookiecutter.json +6 -0
  132. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/.dockerignore +9 -0
  133. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/.gitignore +6 -0
  134. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/.python-version +1 -0
  135. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/AGENTS.md +38 -0
  136. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/Dockerfile +22 -0
  137. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/README.md +130 -0
  138. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/app.development.yaml.jinja +30 -0
  139. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/compose.dev.yaml.jinja +24 -0
  140. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/compose.production.yaml.jinja +20 -0
  141. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/architecture.md +84 -0
  142. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/development.md +185 -0
  143. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/pyproject.toml.jinja +28 -0
  144. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/src/host/__init__.py.jinja +1 -0
  145. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/src/host/main.py.jinja +47 -0
  146. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/src/host/module.py.jinja +37 -0
  147. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/tests/conftest.py.jinja +8 -0
  148. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/tests/host/test_http.py.jinja +57 -0
  149. python_ddd_framework/developer_kit/wiring.py +162 -0
  150. python_ddd_framework/diagnostics/__init__.py +23 -0
  151. python_ddd_framework/diagnostics/journal.py +174 -0
  152. python_ddd_framework/diagnostics/model.py +83 -0
  153. python_ddd_framework/diagnostics/source.py +25 -0
  154. python_ddd_framework/distributed_lock/__init__.py +6 -0
  155. python_ddd_framework/distributed_lock/contracts.py +20 -0
  156. python_ddd_framework/distributed_lock/options.py +19 -0
  157. python_ddd_framework/domain/__init__.py +13 -0
  158. python_ddd_framework/domain/aggregates.py +64 -0
  159. python_ddd_framework/domain/entities.py +33 -0
  160. python_ddd_framework/domain/errors.py +12 -0
  161. python_ddd_framework/domain/value_objects.py +31 -0
  162. python_ddd_framework/errors/__init__.py +75 -0
  163. python_ddd_framework/errors/base.py +13 -0
  164. python_ddd_framework/errors/business.py +53 -0
  165. python_ddd_framework/errors/configuration.py +119 -0
  166. python_ddd_framework/errors/diagnostics.py +12 -0
  167. python_ddd_framework/errors/lifecycle.py +164 -0
  168. python_ddd_framework/errors/modularity.py +100 -0
  169. python_ddd_framework/errors/services.py +100 -0
  170. python_ddd_framework/events/__init__.py +25 -0
  171. python_ddd_framework/events/aggregate.py +39 -0
  172. python_ddd_framework/events/catalog.py +136 -0
  173. python_ddd_framework/events/contracts.py +39 -0
  174. python_ddd_framework/events/contribution.py +128 -0
  175. python_ddd_framework/events/discovery.py +105 -0
  176. python_ddd_framework/events/errors.py +48 -0
  177. python_ddd_framework/events/runtime.py +192 -0
  178. python_ddd_framework/fastapi/__init__.py +49 -0
  179. python_ddd_framework/fastapi/action.py +175 -0
  180. python_ddd_framework/fastapi/adapter.py +391 -0
  181. python_ddd_framework/fastapi/application_services.py +654 -0
  182. python_ddd_framework/fastapi/background.py +42 -0
  183. python_ddd_framework/fastapi/contracts.py +275 -0
  184. python_ddd_framework/fastapi/errors.py +32 -0
  185. python_ddd_framework/fastapi/filters.py +111 -0
  186. python_ddd_framework/fastapi/health.py +44 -0
  187. python_ddd_framework/fastapi/http.py +72 -0
  188. python_ddd_framework/fastapi/http_router.py +216 -0
  189. python_ddd_framework/fastapi/manual_action.py +105 -0
  190. python_ddd_framework/fastapi/middleware.py +93 -0
  191. python_ddd_framework/fastapi/parameters.py +97 -0
  192. python_ddd_framework/fastapi/realtime/__init__.py +13 -0
  193. python_ddd_framework/fastapi/realtime/authentication.py +181 -0
  194. python_ddd_framework/fastapi/realtime/connection.py +166 -0
  195. python_ddd_framework/fastapi/realtime/module.py +41 -0
  196. python_ddd_framework/fastapi/realtime/options.py +50 -0
  197. python_ddd_framework/fastapi/realtime/runtime.py +423 -0
  198. python_ddd_framework/fastapi/request_context.py +247 -0
  199. python_ddd_framework/fastapi/route_integrity.py +88 -0
  200. python_ddd_framework/fastapi/routing.py +424 -0
  201. python_ddd_framework/fastapi/server.py +182 -0
  202. python_ddd_framework/fastapi/settings.py +40 -0
  203. python_ddd_framework/fastapi/tracing.py +62 -0
  204. python_ddd_framework/fastapi/transfer.py +128 -0
  205. python_ddd_framework/fastapi/upload_limits.py +63 -0
  206. python_ddd_framework/fastapi/uploads.py +94 -0
  207. python_ddd_framework/hosted_services/__init__.py +29 -0
  208. python_ddd_framework/hosted_services/bridge.py +420 -0
  209. python_ddd_framework/hosted_services/catalog.py +265 -0
  210. python_ddd_framework/hosted_services/contracts.py +55 -0
  211. python_ddd_framework/hosted_services/errors.py +53 -0
  212. python_ddd_framework/hosted_services/options.py +17 -0
  213. python_ddd_framework/hosted_services/runtime.py +267 -0
  214. python_ddd_framework/hosted_services/state.py +40 -0
  215. python_ddd_framework/hosting/__init__.py +4 -0
  216. python_ddd_framework/hosting/instance.py +40 -0
  217. python_ddd_framework/identity/__init__.py +73 -0
  218. python_ddd_framework/identity/application.py +287 -0
  219. python_ddd_framework/identity/contracts.py +283 -0
  220. python_ddd_framework/identity/errors.py +28 -0
  221. python_ddd_framework/identity/http_api.py +90 -0
  222. python_ddd_framework/identity/module.py +49 -0
  223. python_ddd_framework/identity/passwords.py +34 -0
  224. python_ddd_framework/identity/permissions.py +14 -0
  225. python_ddd_framework/identity/services.py +108 -0
  226. python_ddd_framework/identity/sqlalchemy/__init__.py +5 -0
  227. python_ddd_framework/identity/sqlalchemy/migrations/0001_identity.py +143 -0
  228. python_ddd_framework/identity/sqlalchemy/migrations/0002_physical_delete.py +42 -0
  229. python_ddd_framework/identity/sqlalchemy/migrations/__init__.py +1 -0
  230. python_ddd_framework/identity/sqlalchemy/models.py +85 -0
  231. python_ddd_framework/identity/sqlalchemy/module.py +62 -0
  232. python_ddd_framework/identity/sqlalchemy/stores.py +526 -0
  233. python_ddd_framework/identity/tokens.py +106 -0
  234. python_ddd_framework/invocation/__init__.py +3 -0
  235. python_ddd_framework/invocation/callables.py +171 -0
  236. python_ddd_framework/invocation/contracts.py +33 -0
  237. python_ddd_framework/invocation/entries.py +54 -0
  238. python_ddd_framework/invocation/function_runtime.py +48 -0
  239. python_ddd_framework/invocation/interception.py +193 -0
  240. python_ddd_framework/lifecycle/__init__.py +25 -0
  241. python_ddd_framework/lifecycle/composition.py +35 -0
  242. python_ddd_framework/lifecycle/context.py +31 -0
  243. python_ddd_framework/lifecycle/runtime.py +43 -0
  244. python_ddd_framework/lifecycle/state.py +20 -0
  245. python_ddd_framework/modularity/__init__.py +15 -0
  246. python_ddd_framework/modularity/contracts.py +91 -0
  247. python_ddd_framework/modularity/discovery.py +157 -0
  248. python_ddd_framework/modularity/graph.py +334 -0
  249. python_ddd_framework/modularity/registry.py +100 -0
  250. python_ddd_framework/modularity/selection.py +16 -0
  251. python_ddd_framework/notifications/__init__.py +19 -0
  252. python_ddd_framework/notifications/catalog.py +46 -0
  253. python_ddd_framework/notifications/contracts.py +107 -0
  254. python_ddd_framework/observability/__init__.py +5 -0
  255. python_ddd_framework/observability/context.py +43 -0
  256. python_ddd_framework/observability/export.py +73 -0
  257. python_ddd_framework/observability/formatting.py +89 -0
  258. python_ddd_framework/observability/logging.py +118 -0
  259. python_ddd_framework/observability/options.py +45 -0
  260. python_ddd_framework/observability/tracing.py +80 -0
  261. python_ddd_framework/options/__init__.py +19 -0
  262. python_ddd_framework/options/aliases.py +331 -0
  263. python_ddd_framework/options/contribution.py +62 -0
  264. python_ddd_framework/options/immutability.py +48 -0
  265. python_ddd_framework/options/input_keys.py +214 -0
  266. python_ddd_framework/options/issues.py +335 -0
  267. python_ddd_framework/options/models.py +114 -0
  268. python_ddd_framework/options/registry.py +280 -0
  269. python_ddd_framework/options/schema.py +488 -0
  270. python_ddd_framework/options/validation.py +120 -0
  271. python_ddd_framework/py.typed +0 -0
  272. python_ddd_framework/realtime/__init__.py +12 -0
  273. python_ddd_framework/realtime/contracts.py +28 -0
  274. python_ddd_framework/realtime/diagnostics.py +37 -0
  275. python_ddd_framework/realtime/messages.py +113 -0
  276. python_ddd_framework/redis/__init__.py +6 -0
  277. python_ddd_framework/redis/distributed_lock.py +212 -0
  278. python_ddd_framework/redis/lease_lock.py +36 -0
  279. python_ddd_framework/redis/module.py +50 -0
  280. python_ddd_framework/redis/notification_runtime.py +182 -0
  281. python_ddd_framework/redis/notifications.py +25 -0
  282. python_ddd_framework/redis/options.py +35 -0
  283. python_ddd_framework/redis/runtime.py +116 -0
  284. python_ddd_framework/services/__init__.py +41 -0
  285. python_ddd_framework/services/application_bindings.py +221 -0
  286. python_ddd_framework/services/arbitration.py +381 -0
  287. python_ddd_framework/services/binding.py +102 -0
  288. python_ddd_framework/services/contribution.py +438 -0
  289. python_ddd_framework/services/convention.py +301 -0
  290. python_ddd_framework/services/convention_contracts.py +101 -0
  291. python_ddd_framework/services/exposure.py +23 -0
  292. python_ddd_framework/services/fixed_lifetime.py +52 -0
  293. python_ddd_framework/services/framework_provider.py +178 -0
  294. python_ddd_framework/services/native_graph.py +201 -0
  295. python_ddd_framework/services/provider.py +204 -0
  296. python_ddd_framework/services/registration.py +101 -0
  297. python_ddd_framework/services/repository.py +38 -0
  298. python_ddd_framework/services/runtime.py +302 -0
  299. python_ddd_framework/settings/__init__.py +30 -0
  300. python_ddd_framework/settings/application.py +95 -0
  301. python_ddd_framework/settings/binding.py +33 -0
  302. python_ddd_framework/settings/catalog.py +121 -0
  303. python_ddd_framework/settings/changes.py +14 -0
  304. python_ddd_framework/settings/contracts.py +115 -0
  305. python_ddd_framework/settings/definition_discovery.py +71 -0
  306. python_ddd_framework/settings/definitions.py +41 -0
  307. python_ddd_framework/settings/errors.py +22 -0
  308. python_ddd_framework/settings/handlers.py +43 -0
  309. python_ddd_framework/settings/management.py +57 -0
  310. python_ddd_framework/settings/manager.py +89 -0
  311. python_ddd_framework/settings/module.py +10 -0
  312. python_ddd_framework/settings/notifications.py +39 -0
  313. python_ddd_framework/settings/permissions.py +14 -0
  314. python_ddd_framework/settings/provider.py +74 -0
  315. python_ddd_framework/settings/refresh.py +140 -0
  316. python_ddd_framework/settings/refresh_module.py +45 -0
  317. python_ddd_framework/settings/sqlalchemy/__init__.py +5 -0
  318. python_ddd_framework/settings/sqlalchemy/migrations/0001_settings.py +31 -0
  319. python_ddd_framework/settings/sqlalchemy/migrations/0002_physical_delete.py +32 -0
  320. python_ddd_framework/settings/sqlalchemy/migrations/0003_version_tokens.py +39 -0
  321. python_ddd_framework/settings/sqlalchemy/migrations/__init__.py +1 -0
  322. python_ddd_framework/settings/sqlalchemy/models.py +36 -0
  323. python_ddd_framework/settings/sqlalchemy/module.py +36 -0
  324. python_ddd_framework/settings/sqlalchemy/store.py +86 -0
  325. python_ddd_framework/settings/store.py +16 -0
  326. python_ddd_framework/settings/values.py +39 -0
  327. python_ddd_framework/sqlalchemy/__init__.py +54 -0
  328. python_ddd_framework/sqlalchemy/alembic_runtime/__init__.py +1 -0
  329. python_ddd_framework/sqlalchemy/alembic_runtime/env.py +33 -0
  330. python_ddd_framework/sqlalchemy/alembic_runtime/script.py.mako +14 -0
  331. python_ddd_framework/sqlalchemy/auditing.py +48 -0
  332. python_ddd_framework/sqlalchemy/errors.py +85 -0
  333. python_ddd_framework/sqlalchemy/metadata.py +576 -0
  334. python_ddd_framework/sqlalchemy/migration.py +378 -0
  335. python_ddd_framework/sqlalchemy/migration_options.py +56 -0
  336. python_ddd_framework/sqlalchemy/module.py +51 -0
  337. python_ddd_framework/sqlalchemy/module_migration.py +151 -0
  338. python_ddd_framework/sqlalchemy/options.py +66 -0
  339. python_ddd_framework/sqlalchemy/repository.py +33 -0
  340. python_ddd_framework/sqlalchemy/runtime.py +85 -0
  341. python_ddd_framework/sqlalchemy/session_provider.py +45 -0
  342. python_ddd_framework/sqlalchemy/unit_of_work.py +97 -0
  343. python_ddd_framework/testing/__init__.py +5 -0
  344. python_ddd_framework/testing/runtime.py +112 -0
  345. python_ddd_framework/unit_of_work/__init__.py +14 -0
  346. python_ddd_framework/unit_of_work/contracts.py +223 -0
  347. python_ddd_framework/unit_of_work/errors.py +27 -0
  348. python_ddd_framework/unit_of_work/manager.py +210 -0
  349. python_ddd_framework/unit_of_work/options.py +92 -0
  350. python_ddd_framework-0.3.1.dist-info/METADATA +379 -0
  351. python_ddd_framework-0.3.1.dist-info/RECORD +355 -0
  352. python_ddd_framework-0.3.1.dist-info/WHEEL +4 -0
  353. python_ddd_framework-0.3.1.dist-info/entry_points.txt +9 -0
  354. python_ddd_framework-0.3.1.dist-info/licenses/LICENSE +7 -0
  355. python_ddd_framework-0.3.1.dist-info/licenses/src/python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt +21 -0
@@ -0,0 +1,549 @@
1
+ """公开 Application 状态机、生命周期与非 DI 调用入口。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from collections.abc import AsyncIterator, Awaitable, Callable, Iterator
7
+ from contextlib import asynccontextmanager, contextmanager
8
+ from typing import Concatenate, Never, ParamSpec, TypeAlias, TypeVar, cast
9
+ from uuid import UUID, uuid4
10
+
11
+ from dishka import AsyncContainer, Scope
12
+
13
+ from ..application_services import (
14
+ ApplicationInvocationRejectedError,
15
+ ApplicationServiceCatalog,
16
+ ApplicationServiceContract,
17
+ ApplicationServiceRegistration,
18
+ )
19
+ from ..application_services.dispatcher import _ApplicationServiceDispatcher
20
+ from ..application_services.invocation import (
21
+ _ScopedApplicationServiceInvoker,
22
+ )
23
+ from ..authorization import CurrentUser, PermissionCatalog
24
+ from ..background_jobs import BackgroundJobCatalog
25
+ from ..background_workers import BackgroundWorkerCatalog
26
+ from ..caching import CacheCatalog
27
+ from ..configuration import ConfigurationRoot
28
+ from ..diagnostics import DiagnosticSnapshot
29
+ from ..errors import (
30
+ ApplicationRuntimeError,
31
+ ApplicationStartError,
32
+ ContainerCleanupError,
33
+ InvalidApplicationStateError,
34
+ LifecycleInvocationError,
35
+ )
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
47
+ from ..lifecycle import ApplicationState, ShutdownReason
48
+ from ..modularity import ModuleRegistry
49
+ from ..options import OptionsSnapshot
50
+ from ..services import ServiceCatalog
51
+ from ..settings import SettingCatalog
52
+ from .runtime_hooks import _RuntimeLifecycle
53
+ from .state import _ApplicationStateRoot
54
+
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
+
66
+ class Application:
67
+ """拥有单次 build 的 Module instances、状态、transition 与 diagnostics。"""
68
+
69
+ __slots__ = ("_root",)
70
+
71
+ def __init__(self, root: _ApplicationStateRoot) -> None:
72
+ self._root = root
73
+
74
+ @property
75
+ def id(self) -> UUID:
76
+ return self._root.application_id
77
+
78
+ @property
79
+ def state(self) -> ApplicationState:
80
+ return self._root.state
81
+
82
+ @property
83
+ def modules(self) -> ModuleRegistry:
84
+ return self._root.registry
85
+
86
+ @property
87
+ def configuration(self) -> ConfigurationRoot:
88
+ return self._root.configuration
89
+
90
+ @property
91
+ def options(self) -> OptionsSnapshot:
92
+ return self._root.require_options()
93
+
94
+ @property
95
+ def service_catalog(self) -> ServiceCatalog:
96
+ return self._root.require_service_catalog()
97
+
98
+ @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()
133
+
134
+ @property
135
+ def diagnostics(self) -> DiagnosticSnapshot:
136
+ return self._root.journal.snapshot(self._root.application_id)
137
+
138
+ def _container_for_integration(self) -> AsyncContainer:
139
+ # 外部 adapter 只能在 composition root 建桥;不把 resolver/container 变成 public API。
140
+ return self._root.require_container()
141
+
142
+ async def start(self) -> None:
143
+ await self._start(run_services=True)
144
+
145
+ async def _start(self, *, run_services: bool) -> None:
146
+ token = self._root.claim_transition(
147
+ operation="start",
148
+ allowed=(ApplicationState.COMPOSED,),
149
+ )
150
+ 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
+ lifecycle = _RuntimeLifecycle(self._root)
155
+ started = False
156
+ try:
157
+ try:
158
+ await lifecycle.start_modules()
159
+ except LifecycleInvocationError as primary:
160
+ 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
+ self._root.state = ApplicationState.RUNNING
182
+ started = True
183
+ finally:
184
+ self._root.release_transition(token)
185
+ if started and (self._root.require_background_coordinator().has_runtime_failure_source):
186
+ self._root.runtime_monitor_task = asyncio.create_task(
187
+ self._monitor_runtime_failure(),
188
+ name=f"python-ddd-framework-runtime-monitor:{self.id}",
189
+ )
190
+
191
+ async def _fail_start(
192
+ self,
193
+ lifecycle: _RuntimeLifecycle,
194
+ *,
195
+ primary: LifecycleInvocationError
196
+ | HostedServiceLifecycleError
197
+ | _BackgroundExecutionStartFailure,
198
+ ) -> Never:
199
+ self._root.state = ApplicationState.ROLLING_BACK
200
+ # 回滚 task 由本次 start 强持有;重复取消只能在所有资源实际退出后返回。
201
+ cleanup_task = asyncio.create_task(self._rollback_start(lifecycle, primary=primary))
202
+ cancellation: asyncio.CancelledError | None = None
203
+ while not cleanup_task.done():
204
+ try:
205
+ await asyncio.shield(cleanup_task)
206
+ except asyncio.CancelledError as original_cancellation:
207
+ if cancellation is None:
208
+ cancellation = original_cancellation
209
+ failure = cleanup_task.result()
210
+ self._root.state = ApplicationState.FAILED_START
211
+ if cancellation is not None:
212
+ failure.__cause__ = primary
213
+ raise cancellation from failure
214
+ raise failure from primary
215
+
216
+ async def _rollback_start(
217
+ self,
218
+ lifecycle: _RuntimeLifecycle,
219
+ *,
220
+ primary: LifecycleInvocationError
221
+ | HostedServiceLifecycleError
222
+ | _BackgroundExecutionStartFailure,
223
+ ) -> ApplicationStartError:
224
+ 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
+ self._root.journal.complete_failure(
232
+ 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,
238
+ )
239
+ return ApplicationStartError(
240
+ primary=primary,
241
+ cleanup_failures=await self._rollback_runtime_start(lifecycle, primary=primary),
242
+ )
243
+
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)
278
+
279
+ async def stop(self) -> None:
280
+ if self._root.state in (
281
+ ApplicationState.STOPPED,
282
+ ApplicationState.STOPPED_WITH_ERRORS,
283
+ ApplicationState.FAILED_START,
284
+ ):
285
+ return
286
+
287
+ token = self._root.claim_transition(
288
+ operation="stop",
289
+ allowed=(ApplicationState.COMPOSED, ApplicationState.RUNNING),
290
+ )
291
+ self._root.state = ApplicationState.STOPPING
292
+ # shield 只保留弱引用;局部 task 同时承担 cleanup 的强 ownership。
293
+ cleanup_task = asyncio.create_task(self._complete_stop())
294
+ cancellation: asyncio.CancelledError | None = None
295
+ cleanup_failure: BaseException | None = None
296
+ try:
297
+ while not cleanup_task.done():
298
+ try:
299
+ await asyncio.shield(cleanup_task)
300
+ except asyncio.CancelledError as original_cancellation:
301
+ if cancellation is None:
302
+ cancellation = original_cancellation
303
+ cleanup_failure = cleanup_task.result()
304
+ finally:
305
+ self._root.release_transition(token)
306
+
307
+ if cancellation is not None:
308
+ if cleanup_failure is not None:
309
+ raise cancellation from cleanup_failure
310
+ raise cancellation
311
+ if cleanup_failure is not None:
312
+ raise cleanup_failure
313
+
314
+ async def _complete_stop(self) -> BaseException | None:
315
+ lifecycle = _RuntimeLifecycle(self._root)
316
+ reason = (
317
+ ShutdownReason.RUNTIME_FAILURE
318
+ if self._root.runtime_failure is not None
319
+ else ShutdownReason.NORMAL
320
+ )
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()
336
+ module_failures = await lifecycle.shutdown_touched(
337
+ reason=reason,
338
+ )
339
+ container_failures = await lifecycle.close_container(exception=None)
340
+ runtime_failure = self._root.runtime_failure
341
+ if runtime_failure is not None:
342
+ infrastructure_failures = [
343
+ remaining
344
+ for error in infrastructure_failures
345
+ if (remaining := _exclude_runtime_failure(error, runtime_failure)) is not None
346
+ ]
347
+ cleanup_failures: tuple[BaseException, ...] = (
348
+ *infrastructure_failures,
349
+ *module_failures,
350
+ *container_failures,
351
+ )
352
+ terminal_failure: BaseException | None
353
+ if runtime_failure is not None and cleanup_failures:
354
+ terminal_failure = BaseExceptionGroup(
355
+ "Application runtime termination failed",
356
+ (runtime_failure, *cleanup_failures),
357
+ )
358
+ elif runtime_failure is not None:
359
+ terminal_failure = runtime_failure
360
+ elif cleanup_failures:
361
+ terminal_failure = BaseExceptionGroup(
362
+ "Application shutdown failed",
363
+ cleanup_failures,
364
+ )
365
+ else:
366
+ terminal_failure = None
367
+ self._root.state = (
368
+ ApplicationState.STOPPED_WITH_ERRORS
369
+ if terminal_failure is not None
370
+ else ApplicationState.STOPPED
371
+ )
372
+ self._root.termination_failure = terminal_failure
373
+ self._root.termination_event.set()
374
+ monitor = self._root.runtime_monitor_task
375
+ if (
376
+ runtime_failure is None
377
+ and monitor is not None
378
+ and monitor is not asyncio.current_task()
379
+ ):
380
+ monitor.cancel()
381
+ await asyncio.gather(monitor, return_exceptions=True)
382
+ self._root.runtime_monitor_task = None
383
+ return terminal_failure
384
+
385
+ async def wait_for_termination(self) -> None:
386
+ """等待正常 stop 或受监督 runtime fatal 完整清理,不暴露内部 Task/Future。"""
387
+
388
+ allowed = (
389
+ ApplicationState.RUNNING,
390
+ ApplicationState.STOPPING,
391
+ ApplicationState.STOPPED,
392
+ ApplicationState.STOPPED_WITH_ERRORS,
393
+ )
394
+ if self._root.state not in allowed:
395
+ raise InvalidApplicationStateError(
396
+ operation="wait_for_termination",
397
+ current=self._root.state,
398
+ allowed=allowed,
399
+ )
400
+ await self._root.termination_event.wait()
401
+ failure = self._root.termination_failure
402
+ if failure is not None:
403
+ raise failure
404
+
405
+ async def _monitor_runtime_failure(self) -> None:
406
+ current = asyncio.current_task()
407
+ try:
408
+ # HostedService 只拥有 start/stop;持久任务等其他能力仍保留自身故障传播。
409
+ failure = await self._root.require_background_coordinator().wait_for_runtime_failure()
410
+ if self._root.runtime_failure is None:
411
+ self._root.runtime_failure = ApplicationRuntimeError(
412
+ component=failure.component,
413
+ error_type=failure.error_type,
414
+ )
415
+ self._root.runtime_failure._source = failure._source
416
+ try:
417
+ await self.stop()
418
+ except InvalidApplicationStateError:
419
+ # 显式 stop 已拥有 transition 时,由同一 cleanup 路径发布终态。
420
+ await self._root.termination_event.wait()
421
+ except BaseException:
422
+ # stop 已缓存同一个脱敏终态错误;monitor 不制造未观察异常。
423
+ pass
424
+ finally:
425
+ if self._root.runtime_monitor_task is current:
426
+ self._root.runtime_monitor_task = None
427
+
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)
524
+ try:
525
+ yield
526
+ finally:
527
+ runtime.reset_caller(token)
528
+
529
+ async def _invoke_registration(
530
+ self,
531
+ registration: ApplicationServiceRegistration,
532
+ method_name: str,
533
+ ) -> object:
534
+ """框架内部 contributor 仍走 dispatcher,但不发布 public single Contract。"""
535
+
536
+ if self._root.state not in (ApplicationState.STARTING, ApplicationState.RUNNING):
537
+ raise ApplicationInvocationRejectedError(
538
+ reason=f"Application must be running, got {self._root.state.value}"
539
+ )
540
+ service = self.application_service_catalog._for_registration(registration)
541
+ method = service.methods.get(method_name)
542
+ if method is None:
543
+ raise AssertionError("internal contributor method is absent from its catalog entry")
544
+ container = self._root.require_container()
545
+ async with self._invocation_lease_for_integration():
546
+ with self._caller_context_for_integration(CurrentUser.anonymous(), str(uuid4())):
547
+ async with container(scope=Scope.REQUEST) as request_container:
548
+ dispatcher = await request_container.get(_ApplicationServiceDispatcher)
549
+ return await dispatcher.invoke(service, method, (), {})