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
@@ -9,66 +9,71 @@ from typing import cast
9
9
 
10
10
  from dishka import AsyncContainer, Scope
11
11
 
12
- from ..application_services.catalog import (
13
- ApplicationServiceMethod,
12
+ from ..application_services.invocation import _InvocationRuntime
13
+ from ..authorization import PermissionCatalog
14
+ from ..extensions.catalog import ExtensionCatalog
15
+ from ..invocation.execution import _InterceptorPipeline
16
+ from ..invocation.interceptors import InterceptionStage, InterceptorRegistration, _InterceptorRule
17
+ from ..invocation.methods import (
18
+ ServiceMethod,
19
+ _compile_method,
20
+ _CompiledMethod,
14
21
  _match_interceptors,
15
22
  _method_source,
23
+ _policy_fingerprint,
16
24
  )
17
- from ..application_services.execution import _InterceptorPipeline
18
- from ..application_services.interceptors import (
19
- InterceptionStage,
20
- InterceptorRegistration,
21
- _InterceptorRule,
22
- )
23
- from ..application_services.invocation import _InvocationRuntime
24
- from ..background_jobs import BackgroundJobCatalog
25
- from ..background_workers import BackgroundWorkerCatalog
26
- from ..events.catalog import LocalEventCatalog
27
- from ..events.contracts import LocalEventPhase
28
25
  from ..modularity import ModuleKey
29
26
  from ..unit_of_work.options import _UNIT_OF_WORK_ATTRIBUTE, UnitOfWorkDeclaration, _declaration
30
27
  from .callables import _call_in_scope
31
28
 
32
29
 
30
+ @dataclass(frozen=True, slots=True)
31
+ class InvocationEntry:
32
+ target: type[object] | Callable[..., Awaitable[None]]
33
+ method: str
34
+ owner: ModuleKey
35
+ business_parameters: int
36
+ outer_scope: Scope = Scope.REQUEST
37
+ method_scope: Scope = Scope.ACTION
38
+ unit_of_work_default: UnitOfWorkDeclaration | None = None
39
+ managed_method: bool = False
40
+
41
+
33
42
  @dataclass(frozen=True, slots=True)
34
43
  class _CompiledEntry:
35
- descriptor: ApplicationServiceMethod
44
+ descriptor: ServiceMethod
36
45
  steps: Mapping[InterceptionStage, tuple[InterceptorRegistration, ...]]
37
46
  outer_scope: Scope
38
47
  method_scope: Scope
48
+ managed: _CompiledMethod | None = None
39
49
 
40
50
 
41
51
  class _EntryInterceptionCatalog:
42
52
  def __init__(
43
53
  self,
44
- events: LocalEventCatalog,
54
+ declarations: tuple[InvocationEntry, ...],
45
55
  rules: tuple[_InterceptorRule, ...],
46
- workers: BackgroundWorkerCatalog,
47
- jobs: BackgroundJobCatalog,
56
+ implementations: Mapping[type[object], type[object]],
57
+ permission_catalog: PermissionCatalog | None,
58
+ extensions: ExtensionCatalog,
48
59
  ) -> None:
49
60
  entries: dict[tuple[object, str], _CompiledEntry] = {}
50
- for worker in workers.definitions:
61
+ for declaration in declarations:
51
62
  self._add(
52
63
  entries,
53
- worker.worker_type,
54
- "run_iteration",
55
- workers.owner_for(worker.name),
64
+ declaration.target,
65
+ declaration.method,
66
+ declaration.owner,
56
67
  rules,
57
- True,
58
- )
59
- for job in jobs.definitions:
60
- self._add(entries, job.handler_type, "execute", jobs.owner_for(job), rules, False)
61
- for subscription in events.subscriptions:
62
- scope = Scope.ACTION if subscription.phase is LocalEventPhase.DOMAIN else Scope.REQUEST
63
- self._add(
64
- entries,
65
- subscription.handler_type,
66
- "handle",
67
- subscription.owner,
68
- rules,
69
- False,
70
- outer_scope=scope,
71
- method_scope=scope,
68
+ declaration.unit_of_work_default,
69
+ implementation=implementations.get(declaration.target)
70
+ if isinstance(declaration.target, type)
71
+ else None,
72
+ outer_scope=declaration.outer_scope,
73
+ method_scope=declaration.method_scope,
74
+ managed_method=declaration.managed_method,
75
+ permission_catalog=permission_catalog,
76
+ extensions=extensions,
72
77
  )
73
78
  self._entries: Mapping[tuple[object, str], _CompiledEntry] = MappingProxyType(entries)
74
79
 
@@ -92,13 +97,19 @@ class _EntryInterceptionCatalog:
92
97
  name: str,
93
98
  owner: ModuleKey,
94
99
  rules: tuple[_InterceptorRule, ...],
95
- worker: bool,
100
+ unit_of_work_default: UnitOfWorkDeclaration | None,
96
101
  *,
102
+ implementation: type[object] | None,
97
103
  outer_scope: Scope = Scope.REQUEST,
98
104
  method_scope: Scope = Scope.ACTION,
105
+ managed_method: bool = False,
106
+ permission_catalog: PermissionCatalog | None = None,
107
+ extensions: ExtensionCatalog,
99
108
  ) -> None:
100
109
  if (target, name) in entries:
101
110
  current = entries[target, name]
111
+ if (current.managed is not None) != managed_method:
112
+ raise TypeError("The same entry cannot mix managed and lifecycle-only policies")
102
113
  # 同一 handler 可订阅两个 phase;构建时使用所有实际入口的最窄解析边界。
103
114
  entries[target, name] = replace(
104
115
  current,
@@ -106,15 +117,38 @@ class _EntryInterceptionCatalog:
106
117
  method_scope=min(current.method_scope, method_scope),
107
118
  )
108
119
  return
109
- function = getattr(target, name) if isinstance(target, type) else target
110
- implementation = target if isinstance(target, type) else type(target)
120
+ implementation = implementation or (target if isinstance(target, type) else type(target))
121
+ if managed_method:
122
+ if not isinstance(target, type):
123
+ raise TypeError("Managed entry requires a class binding")
124
+ compiled = _compile_method(
125
+ implementation,
126
+ implementation,
127
+ name,
128
+ owner,
129
+ owner,
130
+ permission_catalog,
131
+ rules,
132
+ auditing_default=False,
133
+ unit_of_work_default=unit_of_work_default or UnitOfWorkDeclaration(),
134
+ extensions=extensions,
135
+ )
136
+ entries[target, name] = _CompiledEntry(
137
+ compiled.descriptor,
138
+ compiled.interceptor_steps,
139
+ outer_scope,
140
+ method_scope,
141
+ compiled,
142
+ )
143
+ return
144
+ function = getattr(implementation, name) if isinstance(target, type) else target
111
145
  declaration = _declaration(implementation, function)
112
- if worker and not any(
146
+ if unit_of_work_default is not None and not any(
113
147
  hasattr(item, _UNIT_OF_WORK_ATTRIBUTE) for item in (implementation, function)
114
148
  ):
115
- declaration = UnitOfWorkDeclaration(disabled=True)
149
+ declaration = unit_of_work_default
116
150
  source = _method_source(function, owner)
117
- descriptor = ApplicationServiceMethod(
151
+ descriptor = ServiceMethod(
118
152
  name=name,
119
153
  source=source,
120
154
  requires_authenticated_user=False,
@@ -152,6 +186,10 @@ class _EntryInterceptionCatalog:
152
186
  connection_name: str | None = None,
153
187
  ) -> object:
154
188
  entry = self._entries[target, name]
189
+ if entry.managed is not None:
190
+ return await _invoke_managed_entry(
191
+ entry.managed, runtime, container, target, args, connection_name
192
+ )
155
193
  pipeline = _InterceptorPipeline(entry.descriptor, entry.steps, args, {})
156
194
 
157
195
  async def call(scope: AsyncContainer) -> object:
@@ -191,3 +229,36 @@ class _EntryInterceptionCatalog:
191
229
  return await pipeline.run(stages[index], container, lambda: run(index + 1))
192
230
 
193
231
  return await run(0)
232
+
233
+
234
+ async def _invoke_managed_entry(
235
+ method: _CompiledMethod,
236
+ runtime: _InvocationRuntime,
237
+ container: AsyncContainer,
238
+ target: object,
239
+ args: tuple[object, ...],
240
+ connection_name: str | None,
241
+ ) -> object:
242
+ from .dispatcher import _MethodDispatcher
243
+
244
+ implementation = method.descriptor.implementation
245
+ if (
246
+ getattr(implementation, method.name) is not method.implementation_method
247
+ or _policy_fingerprint(implementation, method.implementation_method)
248
+ != method.policy_fingerprint
249
+ ):
250
+ raise TypeError("Managed entry declaration changed after compilation")
251
+ if connection_name is None:
252
+ raise ValueError("Managed entry requires an execution connection")
253
+
254
+ async def invoke_target(
255
+ scope: AsyncContainer, arguments: tuple[object, ...], kwargs: dict[str, object]
256
+ ) -> object:
257
+ instance = await scope.get(cast(type[object], target))
258
+ return await cast(Callable[..., Awaitable[object]], getattr(instance, method.name))(
259
+ *arguments, **kwargs
260
+ )
261
+
262
+ return await _MethodDispatcher(container, runtime).invoke_method(
263
+ method, args, {}, invoke_target, connection_name
264
+ )
@@ -12,7 +12,7 @@ from ..diagnostics import SourceLocation
12
12
  from ..modularity import ModuleKey
13
13
 
14
14
  if TYPE_CHECKING:
15
- from .catalog import ApplicationServiceMethod
15
+ from ..invocation.methods import ServiceMethod
16
16
  from .execution import MethodInvocation
17
17
 
18
18
 
@@ -41,4 +41,4 @@ class InterceptorRegistration:
41
41
  @dataclass(frozen=True, slots=True)
42
42
  class _InterceptorRule:
43
43
  registration: InterceptorRegistration
44
- match: Callable[[ApplicationServiceMethod], bool]
44
+ match: Callable[[ServiceMethod], bool]
@@ -0,0 +1,116 @@
1
+ """最终普通 DI 绑定的方法解释、同 scope 原生装饰与图证明。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping
6
+ from functools import wraps
7
+
8
+ from dishka import AsyncContainer, Scope
9
+
10
+ from ..application_services.invocation import _InvocationRuntime, _managed_request
11
+ from ..unit_of_work.options import (
12
+ _DEFAULT_CONNECTION_NAME,
13
+ )
14
+ from .contracts import ApplicationInvocationContext
15
+ from .dispatcher import _MethodDispatcher
16
+ from .methods import (
17
+ _CompiledMethod,
18
+ )
19
+
20
+
21
+ class _ManagedServiceProxy:
22
+ """只转交被标记的方法;实例、未标记成员和 finalizer 仍属于原生绑定。"""
23
+
24
+ __slots__ = (
25
+ "__auditing",
26
+ "__container",
27
+ "__instance",
28
+ "__methods",
29
+ "__runtime",
30
+ "__unit_of_work",
31
+ )
32
+ __instance: object
33
+ __container: AsyncContainer
34
+ __runtime: _InvocationRuntime
35
+ __methods: Mapping[str, _CompiledMethod]
36
+ __unit_of_work: bool
37
+ __auditing: bool
38
+
39
+ def __init__(
40
+ self,
41
+ instance: object,
42
+ container: AsyncContainer,
43
+ runtime: _InvocationRuntime,
44
+ methods: Mapping[str, _CompiledMethod],
45
+ *,
46
+ unit_of_work: bool,
47
+ auditing: bool,
48
+ ) -> None:
49
+ # 仅初始化代理自身;之后业务属性写入必须与方法使用同一个原生实例。
50
+ object.__setattr__(self, "_ManagedServiceProxy__instance", instance)
51
+ object.__setattr__(self, "_ManagedServiceProxy__container", container)
52
+ object.__setattr__(self, "_ManagedServiceProxy__runtime", runtime)
53
+ object.__setattr__(self, "_ManagedServiceProxy__methods", methods)
54
+ object.__setattr__(self, "_ManagedServiceProxy__unit_of_work", unit_of_work)
55
+ object.__setattr__(self, "_ManagedServiceProxy__auditing", auditing)
56
+
57
+ def __setattr__(self, name: str, value: object) -> None:
58
+ setattr(self.__instance, name, value)
59
+
60
+ def __delattr__(self, name: str) -> None:
61
+ delattr(self.__instance, name)
62
+
63
+ def __getattr__(self, name: str) -> object:
64
+ method = self.__methods.get(name)
65
+ if method is None:
66
+ return getattr(self.__instance, name)
67
+ bound = getattr(self.__instance, name)
68
+
69
+ @wraps(bound)
70
+ async def invoke(*args: object, **kwargs: object) -> object:
71
+ async def target(
72
+ _: AsyncContainer, args: tuple[object, ...], kwargs: dict[str, object]
73
+ ) -> object:
74
+ return await method.implementation_method(self.__instance, *args, **kwargs)
75
+
76
+ runtime = self.__runtime
77
+ async with runtime.outer_lease():
78
+ ambient = runtime.current()
79
+ current = (
80
+ ambient.action_container
81
+ if ambient is not None
82
+ else runtime.current_call_scope()
83
+ )
84
+ connection = (
85
+ ambient.context.connection_name
86
+ if ambient is not None
87
+ else _DEFAULT_CONNECTION_NAME
88
+ )
89
+
90
+ async def call(container: AsyncContainer) -> object:
91
+ action_connection = (
92
+ (await container.get(ApplicationInvocationContext)).connection_name
93
+ if container.scope is Scope.ACTION
94
+ else connection
95
+ )
96
+ dispatcher = _MethodDispatcher(
97
+ container,
98
+ runtime,
99
+ unit_of_work_enabled=self.__unit_of_work,
100
+ auditing_enabled=self.__auditing,
101
+ )
102
+ return await dispatcher.invoke_method(
103
+ method, args, kwargs, target, action_connection
104
+ )
105
+
106
+ if current is not None:
107
+ return await call(current)
108
+ if self.__container.scope is not Scope.APP:
109
+ with runtime.call_scope(self.__container):
110
+ return await call(self.__container)
111
+ # APP 对象不捕获某次 REQUEST;每次独立调用创建并排空自己的 scope。
112
+ async with _managed_request(self.__container) as request:
113
+ with runtime.call_scope(request):
114
+ return await call(request)
115
+
116
+ return invoke
@@ -0,0 +1,250 @@
1
+ """最终普通 DI 绑定的方法解释、同 scope 原生装饰与图证明。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable, Mapping
6
+ from dataclasses import dataclass
7
+ from types import MappingProxyType
8
+ from typing import ParamSpec, TypeVar, TypeVarTuple, get_origin
9
+
10
+ from dishka import AsyncContainer, DependencyKey, Provider, Scope
11
+ from dishka.dependency_source import Factory
12
+ from dishka.entities.factory_type import FactoryType
13
+
14
+ from ..application_services.contracts import ApplicationService, ApplicationServiceContract
15
+ from ..application_services.errors import ApplicationServiceDefinitionError
16
+ from ..application_services.invocation import _InvocationRuntime
17
+ from ..authorization.contracts import _AUTHORIZATION_ATTRIBUTE
18
+ from ..extensions.module import ExtensionsModule
19
+ from ..invocation.interceptors import InterceptionStage, _InterceptorRule
20
+ from ..modularity.discovery import _require_type_dependencies
21
+ from ..services.composition import ServiceBindingContext, ServiceValidationContext
22
+ from ..services.registration import RegistrationDecision
23
+ from ..unit_of_work.options import (
24
+ _UNIT_OF_WORK_ATTRIBUTE,
25
+ UnitOfWorkDeclaration,
26
+ )
27
+ from .managed_proxy import _ManagedServiceProxy
28
+ from .methods import (
29
+ _compile_method,
30
+ _CompiledMethod,
31
+ _policy_fingerprint,
32
+ _public_method_names,
33
+ _require_async_instance_method,
34
+ )
35
+ from .policies import ValidationEnabled
36
+ from .validation import _validate_interceptor_scopes
37
+
38
+
39
+ def _managed_names(candidate: type[object]) -> tuple[str, ...]:
40
+ attributes = (_UNIT_OF_WORK_ATTRIBUTE, _AUTHORIZATION_ATTRIBUTE)
41
+ class_marked = issubclass(candidate, ValidationEnabled) or any(
42
+ hasattr(candidate, attribute) for attribute in attributes
43
+ )
44
+ names: list[str] = []
45
+ # 类级标记覆盖公开方法;显式方法标记即使位于非公开方法也必须接受形态校验。
46
+ members = {
47
+ name: value for base in reversed(candidate.__mro__) for name, value in vars(base).items()
48
+ }
49
+ public = _public_method_names(candidate, is_contract=False)
50
+ for name, value in members.items():
51
+ targets = (
52
+ (value, value.__func__) if isinstance(value, (staticmethod, classmethod)) else (value,)
53
+ )
54
+ marked = any(hasattr(target, attribute) for target in targets for attribute in attributes)
55
+ if marked or (class_marked and name in public):
56
+ _require_async_instance_method(candidate, name, role="managed service")
57
+ names.append(name)
58
+ return tuple(names)
59
+
60
+
61
+ def _assert_methods(candidate: type[object], methods: Mapping[str, _CompiledMethod]) -> None:
62
+ if tuple(methods) != _managed_names(candidate) or any(
63
+ getattr(candidate, name) is not method.implementation_method
64
+ or _policy_fingerprint(candidate, method.implementation_method) != method.policy_fingerprint
65
+ for name, method in methods.items()
66
+ ):
67
+ raise ApplicationServiceDefinitionError(
68
+ symbol=candidate.__qualname__,
69
+ reason="managed method declaration differs from the compiled binding",
70
+ )
71
+
72
+
73
+ @dataclass(frozen=True, slots=True)
74
+ class _ManagedBinding:
75
+ key: DependencyKey
76
+ factory: Factory
77
+ implementation: type[object]
78
+ methods: Mapping[str, _CompiledMethod]
79
+
80
+
81
+ class _ManagedServices:
82
+ def __init__(self) -> None:
83
+ self._bindings: list[tuple[_ManagedBinding, object]] = []
84
+
85
+ def decorate(
86
+ self,
87
+ context: ServiceBindingContext,
88
+ runtime: _InvocationRuntime,
89
+ rules: tuple[_InterceptorRule, ...],
90
+ entry_targets: set[object],
91
+ ) -> None:
92
+ from ..auditing.module import AuditingModule
93
+ from ..authorization.module import AuthorizationModule
94
+ from ..unit_of_work.module import UnitOfWorkModule
95
+
96
+ modules = {descriptor.module_type for descriptor in context.modules.dependency_order}
97
+ permissions = (
98
+ context.modules.instance(AuthorizationModule).catalog
99
+ if AuthorizationModule in modules
100
+ else None
101
+ )
102
+ capabilities = dict(
103
+ unit_of_work=UnitOfWorkModule in modules, auditing=AuditingModule in modules
104
+ )
105
+ # 来源 owner 来自已仲裁贡献或派生记录,不另建服务类型清单。
106
+ contributions = [
107
+ (draft.key, draft.contribution.owner, draft.contribution.source, draft.factory)
108
+ for draft in context.arbitration.drafts
109
+ if draft.factory is not None and draft.decision is RegistrationDecision.ACCEPTED
110
+ ]
111
+ for descriptor, inspector, provider, _ in context._provider_checks:
112
+ for item in inspector.inspect(provider).factories:
113
+ contributions.append(
114
+ (item.key, descriptor.key, descriptor.declaration_source, item.factory)
115
+ )
116
+ grouped: dict[DependencyKey, list[_ManagedBinding]] = {}
117
+ # 只消费仲裁胜出的 factory,原生 multi 的每项仍保留自己的最终实现事实。
118
+ for key, owner, source, factory in contributions:
119
+ factory_type = get_origin(factory.source) or factory.source
120
+ candidate = factory_type if isinstance(factory_type, type) else key.type_hint
121
+ candidate = get_origin(candidate) or candidate
122
+ if (
123
+ factory.type is FactoryType.ALIAS
124
+ or not isinstance(candidate, type)
125
+ or issubclass(candidate, ApplicationService)
126
+ or ApplicationServiceContract in candidate.__mro__
127
+ or candidate in entry_targets
128
+ ):
129
+ continue
130
+ names = _managed_names(candidate)
131
+ if names:
132
+ _require_type_dependencies(context.modules, owner, candidate, source=source)
133
+ service = key.type_hint if isinstance(key.type_hint, type) else candidate
134
+ methods = MappingProxyType(
135
+ {
136
+ name: _compile_method(
137
+ service if hasattr(service, name) else candidate,
138
+ candidate,
139
+ name,
140
+ owner,
141
+ owner,
142
+ permissions,
143
+ rules,
144
+ validation_default=issubclass(candidate, ValidationEnabled),
145
+ auditing_default=False,
146
+ unit_of_work_default=UnitOfWorkDeclaration(disabled=True),
147
+ extensions=context.modules.instance(ExtensionsModule).catalog,
148
+ )
149
+ for name in names
150
+ }
151
+ )
152
+ # 闭合 key 不能使仍含类型参数的方法悄悄退化成 Any 验证。
153
+ for method in methods.values():
154
+ annotations = (
155
+ *(parameter.annotation for parameter in method.signature.parameters.values()),
156
+ method.signature.return_annotation,
157
+ )
158
+ if any(
159
+ isinstance(annotation, (TypeVar, ParamSpec, TypeVarTuple))
160
+ or getattr(annotation, "__parameters__", ())
161
+ for annotation in annotations
162
+ ):
163
+ raise ApplicationServiceDefinitionError(
164
+ symbol=f"{candidate.__qualname__}.{method.name}",
165
+ reason="managed methods require concrete annotations",
166
+ )
167
+ grouped.setdefault(key, []).append(_ManagedBinding(key, factory, candidate, methods))
168
+ for key, bindings in grouped.items():
169
+ if not any(
170
+ binding.methods
171
+ or binding.factory.type is FactoryType.CONTEXT
172
+ or not isinstance(
173
+ get_origin(binding.factory.source) or binding.factory.source, type
174
+ )
175
+ for binding in bindings
176
+ ):
177
+ continue
178
+ decorator = self._decorator(key, runtime, tuple(bindings), **capabilities)
179
+ decorated = Provider(component=key.component)
180
+ decorated.decorate(decorator)
181
+ context._add_method_decorator(decorated)
182
+ self._bindings.extend((binding, decorator) for binding in bindings)
183
+
184
+ @staticmethod
185
+ def _decorator(
186
+ key: DependencyKey,
187
+ runtime: _InvocationRuntime,
188
+ bindings: tuple[_ManagedBinding, ...],
189
+ *,
190
+ unit_of_work: bool,
191
+ auditing: bool,
192
+ ) -> Callable[..., object]:
193
+ def decorate(instance: object, container: AsyncContainer) -> object:
194
+ # 外部 Provider 的实际返回对象必须符合已编译策略。
195
+ if not any(binding.methods for binding in bindings):
196
+ if isinstance(instance, ApplicationService) or _managed_names(type(instance)):
197
+ raise ApplicationServiceDefinitionError(
198
+ symbol=str(key), reason="factory result has uncompiled managed policies"
199
+ )
200
+ return instance
201
+ binding = next(
202
+ (item for item in bindings if type(instance) is item.implementation), None
203
+ )
204
+ if binding is None:
205
+ raise ApplicationServiceDefinitionError(
206
+ symbol=str(key), reason="factory result differs from the compiled binding"
207
+ )
208
+ methods = binding.methods
209
+ _assert_methods(type(instance), methods)
210
+ if not methods:
211
+ return instance
212
+ return _ManagedServiceProxy(
213
+ instance, container, runtime, methods, unit_of_work=unit_of_work, auditing=auditing
214
+ )
215
+
216
+ # 原生 decorate 用注解识别被装饰 key,依赖顺序是 instance -> 当前 scope 容器。
217
+ decorate.__annotations__ = {
218
+ "instance": key.type_hint,
219
+ "container": AsyncContainer,
220
+ "return": key.type_hint,
221
+ }
222
+ return decorate
223
+
224
+ def validate(self, context: ServiceValidationContext) -> None:
225
+ graph = context.native_graph
226
+ for binding, decorator in self._bindings:
227
+ _assert_methods(binding.implementation, binding.methods)
228
+ # collect 可移动 key.depth;真实装饰和底层 factory 身份仍须一一对应。
229
+ if not any(
230
+ row.factory.source is decorator
231
+ and row.scope is binding.factory.scope
232
+ and row.factory.cache == binding.factory.cache
233
+ and graph.effective_factory(row).source is binding.factory.source
234
+ for row in graph
235
+ ):
236
+ raise ApplicationServiceDefinitionError(
237
+ symbol=str(binding.key),
238
+ reason="managed decorator differs from final native binding",
239
+ )
240
+ _validate_interceptor_scopes(
241
+ graph,
242
+ (
243
+ (
244
+ rule,
245
+ Scope.ACTION if rule.before is InterceptionStage.METHOD else Scope.REQUEST,
246
+ )
247
+ for method in binding.methods.values()
248
+ for rule in method.descriptor.interceptors
249
+ ),
250
+ )