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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (334) hide show
  1. python_ddd_framework/__init__.py +139 -21
  2. python_ddd_framework/application/builder.py +23 -78
  3. python_ddd_framework/application/composition.py +4 -22
  4. python_ddd_framework/application/runtime.py +96 -291
  5. python_ddd_framework/application/state.py +7 -75
  6. python_ddd_framework/application_services/__init__.py +5 -21
  7. python_ddd_framework/{services/application_bindings.py → application_services/bindings.py} +52 -9
  8. python_ddd_framework/application_services/catalog.py +36 -288
  9. python_ddd_framework/application_services/contracts.py +14 -13
  10. python_ddd_framework/application_services/dispatcher.py +19 -269
  11. python_ddd_framework/application_services/invocation.py +126 -7
  12. python_ddd_framework/application_services/module.py +168 -0
  13. python_ddd_framework/application_services/pagination.py +21 -0
  14. python_ddd_framework/auditing/control.py +1 -1
  15. python_ddd_framework/auditing/module.py +15 -0
  16. python_ddd_framework/authorization/__init__.py +2 -0
  17. python_ddd_framework/authorization/contracts.py +7 -2
  18. python_ddd_framework/authorization/definitions.py +5 -0
  19. python_ddd_framework/authorization/module.py +40 -0
  20. python_ddd_framework/authorization/options.py +7 -0
  21. python_ddd_framework/background_execution/child.py +13 -3
  22. python_ddd_framework/background_execution/declarations.py +45 -0
  23. python_ddd_framework/background_execution/lifecycle.py +12 -0
  24. python_ddd_framework/background_execution/module.py +107 -1
  25. python_ddd_framework/background_jobs/catalog.py +59 -42
  26. python_ddd_framework/background_jobs/contracts.py +16 -19
  27. python_ddd_framework/background_jobs/pgqueuer/enqueue.py +4 -0
  28. python_ddd_framework/background_jobs/pgqueuer/module.py +2 -1
  29. python_ddd_framework/background_jobs/pgqueuer/runtime.py +7 -7
  30. python_ddd_framework/background_workers/catalog.py +23 -36
  31. python_ddd_framework/background_workers/contracts.py +3 -0
  32. python_ddd_framework/background_workers/execution.py +1 -1
  33. python_ddd_framework/background_workers/runtime.py +38 -1
  34. python_ddd_framework/caching/__init__.py +7 -6
  35. python_ddd_framework/caching/contracts.py +65 -67
  36. python_ddd_framework/caching/errors.py +0 -7
  37. python_ddd_framework/caching/module.py +18 -0
  38. python_ddd_framework/caching/naming.py +29 -0
  39. python_ddd_framework/caching/options.py +38 -0
  40. python_ddd_framework/caching/unit_of_work.py +46 -0
  41. python_ddd_framework/cli/__init__.py +31 -6
  42. python_ddd_framework/cli/project.py +68 -6
  43. python_ddd_framework/cli/runtime.py +1 -1
  44. python_ddd_framework/developer_kit/generation.py +75 -22
  45. python_ddd_framework/developer_kit/project_metadata.py +2 -4
  46. python_ddd_framework/developer_kit/templates/basic/cookiecutter.json +4 -0
  47. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/README.md +45 -0
  48. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/__init__.py.jinja +0 -0
  49. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/contracts/__init__.py.jinja +0 -0
  50. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/contracts/conversion_service.py.jinja +7 -0
  51. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/module.py.jinja +38 -0
  52. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/services/__init__.py.jinja +0 -0
  53. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/services/default_conversion_service.py.jinja +10 -0
  54. python_ddd_framework/developer_kit/templates/module/cookiecutter.json +1 -1
  55. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/README.md +41 -49
  56. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/__init__.py.jinja +1 -0
  57. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/__init__.py.jinja +1 -0
  58. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/handler.py.jinja +45 -0
  59. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/payload.py.jinja +8 -0
  60. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/__init__.py.jinja +1 -0
  61. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/handler.py.jinja +35 -0
  62. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/payload.py.jinja +5 -0
  63. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/schedule.py.jinja +15 -0
  64. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/__init__.py.jinja +1 -0
  65. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/order_maintenance_worker.py.jinja +28 -0
  66. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/order_statistics_worker.py.jinja +26 -0
  67. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/__init__.py.jinja +1 -0
  68. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/order_cache.py.jinja +9 -0
  69. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/statistics_cache.py.jinja +9 -0
  70. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/event_handlers/__init__.py.jinja +1 -0
  71. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/event_handlers/order_changed_handler.py.jinja +23 -0
  72. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/__init__.py.jinja +1 -0
  73. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/{integration.py.jinja → hosted_services/order_integration_service.py.jinja} +8 -2
  74. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/order_observation_handler.py.jinja +16 -0
  75. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration_services/__init__.py.jinja +1 -0
  76. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration_services/order_reporting_service.py.jinja +16 -0
  77. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/interceptors/__init__.py.jinja +1 -0
  78. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/interceptors/order_timing_interceptor.py.jinja +17 -0
  79. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/module.py.jinja +105 -10
  80. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/options/__init__.py.jinja +1 -0
  81. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/{options.py.jinja → options/order_options.py.jinja} +1 -1
  82. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/__init__.py.jinja +1 -0
  83. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_approval_service.py.jinja +65 -0
  84. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_management_service.py.jinja +35 -0
  85. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_query_service.py.jinja +41 -0
  86. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/__init__.py.jinja +1 -0
  87. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/approval_setting_observer.py.jinja +17 -0
  88. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/__init__.py.jinja +1 -0
  89. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/approve_order.py.jinja +6 -0
  90. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/create_order.py.jinja +7 -0
  91. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/integration_services/__init__.py.jinja +1 -0
  92. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/integration_services/order_reporting_service.py.jinja +7 -0
  93. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/module.py.jinja +39 -2
  94. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/__init__.py.jinja +1 -0
  95. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_approval_service.py.jinja +13 -0
  96. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_management_service.py.jinja +10 -0
  97. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_query_service.py.jinja +12 -0
  98. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/__init__.py.jinja +1 -0
  99. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/order_statistics_snapshot.py.jinja +9 -0
  100. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/order_view.py.jinja +12 -0
  101. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/entities/__init__.py.jinja +1 -0
  102. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/{orders.py.jinja → entities/order.py.jinja} +10 -18
  103. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/events/__init__.py.jinja +1 -0
  104. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/events/order_changed.py.jinja +13 -0
  105. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/module.py.jinja +42 -3
  106. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repositories/__init__.py.jinja +1 -0
  107. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repositories/order_repository.py.jinja +17 -0
  108. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding/__init__.py.jinja +1 -0
  109. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding/order_seed_contributor.py.jinja +22 -0
  110. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/services/__init__.py.jinja +1 -0
  111. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/services/order_approval_service.py.jinja +23 -0
  112. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/settings/__init__.py.jinja +1 -0
  113. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/{settings.py.jinja → settings/approval_settings.py.jinja} +3 -2
  114. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/value_objects/__init__.py.jinja +1 -0
  115. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/value_objects/order_title.py.jinja +10 -0
  116. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/constants/__init__.py.jinja +1 -0
  117. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/constants/order_constants.py.jinja +3 -0
  118. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/enums/__init__.py.jinja +1 -0
  119. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/enums/order_status.py.jinja +6 -0
  120. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/errors/__init__.py.jinja +1 -0
  121. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/errors/order_errors.py.jinja +11 -0
  122. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/__init__.py.jinja +1 -0
  123. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/order_messages.py.jinja +26 -0
  124. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/order_observation.py.jinja +13 -0
  125. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/module.py.jinja +40 -1
  126. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions/__init__.py.jinja +1 -0
  127. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/{permissions.py.jinja → permissions/order_permission_provider.py.jinja} +6 -2
  128. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions/order_permissions.py.jinja +7 -0
  129. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/value_objects/__init__.py.jinja +1 -0
  130. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/value_objects/money.py.jinja +18 -0
  131. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/filters/__init__.py.jinja +1 -0
  132. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/filters/export_filter.py.jinja +15 -0
  133. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/models/__init__.py.jinja +1 -0
  134. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/models/refresh_orders.py.jinja +5 -0
  135. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/module.py.jinja +85 -7
  136. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/routers/__init__.py.jinja +1 -0
  137. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/{files.py.jinja → routers/order_files.py.jinja} +23 -20
  138. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/websockets/__init__.py.jinja +1 -0
  139. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/websockets/order_socket.py.jinja +44 -0
  140. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/migrations/__init__.py.jinja +1 -1
  141. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/__init__.py.jinja +1 -7
  142. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/base.py.jinja +7 -0
  143. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/{orders.py.jinja → order_model.py.jinja} +7 -3
  144. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/module.py.jinja +42 -5
  145. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/order_repository.py.jinja +79 -0
  146. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/__init__.py.jinja +1 -0
  147. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/test_domain.py.jinja +4 -3
  148. python_ddd_framework/developer_kit/templates/project/cookiecutter.json +1 -1
  149. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/AGENTS.md +16 -3
  150. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/README.md +29 -22
  151. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{app.development.yaml.jinja → backend/app.development.yaml.jinja} +2 -0
  152. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{pyproject.toml.jinja → backend/pyproject.toml.jinja} +1 -1
  153. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/main.py.jinja +2 -0
  154. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/module.py.jinja +40 -3
  155. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/architecture.md +40 -10
  156. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/development.md +134 -23
  157. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/scripts/.gitkeep +0 -0
  158. python_ddd_framework/distributed_lock/contracts.py +4 -1
  159. python_ddd_framework/domain/__init__.py +2 -0
  160. python_ddd_framework/domain/services.py +11 -0
  161. python_ddd_framework/errors/business.py +10 -1
  162. python_ddd_framework/errors/lifecycle.py +3 -31
  163. python_ddd_framework/events/__init__.py +5 -2
  164. python_ddd_framework/events/aggregate.py +3 -3
  165. python_ddd_framework/events/catalog.py +12 -11
  166. python_ddd_framework/events/contracts.py +6 -1
  167. python_ddd_framework/events/contribution.py +1 -52
  168. python_ddd_framework/events/entities.py +23 -0
  169. python_ddd_framework/events/module.py +97 -0
  170. python_ddd_framework/events/runtime.py +80 -29
  171. python_ddd_framework/events/subscription.py +36 -0
  172. python_ddd_framework/events/unit_of_work.py +25 -0
  173. python_ddd_framework/extensions/__init__.py +22 -0
  174. python_ddd_framework/extensions/catalog.py +198 -0
  175. python_ddd_framework/extensions/contracts.py +85 -0
  176. python_ddd_framework/extensions/models.py +142 -0
  177. python_ddd_framework/extensions/module.py +21 -0
  178. python_ddd_framework/fastapi/__init__.py +2 -0
  179. python_ddd_framework/fastapi/action.py +1 -2
  180. python_ddd_framework/fastapi/adapter.py +16 -0
  181. python_ddd_framework/fastapi/application_services.py +43 -25
  182. python_ddd_framework/fastapi/background.py +2 -1
  183. python_ddd_framework/fastapi/contracts.py +26 -8
  184. python_ddd_framework/fastapi/health.py +1 -1
  185. python_ddd_framework/fastapi/module.py +14 -0
  186. python_ddd_framework/fastapi/parameters.py +3 -18
  187. python_ddd_framework/fastapi/realtime/connection.py +10 -7
  188. python_ddd_framework/fastapi/realtime/module.py +3 -0
  189. python_ddd_framework/fastapi/realtime/runtime.py +10 -24
  190. python_ddd_framework/fastapi/request_context.py +2 -1
  191. python_ddd_framework/fastapi/routing.py +2 -1
  192. python_ddd_framework/fastapi/server.py +8 -0
  193. python_ddd_framework/fastapi/settings.py +2 -1
  194. python_ddd_framework/hosted_services/bridge.py +20 -1
  195. python_ddd_framework/hosted_services/catalog.py +6 -16
  196. python_ddd_framework/hosted_services/contracts.py +4 -0
  197. python_ddd_framework/hosted_services/declarations.py +15 -0
  198. python_ddd_framework/hosted_services/module.py +69 -0
  199. python_ddd_framework/hosted_services/runtime.py +30 -1
  200. python_ddd_framework/identity/__init__.py +24 -0
  201. python_ddd_framework/identity/application.py +38 -90
  202. python_ddd_framework/identity/contracts.py +181 -16
  203. python_ddd_framework/identity/http_api.py +79 -2
  204. python_ddd_framework/identity/management.py +166 -0
  205. python_ddd_framework/identity/module.py +12 -4
  206. python_ddd_framework/identity/services.py +29 -16
  207. python_ddd_framework/identity/sqlalchemy/migrations/0003_extra_properties.py +26 -0
  208. python_ddd_framework/identity/sqlalchemy/migrations/0004_concurrency_version.py +29 -0
  209. python_ddd_framework/identity/sqlalchemy/models.py +21 -2
  210. python_ddd_framework/identity/sqlalchemy/module.py +11 -2
  211. python_ddd_framework/identity/sqlalchemy/repository.py +458 -0
  212. python_ddd_framework/identity/sqlalchemy/session_security.py +88 -0
  213. python_ddd_framework/identity/sqlalchemy/stores.py +69 -290
  214. python_ddd_framework/identity/tokens.py +2 -5
  215. python_ddd_framework/{application_services/validation.py → invocation/arguments.py} +8 -6
  216. python_ddd_framework/invocation/contribution.py +61 -0
  217. python_ddd_framework/invocation/dispatcher.py +291 -0
  218. python_ddd_framework/invocation/entries.py +2 -25
  219. python_ddd_framework/invocation/entrypoints.py +126 -0
  220. python_ddd_framework/{application_services → invocation}/execution.py +8 -5
  221. python_ddd_framework/invocation/function_runtime.py +3 -1
  222. python_ddd_framework/invocation/interception.py +113 -42
  223. python_ddd_framework/{application_services → invocation}/interceptors.py +2 -2
  224. python_ddd_framework/invocation/managed_proxy.py +116 -0
  225. python_ddd_framework/invocation/managed_services.py +250 -0
  226. python_ddd_framework/invocation/methods.py +345 -0
  227. python_ddd_framework/invocation/module.py +113 -0
  228. python_ddd_framework/{application_services → invocation}/policies.py +8 -1
  229. python_ddd_framework/invocation/validation.py +25 -0
  230. python_ddd_framework/lifecycle/__init__.py +2 -0
  231. python_ddd_framework/lifecycle/composition.py +5 -2
  232. python_ddd_framework/lifecycle/participants.py +33 -0
  233. python_ddd_framework/messaging/__init__.py +25 -0
  234. python_ddd_framework/messaging/channel.py +205 -0
  235. python_ddd_framework/messaging/contracts.py +69 -0
  236. python_ddd_framework/messaging/module.py +101 -0
  237. python_ddd_framework/messaging/options.py +13 -0
  238. python_ddd_framework/messaging/receipt.py +27 -0
  239. python_ddd_framework/messaging/runtime.py +124 -0
  240. python_ddd_framework/modularity/__init__.py +11 -1
  241. python_ddd_framework/modularity/contracts.py +47 -16
  242. python_ddd_framework/modularity/discovery.py +21 -2
  243. python_ddd_framework/modularity/graph.py +45 -1
  244. python_ddd_framework/modularity/registry.py +83 -3
  245. python_ddd_framework/notifications/__init__.py +2 -0
  246. python_ddd_framework/notifications/catalog.py +3 -2
  247. python_ddd_framework/notifications/declarations.py +18 -0
  248. python_ddd_framework/notifications/module.py +21 -0
  249. python_ddd_framework/observability/logging.py +3 -0
  250. python_ddd_framework/observability/tracing.py +3 -0
  251. python_ddd_framework/options/contribution.py +12 -0
  252. python_ddd_framework/options/registry.py +44 -11
  253. python_ddd_framework/realtime/__init__.py +2 -1
  254. python_ddd_framework/realtime/contracts.py +3 -7
  255. python_ddd_framework/realtime/messages.py +26 -1
  256. python_ddd_framework/redis/cache.py +243 -0
  257. python_ddd_framework/redis/cache_scripts.py +40 -0
  258. python_ddd_framework/redis/distributed_lock.py +10 -2
  259. python_ddd_framework/redis/module.py +13 -4
  260. python_ddd_framework/redis/notifications.py +5 -2
  261. python_ddd_framework/redis/runtime.py +1 -79
  262. python_ddd_framework/seeding/__init__.py +13 -0
  263. python_ddd_framework/seeding/contracts.py +45 -0
  264. python_ddd_framework/seeding/module.py +73 -0
  265. python_ddd_framework/seeding/runtime.py +63 -0
  266. python_ddd_framework/services/arbitration.py +23 -0
  267. python_ddd_framework/services/binding.py +2 -3
  268. python_ddd_framework/services/composition.py +108 -0
  269. python_ddd_framework/services/contribution.py +44 -54
  270. python_ddd_framework/services/convention.py +53 -181
  271. python_ddd_framework/services/fixed_lifetime.py +12 -15
  272. python_ddd_framework/services/framework_provider.py +3 -158
  273. python_ddd_framework/services/native_graph.py +35 -60
  274. python_ddd_framework/services/provider.py +59 -4
  275. python_ddd_framework/services/repository.py +6 -0
  276. python_ddd_framework/services/runtime.py +129 -215
  277. python_ddd_framework/settings/catalog.py +5 -4
  278. python_ddd_framework/settings/declarations.py +18 -0
  279. python_ddd_framework/settings/definitions.py +5 -0
  280. python_ddd_framework/settings/manager.py +1 -1
  281. python_ddd_framework/settings/module.py +50 -1
  282. python_ddd_framework/settings/notifications.py +4 -2
  283. python_ddd_framework/settings/refresh_module.py +9 -2
  284. python_ddd_framework/settings/sqlalchemy/module.py +2 -1
  285. python_ddd_framework/sqlalchemy/__init__.py +12 -0
  286. python_ddd_framework/sqlalchemy/alembic_runtime/env.py +1 -0
  287. python_ddd_framework/sqlalchemy/auditing.py +7 -1
  288. python_ddd_framework/sqlalchemy/extensions.py +186 -0
  289. python_ddd_framework/sqlalchemy/metadata.py +110 -6
  290. python_ddd_framework/sqlalchemy/migration.py +61 -8
  291. python_ddd_framework/sqlalchemy/migration_operations.py +86 -0
  292. python_ddd_framework/sqlalchemy/module.py +14 -11
  293. python_ddd_framework/sqlalchemy/module_migration.py +71 -3
  294. python_ddd_framework/sqlalchemy/repository.py +217 -13
  295. python_ddd_framework/sqlalchemy/session_provider.py +14 -1
  296. python_ddd_framework/testing/runtime.py +5 -4
  297. python_ddd_framework/unit_of_work/__init__.py +2 -1
  298. python_ddd_framework/unit_of_work/contracts.py +6 -7
  299. python_ddd_framework/unit_of_work/errors.py +11 -0
  300. python_ddd_framework/unit_of_work/lifecycle.py +19 -0
  301. python_ddd_framework/unit_of_work/manager.py +86 -32
  302. python_ddd_framework/unit_of_work/module.py +61 -0
  303. python_ddd_framework/unit_of_work/options.py +7 -1
  304. python_ddd_framework/validation/__init__.py +1 -0
  305. python_ddd_framework/validation/models.py +37 -0
  306. {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/METADATA +139 -44
  307. python_ddd_framework-0.4.0.dist-info/RECORD +488 -0
  308. {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/WHEEL +1 -1
  309. python_ddd_framework/application_services/seeding.py +0 -72
  310. python_ddd_framework/caching/catalog.py +0 -73
  311. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/cache.py.jinja +0 -10
  312. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/events.py.jinja +0 -24
  313. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/orders.py.jinja +0 -83
  314. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/tasks.py.jinja +0 -84
  315. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/orders.py.jinja +0 -35
  316. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repository.py.jinja +0 -14
  317. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding.py.jinja +0 -19
  318. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/definitions.py.jinja +0 -24
  319. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/realtime.py.jinja +0 -36
  320. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/orders.py.jinja +0 -49
  321. python_ddd_framework-0.3.2.dist-info/RECORD +0 -355
  322. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.dockerignore → backend/.dockerignore} +0 -0
  323. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.gitignore → backend/.gitignore} +0 -0
  324. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.python-version → backend/.python-version} +0 -0
  325. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{Dockerfile → backend/Dockerfile} +0 -0
  326. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{compose.dev.yaml.jinja → backend/compose.dev.yaml.jinja} +0 -0
  327. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{compose.production.yaml.jinja → backend/compose.production.yaml.jinja} +0 -0
  328. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/__init__.py.jinja +0 -0
  329. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{tests → backend/tests}/conftest.py.jinja +0 -0
  330. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{tests → backend/tests}/host/test_http.py.jinja +0 -0
  331. /python_ddd_framework/{application_services → invocation}/signature.py +0 -0
  332. {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/entry_points.txt +0 -0
  333. {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/licenses/LICENSE +0 -0
  334. {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/licenses/src/python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt +0 -0
@@ -1,228 +1,104 @@
1
- """Dishka container 构建、native graph 核对与 catalog 发布。"""
1
+ """模块贡献、最终绑定仲裁与原生容器构建;不枚举具体能力。"""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
5
  from collections.abc import Mapping
6
- from dataclasses import dataclass
7
- from typing import get_origin
6
+ from dataclasses import dataclass, replace
7
+ from typing import TYPE_CHECKING, get_origin
8
8
 
9
- from dishka import (
10
- DEFAULT_COMPONENT,
11
- STRICT_VALIDATION,
12
- AsyncContainer,
13
- DependencyKey,
14
- make_async_container,
15
- )
9
+ from dishka import STRICT_VALIDATION, AsyncContainer, make_async_container
16
10
 
17
- from ..application_services import ApplicationServiceCatalog
18
- from ..application_services.catalog import (
19
- _build_application_service_catalog,
20
- )
21
- from ..application_services.invocation import (
22
- _InvocationRuntime,
23
- )
24
- from ..authorization import PermissionCatalog
25
- from ..background_jobs import BackgroundJobCatalog
26
- from ..background_workers import BackgroundWorkerCatalog
27
- from ..background_workers.runtime import _BackgroundCoordinator
28
- from ..caching import CacheCatalog
29
11
  from ..configuration import ConfigurationRoot
30
12
  from ..errors import CatalogNativeGraphMismatchError, ContainerValidationError
31
- from ..events import LocalEventCatalog
32
- from ..events.catalog import _build_event_catalog, _validate_event_scopes
33
- from ..events.contribution import EventContributionRegistry
34
- from ..events.discovery import _discover_event_handlers
35
- from ..hosted_services import HostedServiceCatalog
36
- from ..hosted_services.catalog import (
37
- _validate_hosted_service_drafts,
38
- _validate_hosted_service_entries,
39
- )
40
- from ..hosted_services.runtime import _HostedServiceCoordinator
41
- from ..invocation.entries import _validate_callable_entries
42
- from ..invocation.interception import _EntryInterceptionCatalog
43
- from ..modularity import ModuleKey, ModuleRegistry
44
- from ..modularity.discovery import _DiscoveredType
45
- from ..notifications import NotificationCatalog
13
+ from ..modularity import ModuleDescriptor, ModuleKey, ModuleRegistry
14
+ from ..modularity.discovery import _DiscoveredType, _require_type_dependencies
46
15
  from ..options import Options, OptionsSnapshot
47
- from ..settings import SettingCatalog
48
- from ..settings.binding import _provide_settings
49
- from ..settings.store import RuntimeSettingStore
50
- from ..unit_of_work.contracts import _UnitOfWorkFactory
51
- from .application_bindings import (
52
- _bind_application_services,
53
- _runtime_providers,
54
- _validate_application_service_dependencies,
55
- )
56
- from .arbitration import (
57
- _RegistrationDraft,
58
- _ServiceArbitrator,
16
+ from .arbitration import _RegistrationDraft, _ServiceArbitrator
17
+ from .composition import (
18
+ ServiceBindingContext,
19
+ ServiceComposition,
20
+ ServiceDiscoveryContext,
21
+ ServiceValidationContext,
59
22
  )
60
23
  from .contribution import ServiceContributionRegistry
61
24
  from .convention import _ConventionScanner
62
25
  from .convention_contracts import ConventionCandidate
63
- from .framework_provider import (
64
- _framework_provider,
65
- )
66
- from .native_graph import (
67
- _accepted_single_service_scopes,
68
- _NativeServiceGraph,
69
- _validate_interceptors,
70
- )
71
- from .registration import (
72
- RegistrationDecision,
73
- RegistrationKind,
74
- ServiceCatalog,
75
- ServiceRegistration,
76
- )
26
+ from .framework_provider import _framework_provider
27
+ from .native_graph import _NativeServiceGraph
28
+ from .provider import _NativeProviderInspector
29
+ from .registration import RegistrationDecision, ServiceCatalog, ServiceRegistration
30
+
31
+ if TYPE_CHECKING:
32
+ from ..application import Application
33
+ from ..application.build_spec import _ApplicationBuildSpec
77
34
 
78
35
 
79
36
  @dataclass(frozen=True, slots=True)
80
37
  class _ServiceRuntime:
81
38
  catalog: ServiceCatalog
82
- application_service_catalog: ApplicationServiceCatalog
83
- event_catalog: LocalEventCatalog
84
39
  container: AsyncContainer
85
40
 
86
41
 
87
42
  class _ServiceRuntimeBuilder:
88
- """完成一次 Application-local scan、仲裁、容器构建和诊断快照发布。"""
89
-
90
- __slots__ = (
91
- "_background_coordinator",
92
- "_background_job_catalog",
93
- "_background_worker_catalog",
94
- "_cache_catalog",
95
- "_configuration",
96
- "_contributions",
97
- "_discoveries",
98
- "_event_contributions",
99
- "_hosted_service_catalog",
100
- "_hosted_service_coordinator",
101
- "_invocation_runtime",
102
- "_notification_catalog",
103
- "_options",
104
- "_permission_catalog",
105
- "_registry",
106
- "_scan_packages",
107
- "_setting_catalog",
108
- )
109
-
110
43
  def __init__(
111
44
  self,
112
45
  registry: ModuleRegistry,
113
46
  contributions: ServiceContributionRegistry,
114
- event_contributions: EventContributionRegistry,
115
47
  scan_packages: Mapping[ModuleKey, tuple[str, ...]],
116
48
  discoveries: tuple[_DiscoveredType, ...],
117
49
  configuration: ConfigurationRoot,
118
50
  options: OptionsSnapshot,
119
- invocation_runtime: _InvocationRuntime,
120
- permission_catalog: PermissionCatalog,
121
- cache_catalog: CacheCatalog,
122
- setting_catalog: SettingCatalog,
123
- notification_catalog: NotificationCatalog,
124
- background_worker_catalog: BackgroundWorkerCatalog,
125
- background_job_catalog: BackgroundJobCatalog,
126
- background_coordinator: _BackgroundCoordinator,
127
- hosted_service_catalog: HostedServiceCatalog,
128
- hosted_service_coordinator: _HostedServiceCoordinator,
51
+ application: Application,
52
+ build_spec: _ApplicationBuildSpec,
129
53
  ) -> None:
130
54
  self._registry = registry
131
55
  self._contributions = contributions
132
- self._event_contributions = event_contributions
133
56
  self._scan_packages = scan_packages
134
57
  self._discoveries = discoveries
135
58
  self._configuration = configuration
136
59
  self._options = options
137
- self._invocation_runtime = invocation_runtime
138
- self._permission_catalog = permission_catalog
139
- self._cache_catalog = cache_catalog
140
- self._setting_catalog = setting_catalog
141
- self._notification_catalog = notification_catalog
142
- self._background_worker_catalog = background_worker_catalog
143
- self._background_job_catalog = background_job_catalog
144
- self._background_coordinator = background_coordinator
145
- self._hosted_service_catalog = hosted_service_catalog
146
- self._hosted_service_coordinator = hosted_service_coordinator
60
+ self._application = application
61
+ self._build_spec = build_spec
147
62
 
148
63
  def build(self) -> _ServiceRuntime:
149
- event_handler_scopes = _discover_event_handlers(
64
+ compositions = self._contributions.compositions
65
+ discovery = ServiceDiscoveryContext(
150
66
  self._registry,
151
67
  self._discoveries,
152
- self._event_contributions,
68
+ self._contributions,
69
+ {},
153
70
  )
71
+ for _, composition in compositions:
72
+ if composition.discover is not None:
73
+ composition.discover(discovery)
154
74
  scanner = _ConventionScanner(
155
75
  self._registry,
156
76
  self._contributions,
157
77
  self._scan_packages,
158
78
  self._discoveries,
159
- event_handler_scopes,
79
+ discovery.conventions,
160
80
  )
161
81
  convention_candidates = scanner.discover()
162
82
  entries = self._contributions.freeze()
163
- _validate_hosted_service_entries(entries, self._hosted_service_catalog)
83
+ for _, composition in compositions:
84
+ if composition.before_binding is not None:
85
+ composition.before_binding(entries)
164
86
  arbitration = _ServiceArbitrator(self._registry).arbitrate(entries)
165
87
  scanner.validate_lifetimes(arbitration.drafts)
166
- _validate_hosted_service_drafts(arbitration.drafts, self._hosted_service_catalog)
167
- bound_application_services = _bind_application_services(
168
- scanner.application_services,
169
- arbitration.drafts,
170
- )
171
- _validate_application_service_dependencies(
172
- self._registry, bound_application_services, arbitration.drafts
173
- )
174
- application_service_catalog = _build_application_service_catalog(
175
- tuple(item.binding for item in bound_application_services),
176
- self._permission_catalog,
177
- self._contributions.interceptor_rules,
178
- )
179
- event_catalog = _build_event_catalog(
88
+ binding = ServiceBindingContext(
89
+ self._registry.dependency_order[-1],
180
90
  self._registry,
181
- self._event_contributions,
182
- )
183
- entry_interceptors = _EntryInterceptionCatalog(
184
- event_catalog,
185
- self._contributions.interceptor_rules,
186
- self._background_worker_catalog,
187
- self._background_job_catalog,
188
- )
189
- framework_provider = _framework_provider(
190
- entry_interceptors=entry_interceptors,
191
- has_unit_of_work_factory=any(
192
- draft.key.type_hint is _UnitOfWorkFactory for draft in arbitration.drafts
193
- ),
194
- registry=self._registry,
195
- options=self._options,
196
- invocation_runtime=self._invocation_runtime,
197
- application_service_catalog=application_service_catalog,
198
- event_catalog=event_catalog,
199
- permission_catalog=self._permission_catalog,
200
- cache_catalog=self._cache_catalog,
201
- setting_catalog=self._setting_catalog,
202
- notification_catalog=self._notification_catalog,
203
- background_worker_catalog=self._background_worker_catalog,
204
- background_job_catalog=self._background_job_catalog,
205
- background_coordinator=self._background_coordinator,
206
- hosted_service_catalog=self._hosted_service_catalog,
207
- hosted_service_coordinator=self._hosted_service_coordinator,
208
- configuration=self._configuration,
209
- )
210
- _provide_settings(
211
- framework_provider,
212
- self._setting_catalog,
213
91
  self._configuration,
214
- # Alias 的 scope 由原生图解析,不能以 draft.scope 是否已知遗漏有效 binding。
215
- has_store=any(
216
- draft.key == DependencyKey(RuntimeSettingStore, DEFAULT_COMPONENT)
217
- and draft.decision is RegistrationDecision.ACCEPTED
218
- and draft.contribution.kind is not RegistrationKind.MULTI
219
- and not draft.is_collection
220
- for draft in arbitration.drafts
221
- ),
92
+ self._options,
93
+ arbitration,
94
+ list(arbitration.providers),
95
+ [],
96
+ [],
97
+ self._application,
98
+ self._build_spec,
222
99
  )
223
- runtime_providers = _runtime_providers(arbitration, bound_application_services)
100
+ self._bind_capabilities(binding, compositions)
224
101
  try:
225
- # Options 的唯一 owner 是阶段绑定快照;手工 provider 也不能为未声明类型创建旁路。
226
102
  if any(
227
103
  draft.key.type_hint is Options or get_origin(draft.key.type_hint) is Options
228
104
  for draft in arbitration.drafts
@@ -231,8 +107,12 @@ class _ServiceRuntimeBuilder:
231
107
  "Options bindings must come from Module configuration declarations"
232
108
  )
233
109
  container = make_async_container(
234
- *runtime_providers,
235
- framework_provider,
110
+ *binding._providers,
111
+ _framework_provider(
112
+ registry=self._registry,
113
+ configuration=self._configuration,
114
+ options=self._options,
115
+ ),
236
116
  context={
237
117
  Options[model_type]: Options(value) # type: ignore[valid-type]
238
118
  for model_type, value in self._options.values.items()
@@ -240,63 +120,97 @@ class _ServiceRuntimeBuilder:
240
120
  validation_settings=STRICT_VALIDATION,
241
121
  )
242
122
  except Exception as original_error:
243
- error = ContainerValidationError(
123
+ raise ContainerValidationError(
244
124
  sources=tuple(entry.contribution.source for entry in arbitration.ordered_entries),
245
125
  original_error=original_error,
246
- )
247
- raise error from original_error
248
-
249
- native_graph = _NativeServiceGraph(container)
126
+ ) from original_error
127
+ native_graph = _NativeServiceGraph(container, tuple(binding._decorators))
250
128
  native_graph.validate_fixed_lifetimes()
251
- _validate_event_scopes(
252
- event_catalog,
253
- _accepted_single_service_scopes(arbitration.drafts, native_graph),
254
- )
255
- _validate_interceptors(native_graph, application_service_catalog, entry_interceptors)
256
- _validate_callable_entries(
257
- container,
258
- self._background_job_catalog,
259
- self._background_worker_catalog,
260
- event_catalog,
261
- event_implementations={
262
- item.handler_type: native_graph.implementation_for(item.handler_type)
263
- for item in event_catalog.subscriptions
264
- if isinstance(item.handler_type, type)
265
- },
266
- )
267
129
  differences = native_graph.differences(arbitration.drafts)
268
130
  if differences:
269
131
  raise CatalogNativeGraphMismatchError(differences=differences)
132
+ for descriptor, composition in compositions:
133
+ if composition.validate is not None:
134
+ composition.validate(
135
+ ServiceValidationContext(
136
+ replace(binding, descriptor=descriptor), container, native_graph
137
+ )
138
+ )
270
139
  return _ServiceRuntime(
271
- catalog=self._freeze_catalog(
272
- arbitration.drafts,
273
- native_graph,
274
- convention_candidates,
275
- ),
276
- application_service_catalog=application_service_catalog,
277
- event_catalog=event_catalog,
278
- container=container,
140
+ self._freeze_catalog(arbitration.drafts, native_graph, convention_candidates),
141
+ container,
279
142
  )
280
143
 
144
+ @staticmethod
145
+ def _bind_capabilities(
146
+ binding: ServiceBindingContext,
147
+ compositions: tuple[tuple[ModuleDescriptor, ServiceComposition], ...],
148
+ ) -> None:
149
+ # 必须先裁决替换链,再解释能力的最终实现与 scope,避免 Catalog 保存落选事实。
150
+ for descriptor, composition in compositions:
151
+ if composition.bind is not None:
152
+ composition.bind(replace(binding, descriptor=descriptor))
153
+ # 入口来自所有能力的最终绑定;统一屏障后冻结,容器只消费已确定的事实。
154
+ for descriptor, composition in compositions:
155
+ if composition.finalize is not None:
156
+ composition.finalize(replace(binding, descriptor=descriptor))
157
+ # 所有派生绑定已完成后才装饰,不能漏掉后继能力 finalize 提供的服务。
158
+ # replace 生成的各 owner 视图共享准入状态,捕获旧 context 也不能迟到贡献。
159
+ binding._provider_registration.closed = True
160
+ for descriptor, composition in compositions:
161
+ if composition.decorate is not None:
162
+ composition.decorate(replace(binding, descriptor=descriptor))
163
+ _ServiceRuntimeBuilder._validate_final_contributions(binding)
164
+
165
+ @staticmethod
166
+ def _validate_final_contributions(binding: ServiceBindingContext) -> None:
167
+ # 回调不得在仲裁后修改原生贡献,也不得通过派生 Provider 偷渡 override。
168
+ for entry in binding.arbitration.ordered_entries:
169
+ contribution = entry.contribution
170
+ _NativeProviderInspector(
171
+ source=contribution.source, kind=contribution.kind
172
+ ).assert_unchanged(contribution.provider, entry.signature)
173
+ for draft in binding.arbitration.drafts:
174
+ if draft.decision is RegistrationDecision.ACCEPTED and draft.factory is not None:
175
+ for candidate in (draft.key.type_hint, draft.factory.source):
176
+ _require_type_dependencies(
177
+ binding.modules,
178
+ draft.contribution.owner,
179
+ candidate,
180
+ source=draft.contribution.source,
181
+ )
182
+ for descriptor, inspector, provider, signature in binding._provider_checks:
183
+ inspector.assert_unchanged(provider, signature)
184
+ for item in inspector.inspect(provider).factories:
185
+ for candidate in (item.key.type_hint, item.factory.source):
186
+ _require_type_dependencies(
187
+ binding.modules,
188
+ descriptor.key,
189
+ candidate,
190
+ source=descriptor.declaration_source,
191
+ )
192
+
281
193
  @staticmethod
282
194
  def _freeze_catalog(
283
195
  drafts: tuple[_RegistrationDraft, ...],
284
196
  native_graph: _NativeServiceGraph,
285
197
  convention_candidates: tuple[ConventionCandidate, ...],
286
198
  ) -> ServiceCatalog:
287
- registrations = tuple(
288
- ServiceRegistration(
289
- key=draft.key,
290
- scope=native_graph.scope_for(draft),
291
- owner=draft.contribution.owner,
292
- source=draft.contribution.source,
293
- kind=draft.contribution.kind,
294
- declaration_index=draft.contribution.declaration_index,
295
- decision=draft.decision,
296
- decision_reason=draft.decision_reason,
297
- replaced_sources=draft.replaced_sources,
298
- multi_ordinal=draft.multi_ordinal,
299
- )
300
- for draft in sorted(drafts, key=lambda item: item.sequence)
199
+ return ServiceCatalog(
200
+ tuple(
201
+ ServiceRegistration(
202
+ key=draft.key,
203
+ scope=native_graph.scope_for(draft),
204
+ owner=draft.contribution.owner,
205
+ source=draft.contribution.source,
206
+ kind=draft.contribution.kind,
207
+ declaration_index=draft.contribution.declaration_index,
208
+ decision=draft.decision,
209
+ decision_reason=draft.decision_reason,
210
+ replaced_sources=draft.replaced_sources,
211
+ multi_ordinal=draft.multi_ordinal,
212
+ )
213
+ for draft in sorted(drafts, key=lambda item: item.sequence)
214
+ ),
215
+ convention_candidates,
301
216
  )
302
- return ServiceCatalog(registrations, convention_candidates)
@@ -10,6 +10,7 @@ from ..diagnostics import SourceLocation
10
10
  from ..modularity import ModuleRegistry
11
11
  from ..modularity.discovery import _DiscoveredType
12
12
  from .contracts import SettingDefinition
13
+ from .declarations import SettingHandlers
13
14
  from .definition_discovery import _collect_setting_definitions
14
15
  from .errors import SettingDefinitionError
15
16
  from .handlers import SettingHandlerDefinition
@@ -87,8 +88,8 @@ def _build_setting_catalog(
87
88
  ) -> SettingCatalog:
88
89
  definitions, sources = _collect_setting_definitions(discoveries)
89
90
  handlers: list[SettingHandlerDefinition[Any]] = []
90
- for descriptor in registry.dependency_order:
91
- declared_handlers = descriptor.module_type.setting_handler_definitions
91
+ for descriptor, declaration in registry.declarations(SettingHandlers):
92
+ declared_handlers = declaration.definitions
92
93
  if not isinstance(declared_handlers, tuple):
93
94
  raise SettingDefinitionError(
94
95
  setting=str(descriptor.key), reason="setting_handler_definitions must be a tuple"
@@ -105,8 +106,8 @@ def _contribute_setting_handler_defaults(
105
106
 
106
107
  # 声明只拥有 handler 与 setting 的关系;实例和依赖仍交给原生 Dishka APP binding。
107
108
  registered: set[type[object]] = set()
108
- for descriptor in registry.dependency_order:
109
- for definition in descriptor.module_type.setting_handler_definitions:
109
+ for descriptor, declaration in registry.declarations(SettingHandlers):
110
+ for definition in declaration.definitions:
110
111
  handler_type = definition.handler_type
111
112
  if handler_type in registered:
112
113
  continue
@@ -0,0 +1,18 @@
1
+ """Settings handler 的关系声明;实例和生命周期仍由原生 DI 管理。"""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+ from ..modularity import AppModule, ModuleDeclaration
7
+ from .handlers import SettingHandlerDefinition
8
+
9
+
10
+ @dataclass(frozen=True, slots=True)
11
+ class SettingHandlers(ModuleDeclaration):
12
+ definitions: tuple[SettingHandlerDefinition[Any], ...]
13
+
14
+ @property
15
+ def required_module(self) -> type[AppModule]:
16
+ from .module import SettingsModule
17
+
18
+ return SettingsModule
@@ -6,11 +6,16 @@ from abc import ABC, abstractmethod
6
6
  from typing import Any
7
7
 
8
8
  from ..diagnostics import SourceLocation
9
+ from ..modularity import ModuleRef, ModuleRequirement
9
10
  from .contracts import SettingDefinition
10
11
  from .errors import SettingDefinitionError
11
12
 
12
13
 
13
14
  class SettingDefinitionProvider(ABC):
15
+ __settings_module__ = ModuleRequirement(
16
+ ModuleRef("python_ddd_framework.settings.module:SettingsModule")
17
+ )
18
+
14
19
  @abstractmethod
15
20
  def define(self, context: SettingDefinitionContext) -> None:
16
21
  """向所属 Application 贡献定义;必须同步完成并返回 None。"""
@@ -47,7 +47,7 @@ class SettingManager:
47
47
  await self.reset(definition, expected_version=current.version)
48
48
 
49
49
  async def update(
50
- self, definition: SettingDefinition[_T], value: object, *, expected_version: UUID | None
50
+ self, definition: SettingDefinition[_T], value: _T, *, expected_version: UUID | None
51
51
  ) -> SettingWriteOutcome[_T]:
52
52
  self._catalog.require_owned(definition)
53
53
  try:
@@ -1,9 +1,58 @@
1
1
  """Provider-neutral 的 Settings 管理服务与权限贡献。"""
2
2
 
3
- from ..modularity import AppModule
3
+ from dishka import DEFAULT_COMPONENT, DependencyKey, Provider, Scope
4
+
5
+ from ..lifecycle import ConfigureContext
6
+ from ..modularity import AppModule, ModuleRef
7
+ from ..services.composition import (
8
+ ServiceBindingContext,
9
+ ServiceComposition,
10
+ ServiceDiscoveryContext,
11
+ )
12
+ from ..services.registration import RegistrationDecision, RegistrationKind
13
+ from .binding import _provide_settings
14
+ from .catalog import SettingCatalog, _build_setting_catalog, _contribute_setting_handler_defaults
15
+ from .store import RuntimeSettingStore
16
+
17
+
18
+ class SettingsModule(AppModule):
19
+ catalog: SettingCatalog
20
+
21
+ def configure(self, context: ConfigureContext) -> None:
22
+ self.catalog = _build_setting_catalog(context.modules, context.discoveries)
23
+ if self.catalog.definitions:
24
+ context.configure.section("settings")
25
+ context.services.add_composition(
26
+ ServiceComposition(discover=self._discover, bind=self._bind)
27
+ )
28
+
29
+ def _discover(self, context: ServiceDiscoveryContext) -> None:
30
+ _contribute_setting_handler_defaults(context.modules, context.services)
31
+
32
+ def _bind(self, context: ServiceBindingContext) -> None:
33
+ provider = Provider()
34
+ provider.provide(lambda: self.catalog, provides=SettingCatalog, scope=Scope.APP)
35
+ # 存储能力只由最终原生绑定决定;alias 也是有效绑定,不能按 provider 名称推断。
36
+ _provide_settings(
37
+ provider,
38
+ self.catalog,
39
+ context.configuration,
40
+ has_store=any(
41
+ draft.key == DependencyKey(RuntimeSettingStore, DEFAULT_COMPONENT)
42
+ and draft.decision is RegistrationDecision.ACCEPTED
43
+ and draft.contribution.kind is not RegistrationKind.MULTI
44
+ and not draft.is_collection
45
+ for draft in context.arbitration.drafts
46
+ ),
47
+ )
48
+ context.add_provider(provider)
4
49
 
5
50
 
6
51
  class SettingsManagementModule(AppModule):
52
+ dependencies = (
53
+ SettingsModule,
54
+ ModuleRef("python_ddd_framework.application_services.module:ApplicationServicesModule"),
55
+ )
7
56
  scan_packages = (
8
57
  "python_ddd_framework.settings.application",
9
58
  "python_ddd_framework.settings.permissions",
@@ -7,6 +7,8 @@ from contextlib import AsyncExitStack
7
7
  from ..lifecycle import PreInitializeContext, ShutdownContext
8
8
  from ..modularity import AppModule
9
9
  from ..notifications import Notifications
10
+ from ..notifications.declarations import NotificationDefinitions
11
+ from ..notifications.module import NotificationsModule
10
12
  from .changes import SETTINGS_CHANGED, SettingChanged
11
13
  from .manager import SettingManager
12
14
  from .refresh import SettingsRefreshRuntime
@@ -14,8 +16,8 @@ from .refresh_module import SettingsRefreshModule
14
16
 
15
17
 
16
18
  class SettingsNotificationsModule(AppModule):
17
- dependencies = (SettingsRefreshModule,)
18
- notification_definitions = (SETTINGS_CHANGED,)
19
+ dependencies = (SettingsRefreshModule, NotificationsModule)
20
+ notification_definitions = NotificationDefinitions((SETTINGS_CHANGED,))
19
21
 
20
22
  def __init__(self) -> None:
21
23
  self._subscriptions = AsyncExitStack()
@@ -6,21 +6,28 @@ from dishka import DEFAULT_COMPONENT, DependencyKey
6
6
 
7
7
  from python_ddd_framework import ConfigureContext
8
8
 
9
+ from ..hosted_services.declarations import HostedServices
9
10
  from ..lifecycle import PreInitializeContext, ShutdownContext
10
- from ..modularity import AppModule
11
+ from ..modularity import AppModule, ModuleRef
11
12
  from .catalog import SettingCatalog
12
13
  from .manager import SettingManager
14
+ from .module import SettingsModule
13
15
  from .refresh import SettingsRefreshOptions, SettingsRefreshRuntime
14
16
 
15
17
 
16
18
  class SettingsRefreshModule(AppModule):
19
+ dependencies = (
20
+ SettingsModule,
21
+ ModuleRef("python_ddd_framework.hosted_services.module:HostedServicesModule"),
22
+ )
23
+
17
24
  def __init__(self) -> None:
18
25
  self._manager: SettingManager | None = None
19
26
 
20
27
  def configure(self, context: ConfigureContext) -> None:
21
28
  context.configure(SettingsRefreshOptions, section="settings_refresh")
22
29
 
23
- hosted_services = (SettingsRefreshRuntime,)
30
+ hosted_services = HostedServices((SettingsRefreshRuntime,))
24
31
 
25
32
  async def pre_initialize(self, context: PreInitializeContext) -> None:
26
33
  catalog = await context.container.get(SettingCatalog)
@@ -11,6 +11,7 @@ from ...sqlalchemy import (
11
11
  SqlAlchemyPersistenceModule,
12
12
  contributes_sqlalchemy_models,
13
13
  )
14
+ from ..module import SettingsModule
14
15
  from ..store import RuntimeSettingStore
15
16
  from .models import RuntimeSettingsBase
16
17
  from .store import SqlAlchemyRuntimeSettingStore
@@ -24,7 +25,7 @@ from .store import SqlAlchemyRuntimeSettingStore
24
25
  )
25
26
  )
26
27
  class SqlAlchemyRuntimeSettingsModule(AppModule):
27
- dependencies = (SqlAlchemyPersistenceModule,)
28
+ dependencies = (SqlAlchemyPersistenceModule, SettingsModule)
28
29
 
29
30
  def configure(self, context: ConfigureContext) -> None:
30
31
  provider = Provider()
@@ -12,6 +12,13 @@ from .errors import (
12
12
  SqlAlchemyModelRegistrationError,
13
13
  UnknownSqlAlchemyConnectionError,
14
14
  )
15
+ from .extensions import (
16
+ ExtensionColumn,
17
+ SqlAlchemyExtensionMapping,
18
+ SqlAlchemyExtensionMigrations,
19
+ SqlAlchemyExtensionModel,
20
+ SqlAlchemyModelExtension,
21
+ )
15
22
  from .metadata import (
16
23
  SqlAlchemyMigrationContribution,
17
24
  SqlAlchemyMigrationRegistration,
@@ -31,9 +38,13 @@ from .session_provider import SqlAlchemySessionProvider
31
38
  __all__ = (
32
39
  "AuditedEntityMixin",
33
40
  "ConnectionStringsOptions",
41
+ "ExtensionColumn",
34
42
  "FrameworkAsyncSession",
35
43
  "MigrationHeadsMismatchError",
36
44
  "SqlAlchemyConnectionError",
45
+ "SqlAlchemyExtensionMapping",
46
+ "SqlAlchemyExtensionMigrations",
47
+ "SqlAlchemyExtensionModel",
37
48
  "SqlAlchemyMetadataChangedError",
38
49
  "SqlAlchemyMigrationContribution",
39
50
  "SqlAlchemyMigrationError",
@@ -42,6 +53,7 @@ __all__ = (
42
53
  "SqlAlchemyMigrator",
43
54
  "SqlAlchemyModelCatalog",
44
55
  "SqlAlchemyModelContribution",
56
+ "SqlAlchemyModelExtension",
45
57
  "SqlAlchemyModelRegistration",
46
58
  "SqlAlchemyModelRegistrationError",
47
59
  "SqlAlchemyPersistenceModule",