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,345 @@
1
+ """受管理异步方法的共享描述、策略解释与参数编译。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ from collections.abc import Awaitable, Callable, Mapping
7
+ from dataclasses import dataclass, field, replace
8
+ from types import MappingProxyType
9
+ from typing import Any, cast, get_type_hints
10
+
11
+ from pydantic import ConfigDict, TypeAdapter, validate_call
12
+
13
+ from ..application_services.errors import (
14
+ ApplicationServiceDefinitionError,
15
+ )
16
+ from ..auditing.control import _AUDITING_ATTRIBUTE
17
+ from ..authorization import PermissionCatalog, PermissionName
18
+ from ..authorization.contracts import (
19
+ _ALLOW_ANONYMOUS_ATTRIBUTE,
20
+ _AUTHORIZATION_ATTRIBUTE,
21
+ _authorization_requirement,
22
+ _AuthorizationRequirement,
23
+ )
24
+ from ..authorization.errors import AuthorizationDefinitionError
25
+ from ..diagnostics import SourceLocation
26
+ from ..extensions.catalog import ExtensionCatalog
27
+ from ..invocation.interceptors import InterceptionStage, InterceptorRegistration, _InterceptorRule
28
+ from ..invocation.signature import _business_signature
29
+ from ..modularity import ModuleKey
30
+ from ..unit_of_work.options import _UNIT_OF_WORK_ATTRIBUTE, UnitOfWorkDeclaration, _declaration
31
+ from .policies import _VALIDATION_ATTRIBUTE, ValidationEnabled, _effective_switch
32
+
33
+
34
+ @dataclass(frozen=True, slots=True)
35
+ class ServiceMethod:
36
+ name: str
37
+ source: SourceLocation
38
+ requires_authenticated_user: bool
39
+ permissions: tuple[PermissionName, ...]
40
+ allows_anonymous: bool
41
+ service: type[object]
42
+ implementation: type[object]
43
+ method: Callable[..., Awaitable[object]]
44
+ validation_enabled: bool
45
+ auditing_enabled: bool
46
+ policy_sources: tuple[SourceLocation, ...]
47
+ interceptors: tuple[InterceptorRegistration, ...] = ()
48
+ unit_of_work: UnitOfWorkDeclaration = field(default_factory=UnitOfWorkDeclaration)
49
+
50
+
51
+ _ValidatedArguments = tuple[tuple[object, ...], dict[str, object]]
52
+
53
+
54
+ @dataclass(frozen=True, slots=True)
55
+ class _CompiledMethod:
56
+ name: str
57
+ contract_method: Callable[..., Awaitable[object]]
58
+ implementation_method: Callable[..., Awaitable[object]]
59
+ validator: Callable[..., Awaitable[_ValidatedArguments]]
60
+ parameter_names: frozenset[str]
61
+ authorization: _AuthorizationRequirement
62
+ descriptor: ServiceMethod
63
+ signature: inspect.Signature
64
+ implementation_signature: inspect.Signature
65
+ interceptor_steps: Mapping[InterceptionStage, tuple[InterceptorRegistration, ...]]
66
+ policy_fingerprint: tuple[object, ...]
67
+ result_adapter: TypeAdapter[object] | None = None
68
+
69
+
70
+ def _compile_method(
71
+ contract: type[object],
72
+ implementation: type[object],
73
+ name: str,
74
+ owner: ModuleKey,
75
+ implementation_owner: ModuleKey,
76
+ permission_catalog: PermissionCatalog | None,
77
+ interceptor_rules: tuple[_InterceptorRule, ...],
78
+ *,
79
+ validation_default: bool = True,
80
+ auditing_default: bool = True,
81
+ unit_of_work_default: UnitOfWorkDeclaration,
82
+ extensions: ExtensionCatalog,
83
+ ) -> _CompiledMethod:
84
+ implementation_symbol = f"{implementation.__module__}.{implementation.__qualname__}"
85
+ contract_method = _require_async_instance_method(contract, name, role="contract")
86
+ implementation_method = _require_async_instance_method(
87
+ implementation,
88
+ name,
89
+ role="implementation",
90
+ )
91
+ contract_signature, annotations = _resolved_signature(contract_method)
92
+ implementation_signature, _ = _resolved_signature(implementation_method)
93
+ if contract_signature.return_annotation is inspect.Signature.empty:
94
+ raise ApplicationServiceDefinitionError(
95
+ symbol=f"{implementation_symbol}.{name}",
96
+ reason="public service methods must declare a return annotation",
97
+ )
98
+ if _business_signature(contract_signature) != _business_signature(implementation_signature):
99
+ raise ApplicationServiceDefinitionError(
100
+ symbol=f"{implementation_symbol}.{name}",
101
+ reason="implementation signature must exactly match its public contract",
102
+ )
103
+ declared_return = contract_signature.return_annotation
104
+ annotations = {name: extensions.annotation(value) for name, value in annotations.items()}
105
+ contract_signature = contract_signature.replace(
106
+ parameters=tuple(
107
+ parameter.replace(annotation=extensions.annotation(parameter.annotation))
108
+ for parameter in contract_signature.parameters.values()
109
+ ),
110
+ return_annotation=extensions.annotation(declared_return),
111
+ )
112
+ try:
113
+ validator = _compile_argument_validator(
114
+ contract_method,
115
+ contract_signature,
116
+ annotations,
117
+ )
118
+ except Exception as original_error:
119
+ raise ApplicationServiceDefinitionError(
120
+ symbol=f"{contract.__module__}.{contract.__qualname__}.{name}",
121
+ reason=f"Pydantic could not compile the method: {type(original_error).__name__}",
122
+ ) from None
123
+ source = _method_source(contract_method, owner)
124
+ authorization = _authorization_requirement(implementation, implementation_method)
125
+ unknown_permissions = tuple(
126
+ permission
127
+ for permission in authorization.permissions
128
+ if permission_catalog is None or permission not in permission_catalog
129
+ )
130
+ if unknown_permissions:
131
+ raise AuthorizationDefinitionError(
132
+ symbol=f"{contract.__module__}.{contract.__qualname__}.{name}",
133
+ reason=(
134
+ "authorization references unknown permissions: "
135
+ f"{tuple(str(item) for item in unknown_permissions)!r}"
136
+ ),
137
+ )
138
+ descriptor = ServiceMethod(
139
+ name=name,
140
+ source=source,
141
+ requires_authenticated_user=authorization.requires_authenticated_user,
142
+ permissions=authorization.permissions,
143
+ allows_anonymous=authorization.allows_anonymous,
144
+ service=contract,
145
+ implementation=implementation,
146
+ method=implementation_method,
147
+ unit_of_work=_declaration(implementation, implementation_method)
148
+ if any(
149
+ hasattr(item, _UNIT_OF_WORK_ATTRIBUTE)
150
+ for item in (implementation, implementation_method)
151
+ )
152
+ else unit_of_work_default,
153
+ validation_enabled=_effective_switch(
154
+ implementation, implementation_method, _VALIDATION_ATTRIBUTE, validation_default
155
+ ),
156
+ auditing_enabled=_effective_switch(
157
+ implementation, implementation_method, _AUDITING_ATTRIBUTE, auditing_default
158
+ ),
159
+ policy_sources=_policy_sources(implementation, implementation_method, implementation_owner),
160
+ )
161
+ descriptor = replace(
162
+ descriptor, interceptors=_match_interceptors(descriptor, interceptor_rules)
163
+ )
164
+ return _CompiledMethod(
165
+ name=name,
166
+ contract_method=contract_method,
167
+ implementation_method=implementation_method,
168
+ validator=validator,
169
+ parameter_names=frozenset(tuple(contract_signature.parameters)[1:]),
170
+ authorization=authorization,
171
+ descriptor=descriptor,
172
+ signature=contract_signature,
173
+ implementation_signature=implementation_signature.replace(
174
+ parameters=tuple(
175
+ parameter.replace(annotation=extensions.annotation(parameter.annotation))
176
+ for parameter in implementation_signature.parameters.values()
177
+ ),
178
+ return_annotation=extensions.annotation(implementation_signature.return_annotation),
179
+ ),
180
+ result_adapter=TypeAdapter(contract_signature.return_annotation)
181
+ if contract_signature.return_annotation != declared_return
182
+ else None,
183
+ policy_fingerprint=_policy_fingerprint(implementation, implementation_method),
184
+ interceptor_steps=MappingProxyType(
185
+ {
186
+ stage: tuple(item for item in descriptor.interceptors if item.before is stage)
187
+ for stage in InterceptionStage
188
+ }
189
+ ),
190
+ )
191
+
192
+
193
+ _POLICY_ATTRIBUTES = (
194
+ _UNIT_OF_WORK_ATTRIBUTE,
195
+ _AUTHORIZATION_ATTRIBUTE,
196
+ _ALLOW_ANONYMOUS_ATTRIBUTE,
197
+ _VALIDATION_ATTRIBUTE,
198
+ _AUDITING_ATTRIBUTE,
199
+ )
200
+
201
+
202
+ def _policy_fingerprint(implementation: type[object], method: object) -> tuple[object, ...]:
203
+ return (
204
+ issubclass(implementation, ValidationEnabled),
205
+ tuple(
206
+ tuple(vars(target).get(attribute) for attribute in _POLICY_ATTRIBUTES)
207
+ for target in (*implementation.__mro__, method)
208
+ if any(attribute in vars(target) for attribute in _POLICY_ATTRIBUTES)
209
+ ),
210
+ )
211
+
212
+
213
+ def _policy_sources(
214
+ implementation: type[object], method: Callable[..., Any], owner: ModuleKey
215
+ ) -> tuple[SourceLocation, ...]:
216
+ return tuple(
217
+ _method_source(target, owner)
218
+ for target in (*reversed(implementation.__mro__), method)
219
+ if any(attribute in vars(target) for attribute in _POLICY_ATTRIBUTES)
220
+ )
221
+
222
+
223
+ def _match_interceptors(
224
+ method: ServiceMethod, rules: tuple[_InterceptorRule, ...]
225
+ ) -> tuple[InterceptorRegistration, ...]:
226
+ matched: list[InterceptorRegistration] = []
227
+ for rule in rules:
228
+ try:
229
+ accepted = rule.match(method)
230
+ if not isinstance(accepted, bool):
231
+ raise TypeError("interceptor match must return bool")
232
+ except Exception as error:
233
+ raise ApplicationServiceDefinitionError(
234
+ symbol=rule.registration.source.symbol
235
+ or rule.registration.interceptor.__qualname__,
236
+ reason=(
237
+ f"interceptor match failed for {rule.registration.owner}: "
238
+ f"{type(error).__name__}"
239
+ ),
240
+ ) from None
241
+ if accepted:
242
+ matched.append(rule.registration)
243
+ return tuple(sorted(matched, key=lambda item: tuple(InterceptionStage).index(item.before)))
244
+
245
+
246
+ def _public_method_names(candidate: type[object], *, is_contract: bool) -> tuple[str, ...]:
247
+ names: dict[str, None] = {}
248
+ for owner in reversed(candidate.__mro__):
249
+ if owner is object:
250
+ continue
251
+ if is_contract and owner.__module__ == "typing":
252
+ continue
253
+ for name, value in owner.__dict__.items():
254
+ if name.startswith("_"):
255
+ continue
256
+ if inspect.isfunction(value) or isinstance(value, (staticmethod, classmethod)):
257
+ names[name] = None
258
+ return tuple(names)
259
+
260
+
261
+ def _require_async_instance_method(
262
+ owner: type[object],
263
+ name: str,
264
+ *,
265
+ role: str,
266
+ ) -> Callable[..., Awaitable[object]]:
267
+ symbol = f"{owner.__module__}.{owner.__qualname__}.{name}"
268
+ descriptor = inspect.getattr_static(owner, name)
269
+ if isinstance(descriptor, (staticmethod, classmethod)):
270
+ raise ApplicationServiceDefinitionError(
271
+ symbol=symbol,
272
+ reason=f"public {role} methods must be instance methods",
273
+ )
274
+ method = getattr(owner, name)
275
+ if not inspect.isfunction(method) or not inspect.iscoroutinefunction(method):
276
+ raise ApplicationServiceDefinitionError(
277
+ symbol=symbol,
278
+ reason=f"public {role} methods must use async def",
279
+ )
280
+ parameters = tuple(inspect.signature(method).parameters.values())
281
+ if not parameters or parameters[0].kind not in (
282
+ inspect.Parameter.POSITIONAL_ONLY,
283
+ inspect.Parameter.POSITIONAL_OR_KEYWORD,
284
+ ):
285
+ # Catalog 与 HTTP generator 都会剥离 receiver;缺少首个位置参数时不能伪装成实例方法。
286
+ raise ApplicationServiceDefinitionError(
287
+ symbol=symbol,
288
+ reason=f"public {role} methods must declare an instance receiver",
289
+ )
290
+ return cast(Callable[..., Awaitable[object]], method)
291
+
292
+
293
+ def _resolved_signature(
294
+ method: Callable[..., Awaitable[object]],
295
+ ) -> tuple[inspect.Signature, dict[str, object]]:
296
+ annotations = get_type_hints(method, include_extras=True)
297
+ signature = inspect.signature(method)
298
+ parameters = tuple(
299
+ parameter.replace(annotation=annotations.get(name, parameter.annotation))
300
+ for name, parameter in signature.parameters.items()
301
+ )
302
+ resolved = signature.replace(
303
+ parameters=parameters,
304
+ return_annotation=annotations.get("return", signature.return_annotation),
305
+ )
306
+ return resolved, annotations
307
+
308
+
309
+ def _compile_argument_validator(
310
+ method: Callable[..., Awaitable[object]],
311
+ signature: inspect.Signature,
312
+ annotations: Mapping[str, object],
313
+ ) -> Callable[..., Awaitable[_ValidatedArguments]]:
314
+ async def capture(*args: object, **kwargs: object) -> _ValidatedArguments:
315
+ # 第一个值是 validation-only self sentinel;业务 implementation 尚未解析或执行。
316
+ return tuple(args[1:]), dict(kwargs)
317
+
318
+ capture.__name__ = method.__name__
319
+ capture.__qualname__ = method.__qualname__
320
+ cast(Any, capture).__signature__ = signature
321
+ capture.__annotations__ = dict(annotations)
322
+ validated = validate_call(
323
+ config=ConfigDict(
324
+ arbitrary_types_allowed=True,
325
+ validate_by_name=True,
326
+ hide_input_in_errors=True,
327
+ )
328
+ )(capture)
329
+ return cast(Callable[..., Awaitable[_ValidatedArguments]], validated)
330
+
331
+
332
+ def _method_source(method: Any, owner: ModuleKey) -> SourceLocation:
333
+ file_name: str | None = None
334
+ line: int | None = None
335
+ try:
336
+ file_name = inspect.getsourcefile(method)
337
+ _, line = inspect.getsourcelines(method)
338
+ except (OSError, TypeError):
339
+ pass
340
+ return SourceLocation(
341
+ owner=str(owner),
342
+ file=file_name,
343
+ line=line,
344
+ symbol=f"{method.__module__}.{method.__qualname__}",
345
+ )
@@ -0,0 +1,113 @@
1
+ """Application-local 调用上下文、准入和入口编译的能力 owner。"""
2
+
3
+ from dishka import DEFAULT_COMPONENT, DependencyKey, Provider, Scope
4
+
5
+ from ..application_services.errors import ApplicationServiceDefinitionError
6
+ from ..application_services.invocation import _InvocationRuntime
7
+ from ..extensions.module import ExtensionsModule
8
+ from ..invocation.interceptors import _InterceptorRule
9
+ from ..lifecycle import ConfigureContext
10
+ from ..modularity import AppModule
11
+ from ..services.composition import (
12
+ ServiceBindingContext,
13
+ ServiceComposition,
14
+ ServiceValidationContext,
15
+ )
16
+ from .entries import _validate_entry
17
+ from .interception import InvocationEntry, _EntryInterceptionCatalog
18
+ from .managed_services import _ManagedServices
19
+ from .validation import _validate_interceptor_scopes
20
+
21
+
22
+ class InvocationModule(AppModule):
23
+ dependencies = (ExtensionsModule,)
24
+ catalog: _EntryInterceptionCatalog
25
+ invocation_runtime: _InvocationRuntime
26
+
27
+ def __init__(self) -> None:
28
+ self._managed_services = _ManagedServices()
29
+ self.entries: list[InvocationEntry] = []
30
+ self._rules: list[_InterceptorRule] = []
31
+ self._targets: dict[type[object], type[object]] = {}
32
+
33
+ @property
34
+ def rules(self) -> tuple[_InterceptorRule, ...]:
35
+ return tuple(self._rules)
36
+
37
+ def configure(self, context: ConfigureContext) -> None:
38
+ context.services.add_composition(
39
+ ServiceComposition(
40
+ bind=self._bind,
41
+ finalize=self._finalize,
42
+ decorate=self._decorate,
43
+ validate=self._validate,
44
+ )
45
+ )
46
+
47
+ def _bind(self, context: ServiceBindingContext) -> None:
48
+ application = context.application
49
+ runtime = _InvocationRuntime(
50
+ application_id=application.id,
51
+ state_getter=lambda: application.state,
52
+ )
53
+ self.invocation_runtime = runtime
54
+ self.runtime = runtime.runtime_participant()
55
+ provider = Provider()
56
+ provider.provide(lambda: runtime, provides=_InvocationRuntime, scope=Scope.APP)
57
+ provider.provide(runtime.context_for_action, scope=Scope.ACTION)
58
+ provider.provide(lambda: self.catalog, provides=_EntryInterceptionCatalog, scope=Scope.APP)
59
+ context.add_provider(provider)
60
+
61
+ def _finalize(self, context: ServiceBindingContext) -> None:
62
+ for entry in self.entries:
63
+ if isinstance(entry.target, type):
64
+ factory = context.arbitration.single_factory(
65
+ DependencyKey(entry.target, DEFAULT_COMPONENT),
66
+ )
67
+ if factory is None:
68
+ raise ApplicationServiceDefinitionError(
69
+ symbol=entry.target.__qualname__,
70
+ reason="entry requires a single binding",
71
+ )
72
+ self._targets[entry.target] = (
73
+ factory.source if isinstance(factory.source, type) else entry.target
74
+ )
75
+ from ..authorization.module import AuthorizationModule
76
+
77
+ permissions = (
78
+ context.modules.instance(AuthorizationModule).catalog
79
+ if any(
80
+ item.module_type is AuthorizationModule for item in context.modules.dependency_order
81
+ )
82
+ else None
83
+ )
84
+ self.catalog = _EntryInterceptionCatalog(
85
+ tuple(self.entries),
86
+ self.rules,
87
+ self._targets,
88
+ permissions,
89
+ context.modules.instance(ExtensionsModule).catalog,
90
+ )
91
+
92
+ def _validate(self, context: ServiceValidationContext) -> None:
93
+ self._managed_services.validate(context)
94
+ _validate_interceptor_scopes(context.native_graph, self.catalog.interceptor_scopes)
95
+ for entry in self.entries:
96
+ target = entry.target
97
+ if isinstance(target, type):
98
+ target = self._targets[target]
99
+ _validate_entry(
100
+ context.container,
101
+ target,
102
+ entry.method,
103
+ entry.business_parameters,
104
+ entry.method_scope,
105
+ )
106
+
107
+ def _decorate(self, context: ServiceBindingContext) -> None:
108
+ self._managed_services.decorate(
109
+ context,
110
+ self.invocation_runtime,
111
+ self.rules,
112
+ {entry.target for entry in self.entries} | set(self._targets.values()),
113
+ )
@@ -4,7 +4,8 @@ from __future__ import annotations
4
4
 
5
5
  from typing import TypeVar
6
6
 
7
- from .errors import ApplicationServiceDefinitionError
7
+ from ..application_services.errors import ApplicationServiceDefinitionError
8
+ from ..modularity import ModuleRef, ModuleRequirement
8
9
 
9
10
  _T = TypeVar("_T")
10
11
  _VALIDATION_ATTRIBUTE = "__python_ddd_framework_validation_enabled__"
@@ -44,3 +45,9 @@ def _effective_switch(
44
45
  )
45
46
  return value
46
47
  return default
48
+
49
+
50
+ class ValidationEnabled:
51
+ __validation_module__ = ModuleRequirement(
52
+ ModuleRef("python_ddd_framework.invocation.module:InvocationModule")
53
+ )
@@ -0,0 +1,25 @@
1
+ """根据已编译入口所需 scope 核对原生 DI 图。"""
2
+
3
+ from collections.abc import Iterable
4
+
5
+ from dishka import Scope
6
+
7
+ from ..application_services.errors import ApplicationServiceDefinitionError
8
+ from ..invocation.interceptors import InterceptorRegistration
9
+ from ..services.native_graph import _NativeServiceGraph
10
+
11
+
12
+ def _validate_interceptor_scopes(
13
+ graph: _NativeServiceGraph,
14
+ requirements: Iterable[tuple[InterceptorRegistration, Scope]],
15
+ ) -> None:
16
+ for registration, available in requirements:
17
+ scope = graph.interceptor_scope(registration.interceptor)
18
+ if scope not in (Scope.APP, Scope.REQUEST, Scope.ACTION) or scope > available:
19
+ raise ApplicationServiceDefinitionError(
20
+ symbol=registration.interceptor.__qualname__,
21
+ reason=(
22
+ f"interceptor scope is unavailable before {registration.before.value}; "
23
+ f"owner={registration.owner}"
24
+ ),
25
+ )
@@ -2,6 +2,7 @@
2
2
 
3
3
  from .composition import ConfigureContext, PostConfigureContext, PreConfigureContext
4
4
  from .context import ApplicationView, LifecycleContext
5
+ from .participants import RuntimeParticipant
5
6
  from .runtime import (
6
7
  InitializeContext,
7
8
  PostInitializeContext,
@@ -20,6 +21,7 @@ __all__ = (
20
21
  "PostInitializeContext",
21
22
  "PreConfigureContext",
22
23
  "PreInitializeContext",
24
+ "RuntimeParticipant",
23
25
  "ShutdownContext",
24
26
  "ShutdownReason",
25
27
  )
@@ -5,7 +5,8 @@ from __future__ import annotations
5
5
  from dataclasses import dataclass
6
6
 
7
7
  from ..configuration import ConfigurationRoot
8
- from ..events import EventContributor
8
+ from ..modularity import ModuleRegistry
9
+ from ..modularity.discovery import _DiscoveredType
9
10
  from ..options import PreOptionsSnapshot
10
11
  from ..options.contribution import _OptionsContributor
11
12
  from ..services import ServiceContributor
@@ -20,15 +21,17 @@ class PreConfigureContext(LifecycleContext):
20
21
 
21
22
  @dataclass(frozen=True, slots=True)
22
23
  class ConfigureContext(LifecycleContext):
24
+ modules: ModuleRegistry
25
+ discoveries: tuple[_DiscoveredType, ...]
23
26
  configuration: ConfigurationRoot
24
27
  pre_options: PreOptionsSnapshot
25
28
  configure: _OptionsContributor
26
29
  services: ServiceContributor
27
- events: EventContributor
28
30
 
29
31
 
30
32
  @dataclass(frozen=True, slots=True)
31
33
  class PostConfigureContext(LifecycleContext):
34
+ modules: ModuleRegistry
32
35
  configuration: ConfigurationRoot
33
36
  pre_options: PreOptionsSnapshot
34
37
  configure: _OptionsContributor
@@ -0,0 +1,33 @@
1
+ """能力贡献的运行生命周期;内核只编排回调,不解释具体协调器。"""
2
+
3
+ from collections.abc import Awaitable, Callable
4
+ from dataclasses import dataclass
5
+
6
+ from ..errors.lifecycle import ApplicationRuntimeError
7
+ from .state import ShutdownReason
8
+
9
+
10
+ @dataclass(frozen=True, slots=True)
11
+ class RuntimeParticipant:
12
+ start: Callable[[], Awaitable[None]]
13
+ stop: Callable[[ShutdownReason], Awaitable[tuple[BaseException, ...]]]
14
+ prepare: Callable[[bool], None] | None = None
15
+ close_admission: Callable[[], None] | None = None
16
+ ready: Callable[[], bool] | None = None
17
+ failure: Callable[[], Awaitable[ApplicationRuntimeError] | None] | None = None
18
+ drain: Callable[[], Awaitable[None]] | None = None
19
+
20
+ def __post_init__(self) -> None:
21
+ if not callable(self.start) or not callable(self.stop):
22
+ raise TypeError("runtime start and stop must be callable")
23
+ if any(
24
+ callback is not None and not callable(callback)
25
+ for callback in (
26
+ self.prepare,
27
+ self.close_admission,
28
+ self.ready,
29
+ self.failure,
30
+ self.drain,
31
+ )
32
+ ):
33
+ raise TypeError("runtime callbacks must be callable")
@@ -0,0 +1,25 @@
1
+ """有界进程内消息与显式状态快照合并。"""
2
+
3
+ from .channel import MessageChannel
4
+ from .contracts import (
5
+ MessageChannelDefinition,
6
+ MessageHandler,
7
+ MessageOutcome,
8
+ MessageRejectedError,
9
+ MessageState,
10
+ )
11
+ from .module import MessagingModule
12
+ from .options import MessagingOptions
13
+ from .receipt import MessageReceipt
14
+
15
+ __all__ = (
16
+ "MessageChannel",
17
+ "MessageChannelDefinition",
18
+ "MessageHandler",
19
+ "MessageOutcome",
20
+ "MessageReceipt",
21
+ "MessageRejectedError",
22
+ "MessageState",
23
+ "MessagingModule",
24
+ "MessagingOptions",
25
+ )