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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (355) hide show
  1. python_ddd_framework/__init__.py +449 -0
  2. python_ddd_framework/application/__init__.py +6 -0
  3. python_ddd_framework/application/build_spec.py +37 -0
  4. python_ddd_framework/application/builder.py +260 -0
  5. python_ddd_framework/application/composition.py +219 -0
  6. python_ddd_framework/application/runtime.py +549 -0
  7. python_ddd_framework/application/runtime_hooks.py +327 -0
  8. python_ddd_framework/application/state.py +216 -0
  9. python_ddd_framework/application_services/__init__.py +54 -0
  10. python_ddd_framework/application_services/catalog.py +500 -0
  11. python_ddd_framework/application_services/contracts.py +190 -0
  12. python_ddd_framework/application_services/dispatcher.py +423 -0
  13. python_ddd_framework/application_services/errors.py +89 -0
  14. python_ddd_framework/application_services/execution.py +199 -0
  15. python_ddd_framework/application_services/interceptors.py +44 -0
  16. python_ddd_framework/application_services/invocation.py +431 -0
  17. python_ddd_framework/application_services/policies.py +46 -0
  18. python_ddd_framework/application_services/seeding.py +72 -0
  19. python_ddd_framework/application_services/signature.py +49 -0
  20. python_ddd_framework/application_services/validation.py +84 -0
  21. python_ddd_framework/auditing/__init__.py +12 -0
  22. python_ddd_framework/auditing/contracts.py +39 -0
  23. python_ddd_framework/auditing/control.py +48 -0
  24. python_ddd_framework/auditing/sqlalchemy/__init__.py +6 -0
  25. python_ddd_framework/auditing/sqlalchemy/migrations/0001_auditing.py +45 -0
  26. python_ddd_framework/auditing/sqlalchemy/migrations/__init__.py +1 -0
  27. python_ddd_framework/auditing/sqlalchemy/models.py +31 -0
  28. python_ddd_framework/auditing/sqlalchemy/module.py +32 -0
  29. python_ddd_framework/auditing/sqlalchemy/store.py +63 -0
  30. python_ddd_framework/authorization/__init__.py +38 -0
  31. python_ddd_framework/authorization/catalog.py +63 -0
  32. python_ddd_framework/authorization/contracts.py +241 -0
  33. python_ddd_framework/authorization/definition_discovery.py +81 -0
  34. python_ddd_framework/authorization/definitions.py +43 -0
  35. python_ddd_framework/authorization/errors.py +40 -0
  36. python_ddd_framework/background_execution/__init__.py +17 -0
  37. python_ddd_framework/background_execution/application.py +41 -0
  38. python_ddd_framework/background_execution/child.py +123 -0
  39. python_ddd_framework/background_execution/contracts.py +48 -0
  40. python_ddd_framework/background_execution/lifecycle.py +22 -0
  41. python_ddd_framework/background_execution/local.py +127 -0
  42. python_ddd_framework/background_execution/locks.py +33 -0
  43. python_ddd_framework/background_execution/management.py +17 -0
  44. python_ddd_framework/background_execution/module.py +10 -0
  45. python_ddd_framework/background_execution/permissions.py +14 -0
  46. python_ddd_framework/background_execution/processes.py +331 -0
  47. python_ddd_framework/background_jobs/__init__.py +31 -0
  48. python_ddd_framework/background_jobs/catalog.py +340 -0
  49. python_ddd_framework/background_jobs/contracts.py +208 -0
  50. python_ddd_framework/background_jobs/declaration.py +67 -0
  51. python_ddd_framework/background_jobs/errors.py +28 -0
  52. python_ddd_framework/background_jobs/execution.py +203 -0
  53. python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt +21 -0
  54. python_ddd_framework/background_jobs/pgqueuer/__init__.py +6 -0
  55. python_ddd_framework/background_jobs/pgqueuer/enqueue.py +86 -0
  56. python_ddd_framework/background_jobs/pgqueuer/migrations/0001_pgqueuer_1_3_2.py +95 -0
  57. python_ddd_framework/background_jobs/pgqueuer/migrations/__init__.py +1 -0
  58. python_ddd_framework/background_jobs/pgqueuer/module.py +106 -0
  59. python_ddd_framework/background_jobs/pgqueuer/options.py +82 -0
  60. python_ddd_framework/background_jobs/pgqueuer/runtime.py +479 -0
  61. python_ddd_framework/background_jobs/pgqueuer/sql/pgqueuer_1_3_2_install.sql +118 -0
  62. python_ddd_framework/background_jobs/pgqueuer/supervision.py +201 -0
  63. python_ddd_framework/background_workers/__init__.py +22 -0
  64. python_ddd_framework/background_workers/catalog.py +118 -0
  65. python_ddd_framework/background_workers/contracts.py +152 -0
  66. python_ddd_framework/background_workers/errors.py +20 -0
  67. python_ddd_framework/background_workers/execution.py +133 -0
  68. python_ddd_framework/background_workers/runtime.py +219 -0
  69. python_ddd_framework/caching/__init__.py +11 -0
  70. python_ddd_framework/caching/catalog.py +73 -0
  71. python_ddd_framework/caching/contracts.py +73 -0
  72. python_ddd_framework/caching/errors.py +20 -0
  73. python_ddd_framework/cli/__init__.py +105 -0
  74. python_ddd_framework/cli/development.py +85 -0
  75. python_ddd_framework/cli/errors.py +5 -0
  76. python_ddd_framework/cli/inspection.py +96 -0
  77. python_ddd_framework/cli/project.py +69 -0
  78. python_ddd_framework/cli/runtime.py +56 -0
  79. python_ddd_framework/configuration/__init__.py +21 -0
  80. python_ddd_framework/configuration/composition.py +65 -0
  81. python_ddd_framework/configuration/contracts.py +68 -0
  82. python_ddd_framework/configuration/dotenv_source.py +51 -0
  83. python_ddd_framework/configuration/environment_source.py +38 -0
  84. python_ddd_framework/configuration/immutability.py +124 -0
  85. python_ddd_framework/configuration/input_shape.py +58 -0
  86. python_ddd_framework/configuration/merge.py +103 -0
  87. python_ddd_framework/configuration/root.py +215 -0
  88. python_ddd_framework/configuration/sources.py +519 -0
  89. python_ddd_framework/configuration/values.py +427 -0
  90. python_ddd_framework/configuration/yaml_source.py +51 -0
  91. python_ddd_framework/developer_kit/__init__.py +1 -0
  92. python_ddd_framework/developer_kit/generation.py +182 -0
  93. python_ddd_framework/developer_kit/project_metadata.py +45 -0
  94. python_ddd_framework/developer_kit/source.py +53 -0
  95. python_ddd_framework/developer_kit/templates/module/cookiecutter.json +1 -0
  96. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/README.md +70 -0
  97. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/__init__.py.jinja +1 -0
  98. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/__init__.py.jinja +1 -0
  99. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/cache.py.jinja +10 -0
  100. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/events.py.jinja +24 -0
  101. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration.py.jinja +41 -0
  102. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/module.py.jinja +20 -0
  103. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/options.py.jinja +7 -0
  104. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/orders.py.jinja +83 -0
  105. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/tasks.py.jinja +84 -0
  106. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/__init__.py.jinja +1 -0
  107. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/module.py.jinja +10 -0
  108. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/orders.py.jinja +35 -0
  109. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/__init__.py.jinja +1 -0
  110. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/module.py.jinja +10 -0
  111. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/orders.py.jinja +56 -0
  112. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repository.py.jinja +14 -0
  113. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding.py.jinja +19 -0
  114. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/settings.py.jinja +16 -0
  115. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/__init__.py.jinja +1 -0
  116. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/definitions.py.jinja +24 -0
  117. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/module.py.jinja +7 -0
  118. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions.py.jinja +15 -0
  119. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/__init__.py.jinja +1 -0
  120. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/files.py.jinja +73 -0
  121. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/module.py.jinja +25 -0
  122. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/realtime.py.jinja +36 -0
  123. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/__init__.py.jinja +1 -0
  124. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/migrations/__init__.py.jinja +1 -0
  125. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/__init__.py.jinja +7 -0
  126. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/orders.py.jinja +25 -0
  127. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/module.py.jinja +26 -0
  128. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/__init__.py.jinja +1 -0
  129. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/orders.py.jinja +49 -0
  130. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/test_domain.py.jinja +18 -0
  131. python_ddd_framework/developer_kit/templates/project/cookiecutter.json +6 -0
  132. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/.dockerignore +9 -0
  133. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/.gitignore +6 -0
  134. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/.python-version +1 -0
  135. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/AGENTS.md +38 -0
  136. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/Dockerfile +22 -0
  137. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/README.md +130 -0
  138. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/app.development.yaml.jinja +30 -0
  139. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/compose.dev.yaml.jinja +24 -0
  140. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/compose.production.yaml.jinja +20 -0
  141. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/architecture.md +84 -0
  142. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/development.md +185 -0
  143. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/pyproject.toml.jinja +28 -0
  144. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/src/host/__init__.py.jinja +1 -0
  145. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/src/host/main.py.jinja +47 -0
  146. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/src/host/module.py.jinja +37 -0
  147. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/tests/conftest.py.jinja +8 -0
  148. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/tests/host/test_http.py.jinja +57 -0
  149. python_ddd_framework/developer_kit/wiring.py +162 -0
  150. python_ddd_framework/diagnostics/__init__.py +23 -0
  151. python_ddd_framework/diagnostics/journal.py +174 -0
  152. python_ddd_framework/diagnostics/model.py +83 -0
  153. python_ddd_framework/diagnostics/source.py +25 -0
  154. python_ddd_framework/distributed_lock/__init__.py +6 -0
  155. python_ddd_framework/distributed_lock/contracts.py +20 -0
  156. python_ddd_framework/distributed_lock/options.py +19 -0
  157. python_ddd_framework/domain/__init__.py +13 -0
  158. python_ddd_framework/domain/aggregates.py +64 -0
  159. python_ddd_framework/domain/entities.py +33 -0
  160. python_ddd_framework/domain/errors.py +12 -0
  161. python_ddd_framework/domain/value_objects.py +31 -0
  162. python_ddd_framework/errors/__init__.py +75 -0
  163. python_ddd_framework/errors/base.py +13 -0
  164. python_ddd_framework/errors/business.py +53 -0
  165. python_ddd_framework/errors/configuration.py +119 -0
  166. python_ddd_framework/errors/diagnostics.py +12 -0
  167. python_ddd_framework/errors/lifecycle.py +164 -0
  168. python_ddd_framework/errors/modularity.py +100 -0
  169. python_ddd_framework/errors/services.py +100 -0
  170. python_ddd_framework/events/__init__.py +25 -0
  171. python_ddd_framework/events/aggregate.py +39 -0
  172. python_ddd_framework/events/catalog.py +136 -0
  173. python_ddd_framework/events/contracts.py +39 -0
  174. python_ddd_framework/events/contribution.py +128 -0
  175. python_ddd_framework/events/discovery.py +105 -0
  176. python_ddd_framework/events/errors.py +48 -0
  177. python_ddd_framework/events/runtime.py +192 -0
  178. python_ddd_framework/fastapi/__init__.py +49 -0
  179. python_ddd_framework/fastapi/action.py +175 -0
  180. python_ddd_framework/fastapi/adapter.py +391 -0
  181. python_ddd_framework/fastapi/application_services.py +654 -0
  182. python_ddd_framework/fastapi/background.py +42 -0
  183. python_ddd_framework/fastapi/contracts.py +275 -0
  184. python_ddd_framework/fastapi/errors.py +32 -0
  185. python_ddd_framework/fastapi/filters.py +111 -0
  186. python_ddd_framework/fastapi/health.py +44 -0
  187. python_ddd_framework/fastapi/http.py +72 -0
  188. python_ddd_framework/fastapi/http_router.py +216 -0
  189. python_ddd_framework/fastapi/manual_action.py +105 -0
  190. python_ddd_framework/fastapi/middleware.py +93 -0
  191. python_ddd_framework/fastapi/parameters.py +97 -0
  192. python_ddd_framework/fastapi/realtime/__init__.py +13 -0
  193. python_ddd_framework/fastapi/realtime/authentication.py +181 -0
  194. python_ddd_framework/fastapi/realtime/connection.py +166 -0
  195. python_ddd_framework/fastapi/realtime/module.py +41 -0
  196. python_ddd_framework/fastapi/realtime/options.py +50 -0
  197. python_ddd_framework/fastapi/realtime/runtime.py +423 -0
  198. python_ddd_framework/fastapi/request_context.py +247 -0
  199. python_ddd_framework/fastapi/route_integrity.py +88 -0
  200. python_ddd_framework/fastapi/routing.py +424 -0
  201. python_ddd_framework/fastapi/server.py +182 -0
  202. python_ddd_framework/fastapi/settings.py +40 -0
  203. python_ddd_framework/fastapi/tracing.py +62 -0
  204. python_ddd_framework/fastapi/transfer.py +128 -0
  205. python_ddd_framework/fastapi/upload_limits.py +63 -0
  206. python_ddd_framework/fastapi/uploads.py +94 -0
  207. python_ddd_framework/hosted_services/__init__.py +29 -0
  208. python_ddd_framework/hosted_services/bridge.py +420 -0
  209. python_ddd_framework/hosted_services/catalog.py +265 -0
  210. python_ddd_framework/hosted_services/contracts.py +55 -0
  211. python_ddd_framework/hosted_services/errors.py +53 -0
  212. python_ddd_framework/hosted_services/options.py +17 -0
  213. python_ddd_framework/hosted_services/runtime.py +267 -0
  214. python_ddd_framework/hosted_services/state.py +40 -0
  215. python_ddd_framework/hosting/__init__.py +4 -0
  216. python_ddd_framework/hosting/instance.py +40 -0
  217. python_ddd_framework/identity/__init__.py +73 -0
  218. python_ddd_framework/identity/application.py +287 -0
  219. python_ddd_framework/identity/contracts.py +283 -0
  220. python_ddd_framework/identity/errors.py +28 -0
  221. python_ddd_framework/identity/http_api.py +90 -0
  222. python_ddd_framework/identity/module.py +49 -0
  223. python_ddd_framework/identity/passwords.py +34 -0
  224. python_ddd_framework/identity/permissions.py +14 -0
  225. python_ddd_framework/identity/services.py +108 -0
  226. python_ddd_framework/identity/sqlalchemy/__init__.py +5 -0
  227. python_ddd_framework/identity/sqlalchemy/migrations/0001_identity.py +143 -0
  228. python_ddd_framework/identity/sqlalchemy/migrations/0002_physical_delete.py +42 -0
  229. python_ddd_framework/identity/sqlalchemy/migrations/__init__.py +1 -0
  230. python_ddd_framework/identity/sqlalchemy/models.py +85 -0
  231. python_ddd_framework/identity/sqlalchemy/module.py +62 -0
  232. python_ddd_framework/identity/sqlalchemy/stores.py +526 -0
  233. python_ddd_framework/identity/tokens.py +106 -0
  234. python_ddd_framework/invocation/__init__.py +3 -0
  235. python_ddd_framework/invocation/callables.py +171 -0
  236. python_ddd_framework/invocation/contracts.py +33 -0
  237. python_ddd_framework/invocation/entries.py +54 -0
  238. python_ddd_framework/invocation/function_runtime.py +48 -0
  239. python_ddd_framework/invocation/interception.py +193 -0
  240. python_ddd_framework/lifecycle/__init__.py +25 -0
  241. python_ddd_framework/lifecycle/composition.py +35 -0
  242. python_ddd_framework/lifecycle/context.py +31 -0
  243. python_ddd_framework/lifecycle/runtime.py +43 -0
  244. python_ddd_framework/lifecycle/state.py +20 -0
  245. python_ddd_framework/modularity/__init__.py +15 -0
  246. python_ddd_framework/modularity/contracts.py +91 -0
  247. python_ddd_framework/modularity/discovery.py +157 -0
  248. python_ddd_framework/modularity/graph.py +334 -0
  249. python_ddd_framework/modularity/registry.py +100 -0
  250. python_ddd_framework/modularity/selection.py +16 -0
  251. python_ddd_framework/notifications/__init__.py +19 -0
  252. python_ddd_framework/notifications/catalog.py +46 -0
  253. python_ddd_framework/notifications/contracts.py +107 -0
  254. python_ddd_framework/observability/__init__.py +5 -0
  255. python_ddd_framework/observability/context.py +43 -0
  256. python_ddd_framework/observability/export.py +73 -0
  257. python_ddd_framework/observability/formatting.py +89 -0
  258. python_ddd_framework/observability/logging.py +118 -0
  259. python_ddd_framework/observability/options.py +45 -0
  260. python_ddd_framework/observability/tracing.py +80 -0
  261. python_ddd_framework/options/__init__.py +19 -0
  262. python_ddd_framework/options/aliases.py +331 -0
  263. python_ddd_framework/options/contribution.py +62 -0
  264. python_ddd_framework/options/immutability.py +48 -0
  265. python_ddd_framework/options/input_keys.py +214 -0
  266. python_ddd_framework/options/issues.py +335 -0
  267. python_ddd_framework/options/models.py +114 -0
  268. python_ddd_framework/options/registry.py +280 -0
  269. python_ddd_framework/options/schema.py +488 -0
  270. python_ddd_framework/options/validation.py +120 -0
  271. python_ddd_framework/py.typed +0 -0
  272. python_ddd_framework/realtime/__init__.py +12 -0
  273. python_ddd_framework/realtime/contracts.py +28 -0
  274. python_ddd_framework/realtime/diagnostics.py +37 -0
  275. python_ddd_framework/realtime/messages.py +113 -0
  276. python_ddd_framework/redis/__init__.py +6 -0
  277. python_ddd_framework/redis/distributed_lock.py +212 -0
  278. python_ddd_framework/redis/lease_lock.py +36 -0
  279. python_ddd_framework/redis/module.py +50 -0
  280. python_ddd_framework/redis/notification_runtime.py +182 -0
  281. python_ddd_framework/redis/notifications.py +25 -0
  282. python_ddd_framework/redis/options.py +35 -0
  283. python_ddd_framework/redis/runtime.py +116 -0
  284. python_ddd_framework/services/__init__.py +41 -0
  285. python_ddd_framework/services/application_bindings.py +221 -0
  286. python_ddd_framework/services/arbitration.py +381 -0
  287. python_ddd_framework/services/binding.py +102 -0
  288. python_ddd_framework/services/contribution.py +438 -0
  289. python_ddd_framework/services/convention.py +301 -0
  290. python_ddd_framework/services/convention_contracts.py +101 -0
  291. python_ddd_framework/services/exposure.py +23 -0
  292. python_ddd_framework/services/fixed_lifetime.py +52 -0
  293. python_ddd_framework/services/framework_provider.py +178 -0
  294. python_ddd_framework/services/native_graph.py +201 -0
  295. python_ddd_framework/services/provider.py +204 -0
  296. python_ddd_framework/services/registration.py +101 -0
  297. python_ddd_framework/services/repository.py +38 -0
  298. python_ddd_framework/services/runtime.py +302 -0
  299. python_ddd_framework/settings/__init__.py +30 -0
  300. python_ddd_framework/settings/application.py +95 -0
  301. python_ddd_framework/settings/binding.py +33 -0
  302. python_ddd_framework/settings/catalog.py +121 -0
  303. python_ddd_framework/settings/changes.py +14 -0
  304. python_ddd_framework/settings/contracts.py +115 -0
  305. python_ddd_framework/settings/definition_discovery.py +71 -0
  306. python_ddd_framework/settings/definitions.py +41 -0
  307. python_ddd_framework/settings/errors.py +22 -0
  308. python_ddd_framework/settings/handlers.py +43 -0
  309. python_ddd_framework/settings/management.py +57 -0
  310. python_ddd_framework/settings/manager.py +89 -0
  311. python_ddd_framework/settings/module.py +10 -0
  312. python_ddd_framework/settings/notifications.py +39 -0
  313. python_ddd_framework/settings/permissions.py +14 -0
  314. python_ddd_framework/settings/provider.py +74 -0
  315. python_ddd_framework/settings/refresh.py +140 -0
  316. python_ddd_framework/settings/refresh_module.py +45 -0
  317. python_ddd_framework/settings/sqlalchemy/__init__.py +5 -0
  318. python_ddd_framework/settings/sqlalchemy/migrations/0001_settings.py +31 -0
  319. python_ddd_framework/settings/sqlalchemy/migrations/0002_physical_delete.py +32 -0
  320. python_ddd_framework/settings/sqlalchemy/migrations/0003_version_tokens.py +39 -0
  321. python_ddd_framework/settings/sqlalchemy/migrations/__init__.py +1 -0
  322. python_ddd_framework/settings/sqlalchemy/models.py +36 -0
  323. python_ddd_framework/settings/sqlalchemy/module.py +36 -0
  324. python_ddd_framework/settings/sqlalchemy/store.py +86 -0
  325. python_ddd_framework/settings/store.py +16 -0
  326. python_ddd_framework/settings/values.py +39 -0
  327. python_ddd_framework/sqlalchemy/__init__.py +54 -0
  328. python_ddd_framework/sqlalchemy/alembic_runtime/__init__.py +1 -0
  329. python_ddd_framework/sqlalchemy/alembic_runtime/env.py +33 -0
  330. python_ddd_framework/sqlalchemy/alembic_runtime/script.py.mako +14 -0
  331. python_ddd_framework/sqlalchemy/auditing.py +48 -0
  332. python_ddd_framework/sqlalchemy/errors.py +85 -0
  333. python_ddd_framework/sqlalchemy/metadata.py +576 -0
  334. python_ddd_framework/sqlalchemy/migration.py +378 -0
  335. python_ddd_framework/sqlalchemy/migration_options.py +56 -0
  336. python_ddd_framework/sqlalchemy/module.py +51 -0
  337. python_ddd_framework/sqlalchemy/module_migration.py +151 -0
  338. python_ddd_framework/sqlalchemy/options.py +66 -0
  339. python_ddd_framework/sqlalchemy/repository.py +33 -0
  340. python_ddd_framework/sqlalchemy/runtime.py +85 -0
  341. python_ddd_framework/sqlalchemy/session_provider.py +45 -0
  342. python_ddd_framework/sqlalchemy/unit_of_work.py +97 -0
  343. python_ddd_framework/testing/__init__.py +5 -0
  344. python_ddd_framework/testing/runtime.py +112 -0
  345. python_ddd_framework/unit_of_work/__init__.py +14 -0
  346. python_ddd_framework/unit_of_work/contracts.py +223 -0
  347. python_ddd_framework/unit_of_work/errors.py +27 -0
  348. python_ddd_framework/unit_of_work/manager.py +210 -0
  349. python_ddd_framework/unit_of_work/options.py +92 -0
  350. python_ddd_framework-0.3.1.dist-info/METADATA +379 -0
  351. python_ddd_framework-0.3.1.dist-info/RECORD +355 -0
  352. python_ddd_framework-0.3.1.dist-info/WHEEL +4 -0
  353. python_ddd_framework-0.3.1.dist-info/entry_points.txt +9 -0
  354. python_ddd_framework-0.3.1.dist-info/licenses/LICENSE +7 -0
  355. python_ddd_framework-0.3.1.dist-info/licenses/src/python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt +21 -0
@@ -0,0 +1,201 @@
1
+ """只读投影原生 Dishka 图,核对平台 catalog 与实际 binding 的一致性。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping
6
+ from dataclasses import dataclass
7
+ from typing import get_origin
8
+
9
+ from dishka import (
10
+ DEFAULT_COMPONENT,
11
+ AsyncContainer,
12
+ BaseScope,
13
+ DependencyKey,
14
+ Scope,
15
+ )
16
+ from dishka.dependency_source import Factory
17
+ from dishka.entities.factory_type import FactoryType
18
+
19
+ from ..application_services import ApplicationService, ApplicationServiceCatalog
20
+ from ..application_services.errors import ApplicationServiceDefinitionError
21
+ from ..application_services.interceptors import InterceptionStage
22
+ from ..errors import CatalogNativeGraphMismatchError, InvalidConventionDefinitionError
23
+ from ..invocation.interception import _EntryInterceptionCatalog
24
+ from .arbitration import (
25
+ _RegistrationDraft,
26
+ )
27
+ from .fixed_lifetime import _fixed_scope, _validate_fixed_lifetime
28
+ from .registration import (
29
+ RegistrationDecision,
30
+ RegistrationKind,
31
+ )
32
+
33
+
34
+ @dataclass(frozen=True, slots=True)
35
+ class _NativeGraphRegistration:
36
+ key: DependencyKey
37
+ scope: BaseScope
38
+ factory: Factory
39
+
40
+
41
+ class _NativeServiceGraph:
42
+ """只读投影 Dishka 已构建图,用于证明 catalog 与真实容器一致。"""
43
+
44
+ __slots__ = ("_rows",)
45
+
46
+ def __init__(self, container: AsyncContainer) -> None:
47
+ rows: list[_NativeGraphRegistration] = []
48
+ native_registry = container.registry
49
+ while True:
50
+ rows.extend(
51
+ _NativeGraphRegistration(
52
+ key=key,
53
+ scope=native_registry.scope,
54
+ factory=factory,
55
+ )
56
+ for key, factory in native_registry.factories.items()
57
+ )
58
+ child_registry = native_registry.child_registry
59
+ if child_registry is None:
60
+ break
61
+ native_registry = child_registry
62
+ self._rows = tuple(rows)
63
+
64
+ def differences(self, drafts: tuple[_RegistrationDraft, ...]) -> tuple[str, ...]:
65
+ return tuple(
66
+ f"missing accepted {draft.contribution.kind.value} registration for {draft.key}"
67
+ for draft in drafts
68
+ if draft.decision is RegistrationDecision.ACCEPTED and not self._matches(draft)
69
+ )
70
+
71
+ def validate_fixed_lifetimes(self) -> None:
72
+ for row in self._rows:
73
+ for candidate in (row.key.type_hint, row.factory.source):
74
+ # 闭合泛型仍受原始角色契约约束,函数 factory 不提供可替代的类信息。
75
+ candidate = get_origin(candidate) or candidate
76
+ scope = _fixed_scope(candidate)
77
+ if scope is None or not isinstance(candidate, type):
78
+ continue
79
+ _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
+ )
92
+ if row.scope is not scope or not factory.cache:
93
+ raise InvalidConventionDefinitionError(
94
+ symbol=str(row.key.type_hint),
95
+ reason=f"final fixed role binding requires cached {scope.name} lifetime",
96
+ )
97
+
98
+ def scope_for(self, draft: _RegistrationDraft) -> BaseScope:
99
+ if draft.scope is not None:
100
+ return draft.scope
101
+ for row in self._rows:
102
+ if row.key == draft.key:
103
+ return row.scope
104
+ raise CatalogNativeGraphMismatchError(
105
+ differences=(f"native scope unavailable for {draft.key}",)
106
+ )
107
+
108
+ def interceptor_scope(self, interceptor: type[object]) -> BaseScope | None:
109
+ return next(
110
+ (
111
+ row.scope
112
+ for row in self._rows
113
+ if row.key == DependencyKey(interceptor, DEFAULT_COMPONENT)
114
+ ),
115
+ None,
116
+ )
117
+
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
+ def _matches(self, draft: _RegistrationDraft) -> bool:
130
+ if draft.is_collection:
131
+ return any(
132
+ row.key == draft.key and row.factory.type is FactoryType.COLLECTION
133
+ for row in self._rows
134
+ )
135
+ assert draft.factory is not None
136
+ if draft.alias is not None:
137
+ return any(
138
+ row.key == draft.key
139
+ and row.factory.type is FactoryType.ALIAS
140
+ and row.factory.dependencies == draft.factory.dependencies
141
+ for row in self._rows
142
+ )
143
+ if draft.contribution.kind is RegistrationKind.MULTI:
144
+ return any(
145
+ row.key.type_hint == draft.key.type_hint
146
+ and row.key.component == draft.key.component
147
+ and row.factory.source is draft.factory.source
148
+ and row.scope is draft.scope
149
+ for row in self._rows
150
+ )
151
+ return any(
152
+ row.key == draft.key
153
+ and row.factory.source is draft.factory.source
154
+ and row.scope is draft.scope
155
+ for row in self._rows
156
+ )
157
+
158
+
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
+ def _accepted_single_service_scopes(
187
+ drafts: tuple[_RegistrationDraft, ...],
188
+ native_graph: _NativeServiceGraph,
189
+ ) -> Mapping[type[object], BaseScope]:
190
+ accepted: dict[type[object], BaseScope] = {}
191
+ for draft in drafts:
192
+ key_type = draft.key.type_hint
193
+ if (
194
+ draft.decision is RegistrationDecision.ACCEPTED
195
+ and draft.contribution.kind is not RegistrationKind.MULTI
196
+ and draft.factory is not None
197
+ and isinstance(key_type, type)
198
+ and draft.key.component == DEFAULT_COMPONENT
199
+ ):
200
+ accepted[key_type] = native_graph.scope_for(draft)
201
+ return accepted
@@ -0,0 +1,204 @@
1
+ """Dishka native Provider 的受支持边界、结构验证与 mutation fingerprint。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from types import MethodType
7
+
8
+ from dishka import BaseScope, DependencyKey, Provider
9
+ from dishka.dependency_source import Alias, Factory, FactoryUnionMode
10
+ from dishka.entities.factory_type import FactoryType
11
+ from dishka.entities.marker import BoolMarker
12
+
13
+ from ..diagnostics import SourceLocation
14
+ from ..errors import RegistrationConflictError
15
+ from .registration import SUPPORTED_SCOPE_LABEL, SUPPORTED_SCOPES, RegistrationKind
16
+
17
+
18
+ @dataclass(frozen=True, slots=True)
19
+ class _FactorySignature:
20
+ key: DependencyKey
21
+ scope: BaseScope | None
22
+ source_identity: tuple[int, int | None]
23
+ factory_type: FactoryType
24
+ dependencies: tuple[DependencyKey, ...]
25
+ keyword_dependencies: tuple[tuple[str, DependencyKey], ...]
26
+ cache: bool
27
+ allow_static_evaluation: bool
28
+ is_to_bind: bool
29
+ native_override: bool
30
+
31
+
32
+ @dataclass(frozen=True, slots=True)
33
+ class _CollectionSignature:
34
+ source_key: DependencyKey
35
+ key: DependencyKey
36
+ scope: BaseScope | None
37
+ cache: bool
38
+ collect: bool
39
+
40
+
41
+ @dataclass(frozen=True, slots=True)
42
+ class _ProviderSignature:
43
+ factories: tuple[_FactorySignature, ...]
44
+ collections: tuple[_CollectionSignature, ...]
45
+
46
+
47
+ @dataclass(frozen=True, slots=True)
48
+ class _FactoryDefinition:
49
+ factory: Factory
50
+ key: DependencyKey
51
+ scope: BaseScope | None
52
+ native_override: bool
53
+ alias: Alias | None = None
54
+
55
+
56
+ @dataclass(frozen=True, slots=True)
57
+ class _CollectionDefinition:
58
+ collection: FactoryUnionMode
59
+ source_key: DependencyKey
60
+ key: DependencyKey
61
+ scope: BaseScope | None
62
+
63
+
64
+ @dataclass(frozen=True, slots=True)
65
+ class _ValidatedProvider:
66
+ factories: tuple[_FactoryDefinition, ...]
67
+ collections: tuple[_CollectionDefinition, ...]
68
+ signature: _ProviderSignature
69
+
70
+
71
+ class _NativeProviderInspector:
72
+ """隔离 Dishka private dependency-source shape,返回框架拥有的稳定事实。"""
73
+
74
+ __slots__ = ("_kind", "_source")
75
+
76
+ def __init__(self, *, source: SourceLocation, kind: RegistrationKind) -> None:
77
+ self._source = source
78
+ self._kind = kind
79
+
80
+ def inspect(self, provider: Provider) -> _ValidatedProvider:
81
+ if provider.decorators or provider.activators:
82
+ raise RegistrationConflictError(
83
+ key=None,
84
+ sources=(self._source,),
85
+ reason=(
86
+ "service composition supports native provide/provide_all/alias/collect/"
87
+ "from_context; "
88
+ "decorator and activator sources are not catalog-safe"
89
+ ),
90
+ )
91
+ if not (
92
+ provider.factories
93
+ or provider.factory_union_mode
94
+ or provider.aliases
95
+ or provider.context_vars
96
+ ):
97
+ raise RegistrationConflictError(
98
+ key=None,
99
+ sources=(self._source,),
100
+ reason="native Provider contribution contains no dependency source",
101
+ )
102
+ # Alias 的执行与缓存仍使用 Dishka 原生模型;这里只归一化用于仲裁和指纹。
103
+ factories = (
104
+ *(
105
+ self._inspect_factory(provider, item.as_factory(provider.component))
106
+ for item in provider.context_vars
107
+ ),
108
+ *(self._inspect_factory(provider, item) for item in provider.factories),
109
+ *(
110
+ self._inspect_factory(provider, alias.as_factory(None, provider.component), alias)
111
+ for alias in provider.aliases
112
+ ),
113
+ )
114
+ collections = tuple(
115
+ self._inspect_collection(provider, item) for item in provider.factory_union_mode
116
+ )
117
+ signature = _ProviderSignature(
118
+ factories=tuple(
119
+ _FactorySignature(
120
+ key=item.key,
121
+ scope=item.scope,
122
+ source_identity=self.source_identity(item.factory.source),
123
+ factory_type=item.factory.type,
124
+ dependencies=tuple(item.factory.dependencies),
125
+ keyword_dependencies=tuple(sorted(item.factory.kw_dependencies.items())),
126
+ cache=item.factory.cache,
127
+ allow_static_evaluation=item.factory.allow_static_evaluation,
128
+ is_to_bind=item.factory.is_to_bind,
129
+ native_override=item.native_override,
130
+ )
131
+ for item in factories
132
+ ),
133
+ collections=tuple(
134
+ _CollectionSignature(
135
+ source_key=item.source_key,
136
+ key=item.key,
137
+ scope=item.scope,
138
+ cache=item.collection.cache,
139
+ collect=item.collection.collect,
140
+ )
141
+ for item in collections
142
+ ),
143
+ )
144
+ return _ValidatedProvider(factories, collections, signature)
145
+
146
+ def _inspect_factory(
147
+ self,
148
+ provider: Provider,
149
+ factory: Factory,
150
+ alias: Alias | None = None,
151
+ ) -> _FactoryDefinition:
152
+ key = factory.provides.with_component(provider.component)
153
+ scope = factory.scope
154
+ if scope not in SUPPORTED_SCOPES and not (alias is not None and scope is None):
155
+ self._conflict(key, f"service scope must be {SUPPORTED_SCOPE_LABEL}")
156
+ if factory.allow_static_evaluation:
157
+ self._conflict(
158
+ key,
159
+ "native static evaluation would execute a service factory during "
160
+ "Application.build()",
161
+ )
162
+ if (
163
+ factory.when_active is not None and factory.type is not FactoryType.CONTEXT
164
+ ) or factory.when_dependencies:
165
+ self._conflict(
166
+ key, "conditional native factories are outside the service arbitration contract"
167
+ )
168
+ native_override = factory.when_override == BoolMarker(True)
169
+ if factory.when_override is not None and not native_override:
170
+ self._conflict(
171
+ key, "conditional native override is outside the service arbitration contract"
172
+ )
173
+ if (self._kind is RegistrationKind.OVERRIDE) != native_override:
174
+ expected = "must" if self._kind is RegistrationKind.OVERRIDE else "must not"
175
+ self._conflict(
176
+ key, f"{self._kind.value} contribution {expected} use native override=True"
177
+ )
178
+ return _FactoryDefinition(factory, key, scope, native_override, alias)
179
+
180
+ def _inspect_collection(
181
+ self, provider: Provider, collection: FactoryUnionMode
182
+ ) -> _CollectionDefinition:
183
+ source_key = collection.source.with_component(provider.component)
184
+ key = collection.provides.with_component(provider.component)
185
+ if self._kind is not RegistrationKind.MULTI:
186
+ self._conflict(
187
+ source_key, "native collect() must be contributed with RegistrationKind.MULTI"
188
+ )
189
+ if not collection.collect:
190
+ self._conflict(source_key, "multi contribution must retain native collect() semantics")
191
+ if collection.scope is not None and collection.scope not in SUPPORTED_SCOPES:
192
+ self._conflict(key, f"collection scope must be {SUPPORTED_SCOPE_LABEL}")
193
+ return _CollectionDefinition(collection, source_key, key, collection.scope)
194
+
195
+ def _conflict(self, key: DependencyKey, reason: str) -> None:
196
+ raise RegistrationConflictError(key=key, sources=(self._source,), reason=reason)
197
+
198
+ @staticmethod
199
+ def source_identity(source: object) -> tuple[int, int | None]:
200
+ if isinstance(source, MethodType):
201
+ # bound method 的稳定 identity 同时由 function 与 receiver 决定;
202
+ # 不同 Provider instance 复用同一 method 定义不是重复 factory。
203
+ return id(source.__func__), id(source.__self__)
204
+ return id(source), None
@@ -0,0 +1,101 @@
1
+ """Service registration intent、provenance 与 immutable catalog contracts。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping
6
+ from dataclasses import dataclass
7
+ from enum import Enum
8
+ from types import MappingProxyType
9
+ from typing import TYPE_CHECKING
10
+
11
+ from dishka import BaseScope, DependencyKey, Provider, Scope
12
+
13
+ from ..diagnostics import SourceLocation
14
+ from ..modularity import ModuleKey
15
+
16
+ if TYPE_CHECKING:
17
+ from .convention_contracts import ConventionCandidate
18
+
19
+
20
+ SUPPORTED_SCOPES = (Scope.APP, Scope.SESSION, Scope.REQUEST, Scope.ACTION)
21
+ SUPPORTED_SCOPE_LABEL = " or ".join(
22
+ f"{type(scope).__name__}.{scope.name}" for scope in SUPPORTED_SCOPES
23
+ )
24
+
25
+
26
+ class RegistrationKind(str, Enum):
27
+ DEFAULT = "default"
28
+ CONVENTION = "convention"
29
+ EXPLICIT = "explicit"
30
+ OVERRIDE = "override"
31
+ MULTI = "multi"
32
+
33
+
34
+ class RegistrationDecision(str, Enum):
35
+ ACCEPTED = "accepted"
36
+ BACKED_OFF = "backed_off"
37
+ OVERRIDDEN = "overridden"
38
+
39
+
40
+ @dataclass(frozen=True, slots=True)
41
+ class ProviderContribution:
42
+ provider: Provider
43
+ owner: ModuleKey
44
+ kind: RegistrationKind
45
+ source: SourceLocation
46
+ reason: str
47
+ declaration_index: int
48
+
49
+
50
+ @dataclass(frozen=True, slots=True)
51
+ class ProviderContributionView:
52
+ owner: ModuleKey
53
+ kind: RegistrationKind
54
+ source: SourceLocation
55
+ reason: str
56
+ declaration_index: int
57
+
58
+
59
+ @dataclass(frozen=True, slots=True)
60
+ class ServiceRegistration:
61
+ key: DependencyKey
62
+ scope: BaseScope
63
+ owner: ModuleKey
64
+ source: SourceLocation
65
+ kind: RegistrationKind
66
+ declaration_index: int
67
+ decision: RegistrationDecision
68
+ decision_reason: str
69
+ replaced_sources: tuple[SourceLocation, ...]
70
+ multi_ordinal: int | None
71
+
72
+
73
+ class ServiceCatalog:
74
+ """Application-local 诊断目录;不保存 factory,也不参与 service resolution。"""
75
+
76
+ __slots__ = ("_by_key", "_convention_candidates", "_registrations")
77
+
78
+ def __init__(
79
+ self,
80
+ registrations: tuple[ServiceRegistration, ...],
81
+ convention_candidates: tuple[ConventionCandidate, ...] = (),
82
+ ) -> None:
83
+ by_key: dict[DependencyKey, list[ServiceRegistration]] = {}
84
+ for registration in registrations:
85
+ by_key.setdefault(registration.key, []).append(registration)
86
+ self._registrations = registrations
87
+ self._convention_candidates = convention_candidates
88
+ self._by_key: Mapping[DependencyKey, tuple[ServiceRegistration, ...]] = MappingProxyType(
89
+ {key: tuple(values) for key, values in by_key.items()}
90
+ )
91
+
92
+ @property
93
+ def registrations(self) -> tuple[ServiceRegistration, ...]:
94
+ return self._registrations
95
+
96
+ @property
97
+ def convention_candidates(self) -> tuple[ConventionCandidate, ...]:
98
+ return self._convention_candidates
99
+
100
+ def for_key(self, key: DependencyKey) -> tuple[ServiceRegistration, ...]:
101
+ return self._by_key.get(key, ())
@@ -0,0 +1,38 @@
1
+ """provider-neutral 的业务仓储契约与名义发现边界。"""
2
+
3
+ from typing import Protocol
4
+
5
+ from ..errors import InvalidConventionDefinitionError
6
+ from .convention_contracts import _SERVICE_DEFINITION_ATTRIBUTE, _ServiceDefinition
7
+ from .exposure import _business_interfaces
8
+
9
+
10
+ class RepositoryContract(Protocol):
11
+ """仓储接口的正式角色;实例由选中 Module 的 REQUEST scope 缓存。"""
12
+
13
+
14
+ def _repository_contract(candidate: type[object]) -> type[object] | None:
15
+ if RepositoryContract not in candidate.__mro__:
16
+ return None
17
+ contracts = tuple(
18
+ base
19
+ for base in _business_interfaces(candidate)
20
+ if base is not RepositoryContract and RepositoryContract in base.__mro__
21
+ )
22
+ if len(contracts) != 1:
23
+ raise InvalidConventionDefinitionError(
24
+ symbol=f"{candidate.__module__}.{candidate.__qualname__}",
25
+ reason="repository implementation requires one unambiguous RepositoryContract",
26
+ )
27
+ contract = contracts[0]
28
+ metadata = getattr(candidate, _SERVICE_DEFINITION_ATTRIBUTE, None)
29
+ if (
30
+ isinstance(metadata, _ServiceDefinition)
31
+ and metadata.provides is not None
32
+ and metadata.provides is not contract
33
+ ):
34
+ raise InvalidConventionDefinitionError(
35
+ symbol=f"{candidate.__module__}.{candidate.__qualname__}",
36
+ reason="repository service metadata cannot replace its inherited contract",
37
+ )
38
+ return contract