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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (337) 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 +120 -288
  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 +197 -15
  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/local.py +20 -1
  25. python_ddd_framework/background_execution/module.py +107 -1
  26. python_ddd_framework/background_execution/processes.py +11 -1
  27. python_ddd_framework/background_jobs/catalog.py +59 -42
  28. python_ddd_framework/background_jobs/contracts.py +16 -19
  29. python_ddd_framework/background_jobs/pgqueuer/enqueue.py +4 -0
  30. python_ddd_framework/background_jobs/pgqueuer/module.py +2 -1
  31. python_ddd_framework/background_jobs/pgqueuer/runtime.py +7 -7
  32. python_ddd_framework/background_workers/catalog.py +23 -36
  33. python_ddd_framework/background_workers/contracts.py +3 -0
  34. python_ddd_framework/background_workers/execution.py +1 -1
  35. python_ddd_framework/background_workers/runtime.py +47 -1
  36. python_ddd_framework/caching/__init__.py +7 -6
  37. python_ddd_framework/caching/contracts.py +65 -67
  38. python_ddd_framework/caching/errors.py +0 -7
  39. python_ddd_framework/caching/module.py +18 -0
  40. python_ddd_framework/caching/naming.py +29 -0
  41. python_ddd_framework/caching/options.py +38 -0
  42. python_ddd_framework/caching/unit_of_work.py +46 -0
  43. python_ddd_framework/cli/__init__.py +31 -6
  44. python_ddd_framework/cli/project.py +68 -6
  45. python_ddd_framework/cli/runtime.py +1 -1
  46. python_ddd_framework/developer_kit/generation.py +75 -22
  47. python_ddd_framework/developer_kit/project_metadata.py +2 -4
  48. python_ddd_framework/developer_kit/templates/basic/cookiecutter.json +4 -0
  49. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/README.md +45 -0
  50. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/__init__.py.jinja +0 -0
  51. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/contracts/__init__.py.jinja +0 -0
  52. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/contracts/conversion_service.py.jinja +7 -0
  53. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/module.py.jinja +38 -0
  54. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/services/__init__.py.jinja +0 -0
  55. python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/services/default_conversion_service.py.jinja +10 -0
  56. python_ddd_framework/developer_kit/templates/module/cookiecutter.json +1 -1
  57. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/README.md +42 -49
  58. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/__init__.py.jinja +1 -0
  59. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/__init__.py.jinja +1 -0
  60. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/handler.py.jinja +45 -0
  61. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/payload.py.jinja +8 -0
  62. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/__init__.py.jinja +1 -0
  63. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/handler.py.jinja +35 -0
  64. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/payload.py.jinja +5 -0
  65. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/schedule.py.jinja +15 -0
  66. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/__init__.py.jinja +1 -0
  67. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/order_maintenance_worker.py.jinja +28 -0
  68. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/order_statistics_worker.py.jinja +26 -0
  69. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/__init__.py.jinja +1 -0
  70. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/order_cache.py.jinja +9 -0
  71. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/statistics_cache.py.jinja +9 -0
  72. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/event_handlers/__init__.py.jinja +1 -0
  73. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/event_handlers/order_changed_handler.py.jinja +23 -0
  74. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/__init__.py.jinja +1 -0
  75. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/order_integration_service.py.jinja +71 -0
  76. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/order_observation_handler.py.jinja +16 -0
  77. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration_services/__init__.py.jinja +1 -0
  78. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration_services/order_reporting_service.py.jinja +16 -0
  79. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/interceptors/__init__.py.jinja +1 -0
  80. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/interceptors/order_timing_interceptor.py.jinja +17 -0
  81. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/module.py.jinja +105 -10
  82. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/options/__init__.py.jinja +1 -0
  83. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/{options.py.jinja → options/order_options.py.jinja} +1 -1
  84. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/__init__.py.jinja +1 -0
  85. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_approval_service.py.jinja +65 -0
  86. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_management_service.py.jinja +35 -0
  87. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_query_service.py.jinja +41 -0
  88. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/__init__.py.jinja +1 -0
  89. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/approval_setting_observer.py.jinja +17 -0
  90. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/__init__.py.jinja +1 -0
  91. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/approve_order.py.jinja +6 -0
  92. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/create_order.py.jinja +7 -0
  93. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/integration_services/__init__.py.jinja +1 -0
  94. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/integration_services/order_reporting_service.py.jinja +7 -0
  95. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/module.py.jinja +39 -2
  96. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/__init__.py.jinja +1 -0
  97. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_approval_service.py.jinja +13 -0
  98. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_management_service.py.jinja +10 -0
  99. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_query_service.py.jinja +12 -0
  100. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/__init__.py.jinja +1 -0
  101. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/order_statistics_snapshot.py.jinja +9 -0
  102. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/order_view.py.jinja +12 -0
  103. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/entities/__init__.py.jinja +1 -0
  104. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/{orders.py.jinja → entities/order.py.jinja} +10 -18
  105. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/events/__init__.py.jinja +1 -0
  106. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/events/order_changed.py.jinja +13 -0
  107. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/module.py.jinja +42 -3
  108. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repositories/__init__.py.jinja +1 -0
  109. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repositories/order_repository.py.jinja +17 -0
  110. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding/__init__.py.jinja +1 -0
  111. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding/order_seed_contributor.py.jinja +22 -0
  112. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/services/__init__.py.jinja +1 -0
  113. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/services/order_approval_service.py.jinja +23 -0
  114. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/settings/__init__.py.jinja +1 -0
  115. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/{settings.py.jinja → settings/approval_settings.py.jinja} +3 -2
  116. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/value_objects/__init__.py.jinja +1 -0
  117. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/value_objects/order_title.py.jinja +10 -0
  118. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/constants/__init__.py.jinja +1 -0
  119. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/constants/order_constants.py.jinja +3 -0
  120. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/enums/__init__.py.jinja +1 -0
  121. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/enums/order_status.py.jinja +6 -0
  122. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/errors/__init__.py.jinja +1 -0
  123. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/errors/order_errors.py.jinja +11 -0
  124. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/__init__.py.jinja +1 -0
  125. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/order_messages.py.jinja +26 -0
  126. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/order_observation.py.jinja +13 -0
  127. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/module.py.jinja +40 -1
  128. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions/__init__.py.jinja +1 -0
  129. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/{permissions.py.jinja → permissions/order_permission_provider.py.jinja} +6 -2
  130. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions/order_permissions.py.jinja +7 -0
  131. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/value_objects/__init__.py.jinja +1 -0
  132. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/value_objects/money.py.jinja +18 -0
  133. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/filters/__init__.py.jinja +1 -0
  134. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/filters/export_filter.py.jinja +15 -0
  135. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/models/__init__.py.jinja +1 -0
  136. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/models/refresh_orders.py.jinja +5 -0
  137. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/module.py.jinja +85 -7
  138. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/routers/__init__.py.jinja +1 -0
  139. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/{files.py.jinja → routers/order_files.py.jinja} +23 -20
  140. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/websockets/__init__.py.jinja +1 -0
  141. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/websockets/order_socket.py.jinja +44 -0
  142. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/migrations/__init__.py.jinja +1 -1
  143. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/__init__.py.jinja +1 -7
  144. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/base.py.jinja +7 -0
  145. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/{orders.py.jinja → order_model.py.jinja} +7 -3
  146. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/module.py.jinja +42 -5
  147. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/order_repository.py.jinja +79 -0
  148. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/__init__.py.jinja +1 -0
  149. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/test_domain.py.jinja +4 -3
  150. python_ddd_framework/developer_kit/templates/project/cookiecutter.json +1 -1
  151. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/AGENTS.md +23 -3
  152. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/README.md +29 -22
  153. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{app.development.yaml.jinja → backend/app.development.yaml.jinja} +2 -0
  154. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{pyproject.toml.jinja → backend/pyproject.toml.jinja} +1 -1
  155. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/main.py.jinja +2 -0
  156. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/module.py.jinja +40 -3
  157. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/architecture.md +42 -11
  158. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/development.md +144 -23
  159. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/scripts/.gitkeep +0 -0
  160. python_ddd_framework/distributed_lock/contracts.py +4 -1
  161. python_ddd_framework/domain/__init__.py +2 -0
  162. python_ddd_framework/domain/services.py +11 -0
  163. python_ddd_framework/errors/business.py +10 -1
  164. python_ddd_framework/errors/lifecycle.py +3 -31
  165. python_ddd_framework/events/__init__.py +5 -2
  166. python_ddd_framework/events/aggregate.py +3 -3
  167. python_ddd_framework/events/catalog.py +12 -11
  168. python_ddd_framework/events/contracts.py +6 -1
  169. python_ddd_framework/events/contribution.py +1 -52
  170. python_ddd_framework/events/entities.py +23 -0
  171. python_ddd_framework/events/module.py +97 -0
  172. python_ddd_framework/events/runtime.py +80 -29
  173. python_ddd_framework/events/subscription.py +36 -0
  174. python_ddd_framework/events/unit_of_work.py +25 -0
  175. python_ddd_framework/extensions/__init__.py +22 -0
  176. python_ddd_framework/extensions/catalog.py +198 -0
  177. python_ddd_framework/extensions/contracts.py +85 -0
  178. python_ddd_framework/extensions/models.py +142 -0
  179. python_ddd_framework/extensions/module.py +21 -0
  180. python_ddd_framework/fastapi/__init__.py +2 -0
  181. python_ddd_framework/fastapi/action.py +1 -2
  182. python_ddd_framework/fastapi/adapter.py +16 -0
  183. python_ddd_framework/fastapi/application_services.py +43 -25
  184. python_ddd_framework/fastapi/background.py +2 -1
  185. python_ddd_framework/fastapi/contracts.py +26 -8
  186. python_ddd_framework/fastapi/health.py +1 -1
  187. python_ddd_framework/fastapi/module.py +14 -0
  188. python_ddd_framework/fastapi/parameters.py +3 -18
  189. python_ddd_framework/fastapi/realtime/connection.py +10 -7
  190. python_ddd_framework/fastapi/realtime/module.py +3 -0
  191. python_ddd_framework/fastapi/realtime/runtime.py +10 -24
  192. python_ddd_framework/fastapi/request_context.py +2 -1
  193. python_ddd_framework/fastapi/routing.py +2 -1
  194. python_ddd_framework/fastapi/server.py +8 -0
  195. python_ddd_framework/fastapi/settings.py +2 -1
  196. python_ddd_framework/hosted_services/bridge.py +95 -17
  197. python_ddd_framework/hosted_services/catalog.py +6 -16
  198. python_ddd_framework/hosted_services/contracts.py +9 -1
  199. python_ddd_framework/hosted_services/declarations.py +15 -0
  200. python_ddd_framework/hosted_services/module.py +69 -0
  201. python_ddd_framework/hosted_services/runtime.py +101 -18
  202. python_ddd_framework/identity/__init__.py +24 -0
  203. python_ddd_framework/identity/application.py +38 -90
  204. python_ddd_framework/identity/contracts.py +181 -16
  205. python_ddd_framework/identity/http_api.py +79 -2
  206. python_ddd_framework/identity/management.py +166 -0
  207. python_ddd_framework/identity/module.py +12 -4
  208. python_ddd_framework/identity/services.py +29 -16
  209. python_ddd_framework/identity/sqlalchemy/migrations/0003_extra_properties.py +26 -0
  210. python_ddd_framework/identity/sqlalchemy/migrations/0004_concurrency_version.py +29 -0
  211. python_ddd_framework/identity/sqlalchemy/models.py +21 -2
  212. python_ddd_framework/identity/sqlalchemy/module.py +11 -2
  213. python_ddd_framework/identity/sqlalchemy/repository.py +458 -0
  214. python_ddd_framework/identity/sqlalchemy/session_security.py +88 -0
  215. python_ddd_framework/identity/sqlalchemy/stores.py +69 -290
  216. python_ddd_framework/identity/tokens.py +2 -5
  217. python_ddd_framework/{application_services/validation.py → invocation/arguments.py} +8 -6
  218. python_ddd_framework/invocation/contribution.py +61 -0
  219. python_ddd_framework/invocation/dispatcher.py +291 -0
  220. python_ddd_framework/invocation/entries.py +2 -25
  221. python_ddd_framework/invocation/entrypoints.py +126 -0
  222. python_ddd_framework/{application_services → invocation}/execution.py +8 -5
  223. python_ddd_framework/invocation/function_runtime.py +3 -1
  224. python_ddd_framework/invocation/interception.py +113 -42
  225. python_ddd_framework/{application_services → invocation}/interceptors.py +2 -2
  226. python_ddd_framework/invocation/managed_proxy.py +116 -0
  227. python_ddd_framework/invocation/managed_services.py +250 -0
  228. python_ddd_framework/invocation/methods.py +345 -0
  229. python_ddd_framework/invocation/module.py +113 -0
  230. python_ddd_framework/{application_services → invocation}/policies.py +8 -1
  231. python_ddd_framework/invocation/validation.py +25 -0
  232. python_ddd_framework/lifecycle/__init__.py +2 -0
  233. python_ddd_framework/lifecycle/composition.py +5 -2
  234. python_ddd_framework/lifecycle/participants.py +37 -0
  235. python_ddd_framework/messaging/__init__.py +25 -0
  236. python_ddd_framework/messaging/channel.py +232 -0
  237. python_ddd_framework/messaging/contracts.py +78 -0
  238. python_ddd_framework/messaging/module.py +114 -0
  239. python_ddd_framework/messaging/options.py +13 -0
  240. python_ddd_framework/messaging/receipt.py +27 -0
  241. python_ddd_framework/messaging/runtime.py +139 -0
  242. python_ddd_framework/modularity/__init__.py +11 -1
  243. python_ddd_framework/modularity/contracts.py +47 -16
  244. python_ddd_framework/modularity/discovery.py +21 -2
  245. python_ddd_framework/modularity/graph.py +45 -1
  246. python_ddd_framework/modularity/registry.py +83 -3
  247. python_ddd_framework/notifications/__init__.py +2 -0
  248. python_ddd_framework/notifications/catalog.py +3 -2
  249. python_ddd_framework/notifications/declarations.py +18 -0
  250. python_ddd_framework/notifications/module.py +21 -0
  251. python_ddd_framework/observability/logging.py +3 -0
  252. python_ddd_framework/observability/tracing.py +3 -0
  253. python_ddd_framework/options/contribution.py +12 -0
  254. python_ddd_framework/options/registry.py +44 -11
  255. python_ddd_framework/realtime/__init__.py +2 -1
  256. python_ddd_framework/realtime/contracts.py +3 -7
  257. python_ddd_framework/realtime/messages.py +26 -1
  258. python_ddd_framework/redis/cache.py +243 -0
  259. python_ddd_framework/redis/cache_scripts.py +40 -0
  260. python_ddd_framework/redis/distributed_lock.py +10 -2
  261. python_ddd_framework/redis/module.py +13 -4
  262. python_ddd_framework/redis/notifications.py +5 -2
  263. python_ddd_framework/redis/runtime.py +1 -79
  264. python_ddd_framework/seeding/__init__.py +13 -0
  265. python_ddd_framework/seeding/contracts.py +45 -0
  266. python_ddd_framework/seeding/module.py +73 -0
  267. python_ddd_framework/seeding/runtime.py +63 -0
  268. python_ddd_framework/services/arbitration.py +23 -0
  269. python_ddd_framework/services/binding.py +2 -3
  270. python_ddd_framework/services/composition.py +108 -0
  271. python_ddd_framework/services/contribution.py +44 -54
  272. python_ddd_framework/services/convention.py +53 -181
  273. python_ddd_framework/services/fixed_lifetime.py +12 -15
  274. python_ddd_framework/services/framework_provider.py +3 -158
  275. python_ddd_framework/services/native_graph.py +35 -60
  276. python_ddd_framework/services/provider.py +59 -4
  277. python_ddd_framework/services/repository.py +6 -0
  278. python_ddd_framework/services/runtime.py +129 -215
  279. python_ddd_framework/settings/catalog.py +5 -4
  280. python_ddd_framework/settings/declarations.py +18 -0
  281. python_ddd_framework/settings/definitions.py +5 -0
  282. python_ddd_framework/settings/manager.py +1 -1
  283. python_ddd_framework/settings/module.py +50 -1
  284. python_ddd_framework/settings/notifications.py +4 -2
  285. python_ddd_framework/settings/refresh_module.py +9 -2
  286. python_ddd_framework/settings/sqlalchemy/module.py +2 -1
  287. python_ddd_framework/sqlalchemy/__init__.py +12 -0
  288. python_ddd_framework/sqlalchemy/alembic_runtime/env.py +1 -0
  289. python_ddd_framework/sqlalchemy/auditing.py +7 -1
  290. python_ddd_framework/sqlalchemy/extensions.py +186 -0
  291. python_ddd_framework/sqlalchemy/metadata.py +110 -6
  292. python_ddd_framework/sqlalchemy/migration.py +61 -8
  293. python_ddd_framework/sqlalchemy/migration_operations.py +86 -0
  294. python_ddd_framework/sqlalchemy/module.py +14 -11
  295. python_ddd_framework/sqlalchemy/module_migration.py +71 -3
  296. python_ddd_framework/sqlalchemy/repository.py +217 -13
  297. python_ddd_framework/sqlalchemy/session_provider.py +14 -1
  298. python_ddd_framework/testing/runtime.py +5 -4
  299. python_ddd_framework/unit_of_work/__init__.py +2 -1
  300. python_ddd_framework/unit_of_work/contracts.py +6 -7
  301. python_ddd_framework/unit_of_work/errors.py +11 -0
  302. python_ddd_framework/unit_of_work/lifecycle.py +19 -0
  303. python_ddd_framework/unit_of_work/manager.py +86 -32
  304. python_ddd_framework/unit_of_work/module.py +61 -0
  305. python_ddd_framework/unit_of_work/options.py +7 -1
  306. python_ddd_framework/validation/__init__.py +1 -0
  307. python_ddd_framework/validation/models.py +37 -0
  308. {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.5.1.dist-info}/METADATA +147 -44
  309. python_ddd_framework-0.5.1.dist-info/RECORD +488 -0
  310. {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.5.1.dist-info}/WHEEL +1 -1
  311. python_ddd_framework/application_services/seeding.py +0 -72
  312. python_ddd_framework/caching/catalog.py +0 -73
  313. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/cache.py.jinja +0 -10
  314. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/events.py.jinja +0 -24
  315. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration.py.jinja +0 -41
  316. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/orders.py.jinja +0 -83
  317. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/tasks.py.jinja +0 -84
  318. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/orders.py.jinja +0 -35
  319. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repository.py.jinja +0 -14
  320. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding.py.jinja +0 -19
  321. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/definitions.py.jinja +0 -24
  322. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/realtime.py.jinja +0 -36
  323. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/orders.py.jinja +0 -49
  324. python_ddd_framework-0.3.2.dist-info/RECORD +0 -355
  325. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.dockerignore → backend/.dockerignore} +0 -0
  326. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.gitignore → backend/.gitignore} +0 -0
  327. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.python-version → backend/.python-version} +0 -0
  328. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{Dockerfile → backend/Dockerfile} +0 -0
  329. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{compose.dev.yaml.jinja → backend/compose.dev.yaml.jinja} +0 -0
  330. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{compose.production.yaml.jinja → backend/compose.production.yaml.jinja} +0 -0
  331. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/__init__.py.jinja +0 -0
  332. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{tests → backend/tests}/conftest.py.jinja +0 -0
  333. /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{tests → backend/tests}/host/test_http.py.jinja +0 -0
  334. /python_ddd_framework/{application_services → invocation}/signature.py +0 -0
  335. {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.5.1.dist-info}/entry_points.txt +0 -0
  336. {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.5.1.dist-info}/licenses/LICENSE +0 -0
  337. {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.5.1.dist-info}/licenses/src/python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt +0 -0
@@ -1,178 +1,23 @@
1
- """组装平台自有依赖与 scoped proxy;业务贡献仍由原生 Dishka 管理。"""
1
+ """通用组装事实进入原生 Dishka;具体能力的注册由模块拥有。"""
2
2
 
3
- from __future__ import annotations
3
+ from dishka import Provider, Scope
4
4
 
5
- from dishka import (
6
- Provider,
7
- Scope,
8
- )
9
-
10
- from ..application_services import ApplicationServiceCatalog
11
- from ..application_services.dispatcher import _ApplicationServiceDispatcher, _proxy_factory
12
- from ..application_services.invocation import (
13
- _InvocationRuntime,
14
- _ScopedApplicationServiceInvoker,
15
- )
16
- from ..auditing.control import AuditingControl
17
- from ..authorization import CurrentUser, PermissionCatalog
18
- from ..background_jobs import BackgroundJobCatalog
19
- from ..background_workers import BackgroundWorkerCatalog
20
- from ..background_workers.runtime import _BackgroundCoordinator
21
- from ..caching import CacheCatalog
22
5
  from ..configuration import ConfigurationRoot
23
- from ..events import AggregateEventCollector, LocalEventBus, LocalEventCatalog
24
- from ..hosted_services import HostedServiceCatalog
25
- from ..hosted_services.runtime import _HostedServiceCoordinator
26
- from ..invocation import ApplicationInvocationContext, BackgroundSystemCaller
27
- from ..invocation.interception import _EntryInterceptionCatalog
28
6
  from ..modularity import ModuleRegistry
29
- from ..notifications import NotificationCatalog
30
7
  from ..options import Options, OptionsSnapshot
31
- from ..settings import SettingCatalog
32
- from ..unit_of_work import UnitOfWork, UnitOfWorkManager, UnitOfWorkOptions
33
- from ..unit_of_work.contracts import _UnitOfWorkFactory
34
8
 
35
9
 
36
10
  def _framework_provider(
37
11
  *,
38
12
  registry: ModuleRegistry,
39
- has_unit_of_work_factory: bool,
40
- entry_interceptors: _EntryInterceptionCatalog,
41
13
  configuration: ConfigurationRoot,
42
14
  options: OptionsSnapshot,
43
- invocation_runtime: _InvocationRuntime,
44
- application_service_catalog: ApplicationServiceCatalog,
45
- event_catalog: LocalEventCatalog,
46
- permission_catalog: PermissionCatalog,
47
- cache_catalog: CacheCatalog,
48
- setting_catalog: SettingCatalog,
49
- notification_catalog: NotificationCatalog,
50
- background_worker_catalog: BackgroundWorkerCatalog,
51
- background_job_catalog: BackgroundJobCatalog,
52
- background_coordinator: _BackgroundCoordinator,
53
- hosted_service_catalog: HostedServiceCatalog,
54
- hosted_service_coordinator: _HostedServiceCoordinator,
55
15
  ) -> Provider:
56
- """框架自身 contract 直接进入 Dishka;不写入业务 ServiceCatalog 第二清单。"""
57
16
  provider = Provider()
58
- provider.provide(
59
- lambda: entry_interceptors, provides=_EntryInterceptionCatalog, scope=Scope.APP
60
- )
61
- provider.provide(AuditingControl, scope=Scope.APP)
62
- _provide_unit_of_work(provider, has_unit_of_work_factory)
63
17
  provider.provide(lambda: registry, provides=ModuleRegistry, scope=Scope.APP)
64
18
  provider.provide(lambda: configuration, provides=ConfigurationRoot, scope=Scope.APP)
65
19
  provider.provide(lambda: options, provides=OptionsSnapshot, scope=Scope.APP)
66
- # Dishka 原生 context binding 直接发布已冻结实例,保持每个 Application 的唯一 snapshot。
20
+ # 已冻结 Options 是唯一配置解释结果;容器仅发布实例,不再绑定或建立配置旁路。
67
21
  for model_type in options.values:
68
22
  provider.from_context(provides=Options[model_type], scope=Scope.APP) # type: ignore[valid-type]
69
- provider.provide(
70
- lambda: invocation_runtime,
71
- provides=_InvocationRuntime,
72
- scope=Scope.APP,
73
- )
74
- provider.provide(
75
- lambda: application_service_catalog,
76
- provides=ApplicationServiceCatalog,
77
- scope=Scope.APP,
78
- )
79
- provider.provide(lambda: event_catalog, provides=LocalEventCatalog, scope=Scope.APP)
80
- provider.provide(lambda: permission_catalog, provides=PermissionCatalog, scope=Scope.APP)
81
- provider.provide(lambda: cache_catalog, provides=CacheCatalog, scope=Scope.APP)
82
- provider.provide(lambda: setting_catalog, provides=SettingCatalog, scope=Scope.APP)
83
- provider.provide(lambda: notification_catalog, provides=NotificationCatalog, scope=Scope.APP)
84
- provider.provide(
85
- lambda: background_worker_catalog,
86
- provides=BackgroundWorkerCatalog,
87
- scope=Scope.APP,
88
- )
89
- provider.provide(
90
- lambda: background_job_catalog,
91
- provides=BackgroundJobCatalog,
92
- scope=Scope.APP,
93
- )
94
- provider.provide(
95
- lambda: background_coordinator,
96
- provides=_BackgroundCoordinator,
97
- scope=Scope.APP,
98
- )
99
- provider.provide(
100
- lambda: hosted_service_catalog,
101
- provides=HostedServiceCatalog,
102
- scope=Scope.APP,
103
- )
104
- provider.provide(
105
- lambda: hosted_service_coordinator,
106
- provides=_HostedServiceCoordinator,
107
- scope=Scope.APP,
108
- )
109
- provider.provide(
110
- lambda: background_coordinator.system_caller,
111
- provides=BackgroundSystemCaller,
112
- scope=Scope.APP,
113
- )
114
- provider.provide(
115
- _current_user,
116
- provides=CurrentUser,
117
- scope=Scope.REQUEST,
118
- )
119
- provider.from_context(provides=ApplicationInvocationContext, scope=Scope.ACTION)
120
- provider.provide(
121
- _ApplicationServiceDispatcher,
122
- provides=_ApplicationServiceDispatcher,
123
- scope=Scope.REQUEST,
124
- )
125
- provider.provide(
126
- _ScopedApplicationServiceInvoker,
127
- provides=_ScopedApplicationServiceInvoker,
128
- scope=Scope.APP,
129
- )
130
- for registration in application_service_catalog.registrations:
131
- if not registration.is_public_service:
132
- continue
133
- provider.provide(
134
- _proxy_factory(application_service_catalog._for_registration(registration)),
135
- provides=registration.contract,
136
- scope=Scope.REQUEST,
137
- )
138
- provider.provide(LocalEventBus, provides=LocalEventBus, scope=Scope.REQUEST)
139
- provider.provide(
140
- AggregateEventCollector,
141
- provides=AggregateEventCollector,
142
- scope=Scope.ACTION,
143
- )
144
- for worker_type in {item.worker_type for item in background_worker_catalog.definitions}:
145
- if isinstance(worker_type, type):
146
- provider.provide(worker_type, provides=worker_type, scope=Scope.ACTION)
147
- for handler_type in {item.handler_type for item in background_job_catalog.definitions}:
148
- if isinstance(handler_type, type):
149
- provider.provide(handler_type, provides=handler_type, scope=Scope.ACTION)
150
23
  return provider
151
-
152
-
153
- def _current_user(invocation_runtime: _InvocationRuntime) -> CurrentUser:
154
- # CurrentUser 只来自当前 Application 的 task-local request context。
155
- return invocation_runtime.current_user()
156
-
157
-
158
- def _unit_of_work_manager(
159
- runtime: _InvocationRuntime,
160
- factory: _UnitOfWorkFactory,
161
- options: Options[UnitOfWorkOptions],
162
- catalog: LocalEventCatalog,
163
- ) -> UnitOfWorkManager:
164
- return UnitOfWorkManager(runtime, factory, options.value, catalog)
165
-
166
-
167
- def _current_unit_of_work(manager: UnitOfWorkManager) -> UnitOfWork:
168
- return manager.require_current()
169
-
170
-
171
- def _provide_unit_of_work(
172
- provider: Provider,
173
- has_unit_of_work_factory: bool,
174
- ) -> None:
175
- if not has_unit_of_work_factory:
176
- provider.provide(_UnitOfWorkFactory, scope=Scope.APP)
177
- provider.provide(_unit_of_work_manager, provides=UnitOfWorkManager, scope=Scope.APP)
178
- provider.provide(_current_unit_of_work, provides=UnitOfWork, scope=Scope.ACTION, cache=False)
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from collections.abc import Mapping
5
+ from collections.abc import Iterator, Mapping
6
6
  from dataclasses import dataclass
7
7
  from typing import get_origin
8
8
 
@@ -11,16 +11,12 @@ from dishka import (
11
11
  AsyncContainer,
12
12
  BaseScope,
13
13
  DependencyKey,
14
- Scope,
14
+ Provider,
15
15
  )
16
16
  from dishka.dependency_source import Factory
17
17
  from dishka.entities.factory_type import FactoryType
18
18
 
19
- from ..application_services import ApplicationService, ApplicationServiceCatalog
20
- from ..application_services.errors import ApplicationServiceDefinitionError
21
- from ..application_services.interceptors import InterceptionStage
22
19
  from ..errors import CatalogNativeGraphMismatchError, InvalidConventionDefinitionError
23
- from ..invocation.interception import _EntryInterceptionCatalog
24
20
  from .arbitration import (
25
21
  _RegistrationDraft,
26
22
  )
@@ -41,9 +37,14 @@ class _NativeGraphRegistration:
41
37
  class _NativeServiceGraph:
42
38
  """只读投影 Dishka 已构建图,用于证明 catalog 与真实容器一致。"""
43
39
 
44
- __slots__ = ("_rows",)
40
+ __slots__ = ("_method_decorators", "_rows")
45
41
 
46
- def __init__(self, container: AsyncContainer) -> None:
42
+ def __init__(self, container: AsyncContainer, decorators: tuple[Provider, ...] = ()) -> None:
43
+ self._method_decorators = {
44
+ id(decorator.factory.source)
45
+ for provider in decorators
46
+ for decorator in provider.decorators
47
+ }
47
48
  rows: list[_NativeGraphRegistration] = []
48
49
  native_registry = container.registry
49
50
  while True:
@@ -61,6 +62,26 @@ class _NativeServiceGraph:
61
62
  native_registry = child_registry
62
63
  self._rows = tuple(rows)
63
64
 
65
+ def __iter__(self) -> Iterator[_NativeGraphRegistration]:
66
+ return iter(self._rows)
67
+
68
+ def effective_factory(self, row: _NativeGraphRegistration) -> Factory:
69
+ factory = self._undecorated_factory(row.factory)
70
+ # Alias 自身不缓存;只沿 Dishka 已验证的链读取实际实例 owner。
71
+ while factory.type is FactoryType.ALIAS:
72
+ factory = self._undecorated_factory(
73
+ next(item.factory for item in self._rows if item.key == factory.dependencies[0])
74
+ )
75
+ return factory
76
+
77
+ def _undecorated_factory(self, factory: Factory) -> Factory:
78
+ # 注册核对保留 alias 身份;实例 owner 核对才继续追踪 alias。
79
+ while id(factory.source) in self._method_decorators:
80
+ factory = next(
81
+ item.factory for item in self._rows if item.key == factory.dependencies[0]
82
+ )
83
+ return factory
84
+
64
85
  def differences(self, drafts: tuple[_RegistrationDraft, ...]) -> tuple[str, ...]:
65
86
  return tuple(
66
87
  f"missing accepted {draft.contribution.kind.value} registration for {draft.key}"
@@ -77,18 +98,10 @@ class _NativeServiceGraph:
77
98
  if scope is None or not isinstance(candidate, type):
78
99
  continue
79
100
  _validate_fixed_lifetime(candidate)
80
- if (
81
- issubclass(candidate, ApplicationService)
82
- and row.key.component == DEFAULT_COMPONENT
83
- ):
84
- # 公开 REQUEST proxy 由框架提供;其实际实现已移入私有 ACTION component。
85
- continue
86
- factory = row.factory
87
- # Alias 自身不缓存;沿 Dishka 已验证的链核对真正创建实例的 factory。
88
- while factory.type is FactoryType.ALIAS:
89
- factory = next(
90
- item.factory for item in self._rows if item.key == factory.dependencies[0]
91
- )
101
+ if candidate is row.key.type_hint and row.key.component == DEFAULT_COMPONENT:
102
+ # 具有代理的角色分别声明公开入口与实际实现的生命周期。
103
+ scope = getattr(candidate, "_public_service_scope", scope)
104
+ factory = self.effective_factory(row)
92
105
  if row.scope is not scope or not factory.cache:
93
106
  raise InvalidConventionDefinitionError(
94
107
  symbol=str(row.key.type_hint),
@@ -115,17 +128,6 @@ class _NativeServiceGraph:
115
128
  None,
116
129
  )
117
130
 
118
- def implementation_for(self, target: type[object]) -> type[object]:
119
- key = DependencyKey(target, DEFAULT_COMPONENT)
120
- while True:
121
- factory = next(row.factory for row in self._rows if row.key == key)
122
- if factory.type is FactoryType.ALIAS:
123
- # 原生图已验证 alias 链及循环;沿真实依赖取得最终 class factory。
124
- key = factory.dependencies[0]
125
- continue
126
- # 动态工厂只承诺注册契约,不能在 build 执行它来猜测运行实例。
127
- return factory.source if isinstance(factory.source, type) else target
128
-
129
131
  def _matches(self, draft: _RegistrationDraft) -> bool:
130
132
  if draft.is_collection:
131
133
  return any(
@@ -144,45 +146,18 @@ class _NativeServiceGraph:
144
146
  return any(
145
147
  row.key.type_hint == draft.key.type_hint
146
148
  and row.key.component == draft.key.component
147
- and row.factory.source is draft.factory.source
149
+ and self._undecorated_factory(row.factory).source is draft.factory.source
148
150
  and row.scope is draft.scope
149
151
  for row in self._rows
150
152
  )
151
153
  return any(
152
154
  row.key == draft.key
153
- and row.factory.source is draft.factory.source
155
+ and self._undecorated_factory(row.factory).source is draft.factory.source
154
156
  and row.scope is draft.scope
155
157
  for row in self._rows
156
158
  )
157
159
 
158
160
 
159
- def _validate_interceptors(
160
- graph: _NativeServiceGraph,
161
- catalog: ApplicationServiceCatalog,
162
- entries: _EntryInterceptionCatalog,
163
- ) -> None:
164
- # 匹配结果和实际入口 scope 来自同一编译 catalog,不另建后台类型或规则清单。
165
- requirements = (
166
- (
167
- registration,
168
- Scope.ACTION if registration.before is InterceptionStage.METHOD else Scope.REQUEST,
169
- )
170
- for service in catalog.registrations
171
- for method in service.methods
172
- for registration in method.interceptors
173
- )
174
- for registration, available in (*requirements, *entries.interceptor_scopes):
175
- scope = graph.interceptor_scope(registration.interceptor)
176
- if scope not in (Scope.APP, Scope.REQUEST, Scope.ACTION) or scope > available:
177
- raise ApplicationServiceDefinitionError(
178
- symbol=registration.interceptor.__qualname__,
179
- reason=(
180
- f"interceptor scope is unavailable before {registration.before.value}; "
181
- f"owner={registration.owner}"
182
- ),
183
- )
184
-
185
-
186
161
  def _accepted_single_service_scopes(
187
162
  drafts: tuple[_RegistrationDraft, ...],
188
163
  native_graph: _NativeServiceGraph,
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from dataclasses import dataclass
6
6
  from types import MethodType
7
+ from typing import TypeVar, get_origin
7
8
 
8
9
  from dishka import BaseScope, DependencyKey, Provider
9
10
  from dishka.dependency_source import Alias, Factory, FactoryUnionMode
@@ -12,6 +13,7 @@ from dishka.entities.marker import BoolMarker
12
13
 
13
14
  from ..diagnostics import SourceLocation
14
15
  from ..errors import RegistrationConflictError
16
+ from ..modularity.contracts import ModuleDeclaration, _module_declarations
15
17
  from .registration import SUPPORTED_SCOPE_LABEL, SUPPORTED_SCOPES, RegistrationKind
16
18
 
17
19
 
@@ -27,6 +29,7 @@ class _FactorySignature:
27
29
  allow_static_evaluation: bool
28
30
  is_to_bind: bool
29
31
  native_override: bool
32
+ declarations: tuple[tuple[object, tuple[tuple[str, ModuleDeclaration], ...]], ...]
30
33
 
31
34
 
32
35
  @dataclass(frozen=True, slots=True)
@@ -42,6 +45,7 @@ class _CollectionSignature:
42
45
  class _ProviderSignature:
43
46
  factories: tuple[_FactorySignature, ...]
44
47
  collections: tuple[_CollectionSignature, ...]
48
+ decorators: tuple[tuple[object, ...], ...]
45
49
 
46
50
 
47
51
  @dataclass(frozen=True, slots=True)
@@ -71,14 +75,17 @@ class _ValidatedProvider:
71
75
  class _NativeProviderInspector:
72
76
  """隔离 Dishka private dependency-source shape,返回框架拥有的稳定事实。"""
73
77
 
74
- __slots__ = ("_kind", "_source")
78
+ __slots__ = ("_kind", "_method_decorators", "_source")
75
79
 
76
- def __init__(self, *, source: SourceLocation, kind: RegistrationKind) -> None:
80
+ def __init__(
81
+ self, *, source: SourceLocation, kind: RegistrationKind, method_decorators: bool = False
82
+ ) -> None:
77
83
  self._source = source
78
84
  self._kind = kind
85
+ self._method_decorators = method_decorators
79
86
 
80
87
  def inspect(self, provider: Provider) -> _ValidatedProvider:
81
- if provider.decorators or provider.activators:
88
+ if (provider.decorators and not self._method_decorators) or provider.activators:
82
89
  raise RegistrationConflictError(
83
90
  key=None,
84
91
  sources=(self._source,),
@@ -93,6 +100,7 @@ class _NativeProviderInspector:
93
100
  or provider.factory_union_mode
94
101
  or provider.aliases
95
102
  or provider.context_vars
103
+ or provider.decorators
96
104
  ):
97
105
  raise RegistrationConflictError(
98
106
  key=None,
@@ -127,6 +135,7 @@ class _NativeProviderInspector:
127
135
  allow_static_evaluation=item.factory.allow_static_evaluation,
128
136
  is_to_bind=item.factory.is_to_bind,
129
137
  native_override=item.native_override,
138
+ declarations=self._declarations(item.key.type_hint, item.factory.source),
130
139
  )
131
140
  for item in factories
132
141
  ),
@@ -140,9 +149,55 @@ class _NativeProviderInspector:
140
149
  )
141
150
  for item in collections
142
151
  ),
152
+ decorators=tuple(self._decorator_signature(item) for item in provider.decorators),
143
153
  )
144
154
  return _ValidatedProvider(factories, collections, signature)
145
155
 
156
+ def _decorator_signature(self, decorator: object) -> tuple[object, ...]:
157
+ from dishka.dependency_source.decorator import Decorator
158
+
159
+ assert isinstance(decorator, Decorator)
160
+ factory = decorator.factory
161
+ if (
162
+ decorator.scope is not None
163
+ or decorator.when is not None
164
+ or decorator.allow_static_evaluation
165
+ or (
166
+ decorator.generic
167
+ and (
168
+ isinstance(decorator.provides.type_hint, TypeVar)
169
+ or bool(getattr(decorator.provides.type_hint, "__parameters__", ()))
170
+ )
171
+ )
172
+ or factory.type is not FactoryType.FACTORY
173
+ ):
174
+ self._conflict(
175
+ decorator.provides, "managed method decorators must retain native scope/cache"
176
+ )
177
+ return (
178
+ decorator.provides,
179
+ self.source_identity(factory.source),
180
+ tuple(factory.dependencies),
181
+ tuple(sorted(factory.kw_dependencies.items())),
182
+ factory.provides,
183
+ factory.is_to_bind,
184
+ )
185
+
186
+ @staticmethod
187
+ def _declarations(
188
+ *candidates: object,
189
+ ) -> tuple[tuple[object, tuple[tuple[str, ModuleDeclaration], ...]], ...]:
190
+ result = []
191
+ for candidate in candidates:
192
+ candidate = get_origin(candidate) or candidate
193
+ if isinstance(candidate, type):
194
+ result.append((candidate, tuple(_module_declarations(candidate).items())))
195
+ return tuple(result)
196
+
197
+ def assert_unchanged(self, provider: Provider, signature: _ProviderSignature) -> None:
198
+ if self.inspect(provider).signature != signature:
199
+ self._conflict(None, "native Provider was mutated after contribution")
200
+
146
201
  def _inspect_factory(
147
202
  self,
148
203
  provider: Provider,
@@ -192,7 +247,7 @@ class _NativeProviderInspector:
192
247
  self._conflict(key, f"collection scope must be {SUPPORTED_SCOPE_LABEL}")
193
248
  return _CollectionDefinition(collection, source_key, key, collection.scope)
194
249
 
195
- def _conflict(self, key: DependencyKey, reason: str) -> None:
250
+ def _conflict(self, key: DependencyKey | None, reason: str) -> None:
196
251
  raise RegistrationConflictError(key=key, sources=(self._source,), reason=reason)
197
252
 
198
253
  @staticmethod
@@ -2,6 +2,8 @@
2
2
 
3
3
  from typing import Protocol
4
4
 
5
+ from dishka import Scope
6
+
5
7
  from ..errors import InvalidConventionDefinitionError
6
8
  from .convention_contracts import _SERVICE_DEFINITION_ATTRIBUTE, _ServiceDefinition
7
9
  from .exposure import _business_interfaces
@@ -11,6 +13,10 @@ class RepositoryContract(Protocol):
11
13
  """仓储接口的正式角色;实例由选中 Module 的 REQUEST scope 缓存。"""
12
14
 
13
15
 
16
+ # 这是框架发现元数据,不是仓储实例的结构化成员契约。
17
+ setattr(RepositoryContract, "_fixed_service_scope", Scope.REQUEST) # noqa: B010 -- 不扩充 Protocol 成员
18
+
19
+
14
20
  def _repository_contract(candidate: type[object]) -> type[object] | None:
15
21
  if RepositoryContract not in candidate.__mro__:
16
22
  return None