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,519 @@
1
+ """Pydantic Settings native sources 的受控读取与输入边界验证。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from collections.abc import Iterable, Mapping
7
+ from pathlib import Path
8
+ from typing import Final, NoReturn, cast, get_origin
9
+
10
+ from pydantic_settings import (
11
+ BaseSettings,
12
+ CliSettingsSource,
13
+ InitSettingsSource,
14
+ PydanticBaseSettingsSource,
15
+ SettingsConfigDict,
16
+ )
17
+ from pydantic_settings.sources.utils import parse_env_vars
18
+
19
+ from ..diagnostics import SourceLocation
20
+ from ..errors import ConfigurationSourceError
21
+ from .contracts import (
22
+ ConfigurationSource,
23
+ ConfigurationSourceDiagnostic,
24
+ ConfigurationSourceStatus,
25
+ _ConfigurationDefaultContribution,
26
+ _SourcePayload,
27
+ )
28
+ from .dotenv_source import _SnapshotDotEnvSource
29
+ from .environment_source import _LayeredEnvSource
30
+ from .input_shape import _cli_settings_type, _environment_input_name, _input_sections
31
+ from .values import _capture_input_mapping, canonical_path, canonical_segment, is_supported_scalar
32
+ from .yaml_source import _YamlConfigurationSource
33
+
34
+ _ENVIRONMENT_NESTED_DELIMITER: Final = "__"
35
+
36
+
37
+ class _NativeConfigurationSettings(BaseSettings):
38
+ # Source 只负责读取/解码;最终 schema validation 归具体 Options model。
39
+ model_config = SettingsConfigDict(extra="ignore")
40
+
41
+
42
+ class _SnapshotEnvSource(_LayeredEnvSource):
43
+ def _load_env_vars(self) -> Mapping[str, str | None]:
44
+ # 必须在原生大小写归一化丢失重复 key 前校验;校验和解码共享同一次原始读取。
45
+ snapshot = dict(os.environ)
46
+ inputs: dict[str, str] = {}
47
+ for name, value in snapshot.items():
48
+ key = _environment_input_name(name, self.env_prefix)
49
+ if key in inputs:
50
+ raise ValueError("duplicate canonical environment path")
51
+ inputs[key] = value
52
+ return parse_env_vars(inputs, True, self.env_ignore_empty, self.env_parse_none_str)
53
+
54
+
55
+ class _ConfigurationSourceLoader:
56
+ """拥有一次 build 的 source 选择、动态 settings shape 与读取顺序。"""
57
+
58
+ __slots__ = (
59
+ "_base_path",
60
+ "_cli_args",
61
+ "_dotenv_path",
62
+ "_env_prefix",
63
+ "_environment",
64
+ "_explicit_overrides",
65
+ )
66
+
67
+ def __init__(
68
+ self,
69
+ *,
70
+ environment: str,
71
+ base_path: Path,
72
+ env_prefix: str,
73
+ dotenv_path: Path | None,
74
+ explicit_overrides: Mapping[str, object],
75
+ cli_args: tuple[str, ...],
76
+ ) -> None:
77
+ self._environment = environment
78
+ self._base_path = base_path
79
+ self._env_prefix = env_prefix
80
+ self._dotenv_path = dotenv_path
81
+ self._explicit_overrides = explicit_overrides
82
+ self._cli_args = cli_args
83
+
84
+ def load(
85
+ self,
86
+ defaults: tuple[_ConfigurationDefaultContribution, ...],
87
+ ) -> tuple[tuple[_SourcePayload, ...], tuple[ConfigurationSourceDiagnostic, ...]]:
88
+ failure: ConfigurationSourceError | None = None
89
+ try:
90
+ return self._load(defaults)
91
+ except ConfigurationSourceError as error:
92
+ failure = error
93
+ # 离开 except 后重建安全异常,断开原生 parser 的 input/context/traceback 引用。
94
+ assert failure is not None
95
+ raise ConfigurationSourceError(
96
+ source_id=failure.source_id,
97
+ location=failure.location,
98
+ original_error=(
99
+ FileNotFoundError("configuration file missing")
100
+ if isinstance(failure.original_error, FileNotFoundError)
101
+ else ValueError("configuration source rejected")
102
+ ),
103
+ )
104
+
105
+ def _load(
106
+ self, defaults: tuple[_ConfigurationDefaultContribution, ...]
107
+ ) -> tuple[tuple[_SourcePayload, ...], tuple[ConfigurationSourceDiagnostic, ...]]:
108
+ payloads = list(self._load_module_defaults(defaults))
109
+ diagnostics = [
110
+ _loaded_diagnostic(
111
+ ConfigurationSource.MODULE_DEFAULTS,
112
+ _source_location(ConfigurationSource.MODULE_DEFAULTS),
113
+ )
114
+ ]
115
+
116
+ self._append_optional_yaml(
117
+ payloads,
118
+ diagnostics,
119
+ ConfigurationSource.YAML_BASE,
120
+ self._base_path / "app.yaml",
121
+ )
122
+ self._append_optional_yaml(
123
+ payloads,
124
+ diagnostics,
125
+ ConfigurationSource.YAML_ENVIRONMENT,
126
+ self._base_path / f"app.{self._environment}.yaml",
127
+ )
128
+
129
+ explicit_preview = _normalize_with_boundary(
130
+ ConfigurationSource.EXPLICIT,
131
+ _source_location(ConfigurationSource.EXPLICIT),
132
+ self._explicit_overrides,
133
+ )
134
+ # 原生来源各读取一次。后续 Module 声明只能绑定此次输入快照。
135
+ try:
136
+ environment_source = _SnapshotEnvSource(
137
+ _NativeConfigurationSettings,
138
+ env_prefix=self._env_prefix,
139
+ env_nested_delimiter=_ENVIRONMENT_NESTED_DELIMITER,
140
+ )
141
+ except Exception as original_error:
142
+ _raise_source_error(
143
+ ConfigurationSource.OS_ENVIRONMENT,
144
+ _source_location(ConfigurationSource.OS_ENVIRONMENT),
145
+ original_error,
146
+ )
147
+ dotenv_source, dotenv_diagnostic = self._prepare_dotenv()
148
+ env_names = tuple(environment_source.env_vars)
149
+ dotenv_names = tuple(dotenv_source.env_vars) if dotenv_source is not None else ()
150
+ try:
151
+ sections = _input_sections((*env_names, *dotenv_names), self._env_prefix)
152
+ except (TypeError, ValueError) as original_error:
153
+ _raise_source_error(
154
+ ConfigurationSource.OS_ENVIRONMENT,
155
+ _source_location(ConfigurationSource.OS_ENVIRONMENT),
156
+ original_error,
157
+ )
158
+ settings_type = _build_settings_type(
159
+ payloads=tuple(payloads),
160
+ explicit_preview=explicit_preview,
161
+ known_sections=sections,
162
+ )
163
+ diagnostics.append(dotenv_diagnostic)
164
+ if dotenv_source is not None:
165
+ dotenv_source.settings_cls = settings_type
166
+ _validate_prefixed_names(
167
+ dotenv_names,
168
+ settings_type=settings_type,
169
+ env_prefix=self._env_prefix,
170
+ source_id=ConfigurationSource.DOTENV,
171
+ location=dotenv_diagnostic.location,
172
+ )
173
+ payloads.extend(
174
+ _environment_payloads(
175
+ ConfigurationSource.DOTENV, dotenv_diagnostic.location, dotenv_source
176
+ )
177
+ )
178
+
179
+ environment_payload = self._load_environment(settings_type, environment_source)
180
+ diagnostics.append(
181
+ _loaded_diagnostic(
182
+ ConfigurationSource.OS_ENVIRONMENT,
183
+ _source_location(ConfigurationSource.OS_ENVIRONMENT),
184
+ )
185
+ )
186
+ payloads.extend(environment_payload)
187
+
188
+ cli_location = _source_location(ConfigurationSource.CLI)
189
+ try:
190
+ cli_source: CliSettingsSource[BaseSettings] = CliSettingsSource(
191
+ _cli_settings_type(self._cli_args),
192
+ cli_parse_args=self._cli_args,
193
+ cli_exit_on_error=False,
194
+ )
195
+ except Exception as original_error:
196
+ _raise_source_error(ConfigurationSource.CLI, cli_location, original_error)
197
+ payloads.append(
198
+ _SourcePayload(
199
+ ConfigurationSource.CLI,
200
+ cli_location,
201
+ _call_native_source(ConfigurationSource.CLI, cli_location, cli_source),
202
+ )
203
+ )
204
+ diagnostics.append(_loaded_diagnostic(ConfigurationSource.CLI, cli_location))
205
+ explicit_payload = self._load_explicit(settings_type)
206
+ diagnostics.append(
207
+ _loaded_diagnostic(ConfigurationSource.EXPLICIT, explicit_payload.location)
208
+ )
209
+ payloads.append(explicit_payload)
210
+ return tuple(payloads), tuple(diagnostics)
211
+
212
+ @staticmethod
213
+ def _load_module_defaults(
214
+ defaults: tuple[_ConfigurationDefaultContribution, ...],
215
+ ) -> tuple[_SourcePayload, ...]:
216
+ payloads: list[_SourcePayload] = []
217
+ for contribution in defaults:
218
+ try:
219
+ _validate_immutable_module_default(contribution.values)
220
+ except (TypeError, ValueError) as original_error:
221
+ _raise_source_error(
222
+ ConfigurationSource.MODULE_DEFAULTS,
223
+ contribution.source_location,
224
+ original_error,
225
+ )
226
+ values = _normalize_with_boundary(
227
+ ConfigurationSource.MODULE_DEFAULTS,
228
+ contribution.source_location,
229
+ contribution.values,
230
+ )
231
+ payloads.append(
232
+ _SourcePayload(
233
+ source=ConfigurationSource.MODULE_DEFAULTS,
234
+ location=contribution.source_location,
235
+ values=values,
236
+ )
237
+ )
238
+ return tuple(payloads)
239
+
240
+ @staticmethod
241
+ def _append_optional_yaml(
242
+ payloads: list[_SourcePayload],
243
+ diagnostics: list[ConfigurationSourceDiagnostic],
244
+ source_id: ConfigurationSource,
245
+ path: Path,
246
+ ) -> None:
247
+ payload, diagnostic = _load_optional_yaml(source_id, path)
248
+ diagnostics.append(diagnostic)
249
+ if payload is not None:
250
+ payloads.append(payload)
251
+
252
+ def _prepare_dotenv(self) -> tuple[_SnapshotDotEnvSource | None, ConfigurationSourceDiagnostic]:
253
+ path, required, skipped_reason = self._select_dotenv()
254
+ source_id = ConfigurationSource.DOTENV
255
+ location = _source_location(source_id, file=path)
256
+ if path is None:
257
+ return None, _skipped_diagnostic(source_id, location, skipped_reason or "disabled")
258
+ if not path.exists():
259
+ if required:
260
+ _raise_source_error(source_id, location, FileNotFoundError())
261
+ return None, _skipped_diagnostic(source_id, location, "optional_missing")
262
+ try:
263
+ if not path.is_file():
264
+ raise ValueError("dotenv path must be a file")
265
+ source = _SnapshotDotEnvSource(
266
+ _NativeConfigurationSettings,
267
+ env_file=path,
268
+ env_file_encoding="utf-8",
269
+ dotenv_filtering="match_prefix",
270
+ env_prefix=self._env_prefix,
271
+ env_nested_delimiter=_ENVIRONMENT_NESTED_DELIMITER,
272
+ )
273
+ except ConfigurationSourceError:
274
+ raise
275
+ except Exception as original_error:
276
+ _raise_source_error(source_id, location, original_error)
277
+ return source, _loaded_diagnostic(source_id, location)
278
+
279
+ def _select_dotenv(self) -> tuple[Path | None, bool, str | None]:
280
+ if self._dotenv_path is not None:
281
+ return self._dotenv_path, True, None
282
+ if self._environment.casefold() == "development":
283
+ return self._base_path / ".env", False, None
284
+ return None, False, "disabled_for_environment"
285
+
286
+ def _load_environment(
287
+ self,
288
+ settings_type: type[BaseSettings],
289
+ source: _LayeredEnvSource,
290
+ ) -> tuple[_SourcePayload, ...]:
291
+ source_id = ConfigurationSource.OS_ENVIRONMENT
292
+ location = _source_location(source_id)
293
+ source.settings_cls = settings_type
294
+ _validate_prefixed_names(
295
+ source.env_vars,
296
+ settings_type=settings_type,
297
+ env_prefix=self._env_prefix,
298
+ source_id=source_id,
299
+ location=location,
300
+ )
301
+ return _environment_payloads(source_id, location, source)
302
+
303
+ def _load_explicit(self, settings_type: type[BaseSettings]) -> _SourcePayload:
304
+ source_id = ConfigurationSource.EXPLICIT
305
+ location = _source_location(source_id)
306
+ try:
307
+ source = InitSettingsSource(settings_type, dict(self._explicit_overrides))
308
+ except Exception as original_error:
309
+ _raise_source_error(source_id, location, original_error)
310
+ return _SourcePayload(
311
+ source_id,
312
+ location,
313
+ _call_native_source(source_id, location, source),
314
+ )
315
+
316
+
317
+ def _validate_immutable_module_default(value: object) -> None:
318
+ if isinstance(value, Mapping):
319
+ from types import MappingProxyType
320
+
321
+ if not isinstance(value, MappingProxyType):
322
+ raise TypeError("configuration_defaults mappings must be immutable MappingProxyType")
323
+ for key, item in value.items():
324
+ if not isinstance(key, str):
325
+ raise TypeError("configuration mapping keys must be strings")
326
+ _validate_immutable_module_default(item)
327
+ return
328
+ if isinstance(value, (tuple, frozenset)):
329
+ for item in value:
330
+ _validate_immutable_module_default(item)
331
+ return
332
+ if is_supported_scalar(value):
333
+ return
334
+ raise TypeError(f"configuration_defaults contains mutable or opaque {type(value).__name__}")
335
+
336
+
337
+ def _load_optional_yaml(
338
+ source_id: ConfigurationSource,
339
+ path: Path,
340
+ ) -> tuple[_SourcePayload | None, ConfigurationSourceDiagnostic]:
341
+ location = _source_location(source_id, file=path)
342
+ if not path.exists():
343
+ return None, _skipped_diagnostic(source_id, location, "optional_missing")
344
+ if not path.is_file():
345
+ _raise_source_error(source_id, location, ValueError("configuration path is not a file"))
346
+ try:
347
+ source = _YamlConfigurationSource(_NativeConfigurationSettings, yaml_file=path)
348
+ except Exception as original_error:
349
+ _raise_source_error(source_id, location, original_error)
350
+ values = _call_native_source(source_id, location, source)
351
+ return _SourcePayload(source_id, location, values), _loaded_diagnostic(source_id, location)
352
+
353
+
354
+ def _environment_payloads(
355
+ source_id: ConfigurationSource,
356
+ location: SourceLocation,
357
+ source: _LayeredEnvSource,
358
+ ) -> tuple[_SourcePayload, ...]:
359
+ # 同一次读取按嵌套深度保留覆盖层。原生 parser 仍负责解码;字段与数据键的
360
+ # 身份由晚声明的 Options 决定,不能在原生 deep_update 里提前覆盖或合并。
361
+ _call_native_source(source_id, location, source)
362
+ return tuple(
363
+ _SourcePayload(
364
+ source_id, location, _normalize_with_boundary(source_id, location, values), depth
365
+ )
366
+ for depth, values in sorted(source.input_layers.items())
367
+ )
368
+
369
+
370
+ def _call_native_source(
371
+ source_id: ConfigurationSource,
372
+ location: SourceLocation,
373
+ source: PydanticBaseSettingsSource,
374
+ ) -> dict[str, object]:
375
+ # 第三方动态 Any 只存在于这个边界,返回前递归收窄为配置对象树。
376
+ try:
377
+ raw_values = source()
378
+ except Exception as original_error:
379
+ _raise_source_error(source_id, location, original_error)
380
+ return _normalize_with_boundary(source_id, location, raw_values)
381
+
382
+
383
+ def _build_settings_type(
384
+ *,
385
+ payloads: tuple[_SourcePayload, ...],
386
+ explicit_preview: Mapping[str, object],
387
+ known_sections: frozenset[str],
388
+ ) -> type[BaseSettings]:
389
+ mapping_fields: set[str] = set()
390
+ scalar_fields: set[str] = set()
391
+ for payload in payloads:
392
+ _collect_field_shapes(payload.values, mapping_fields, scalar_fields)
393
+ _collect_field_shapes(explicit_preview, mapping_fields, scalar_fields)
394
+ for section in known_sections:
395
+ top_level = canonical_path(section)[0]
396
+ if top_level not in scalar_fields:
397
+ mapping_fields.add(top_level)
398
+
399
+ annotations: dict[str, object] = {
400
+ name: dict if name in mapping_fields else object
401
+ for name in sorted(mapping_fields | scalar_fields)
402
+ }
403
+ namespace: dict[str, object] = {
404
+ "__annotations__": annotations,
405
+ # 无 prefix dotenv key 由 native source 作为 extra 返回;ignore 只屏蔽这些变量。
406
+ "model_config": SettingsConfigDict(extra="ignore"),
407
+ }
408
+ return type(
409
+ "_ApplicationConfigurationSettings",
410
+ (_NativeConfigurationSettings,),
411
+ namespace,
412
+ )
413
+
414
+
415
+ def _collect_field_shapes(
416
+ values: Mapping[str, object],
417
+ mapping_fields: set[str],
418
+ scalar_fields: set[str],
419
+ ) -> None:
420
+ for key, value in values.items():
421
+ if isinstance(value, Mapping):
422
+ mapping_fields.add(key)
423
+ scalar_fields.discard(key)
424
+ elif key not in mapping_fields:
425
+ scalar_fields.add(key)
426
+
427
+
428
+ def _validate_prefixed_names(
429
+ env_names: Iterable[str],
430
+ *,
431
+ settings_type: type[BaseSettings],
432
+ env_prefix: str,
433
+ source_id: ConfigurationSource,
434
+ location: SourceLocation,
435
+ ) -> None:
436
+ prefix = env_prefix.casefold()
437
+ for raw_name in env_names:
438
+ normalized_name = raw_name.casefold()
439
+ if not normalized_name.startswith(prefix):
440
+ continue
441
+ relative = normalized_name[len(prefix) :]
442
+ raw_parts = relative.split(_ENVIRONMENT_NESTED_DELIMITER)
443
+ try:
444
+ parts = (canonical_segment(raw_parts[0]), *raw_parts[1:])
445
+ # 字段来自同一次环境输入快照;未知 section 在 Module 声明完成后统一校验。
446
+ field = settings_type.model_fields[parts[0]]
447
+ if (
448
+ len(parts) > 1
449
+ and field.annotation is not dict
450
+ and get_origin(field.annotation) is not dict
451
+ ):
452
+ raise ValueError(
453
+ f"configuration key {parts[0]!r} does not accept nested environment keys"
454
+ )
455
+ except (IndexError, TypeError, ValueError) as original_error:
456
+ _raise_source_error(source_id, location, original_error)
457
+
458
+
459
+ def _normalize_with_boundary(
460
+ source_id: ConfigurationSource,
461
+ location: SourceLocation,
462
+ values: object,
463
+ ) -> dict[str, object]:
464
+ try:
465
+ if not isinstance(values, Mapping):
466
+ raise TypeError("configuration source must return a mapping")
467
+ return _capture_input_mapping(cast(Mapping[str, object], values))
468
+ except (TypeError, ValueError) as original_error:
469
+ _raise_source_error(source_id, location, original_error)
470
+
471
+
472
+ def _source_location(
473
+ source: ConfigurationSource,
474
+ *,
475
+ file: Path | None = None,
476
+ ) -> SourceLocation:
477
+ return SourceLocation(
478
+ owner=source.value,
479
+ file=str(file) if file is not None else None,
480
+ line=None,
481
+ symbol=None,
482
+ )
483
+
484
+
485
+ def _loaded_diagnostic(
486
+ source: ConfigurationSource,
487
+ location: SourceLocation,
488
+ ) -> ConfigurationSourceDiagnostic:
489
+ return ConfigurationSourceDiagnostic(
490
+ source=source,
491
+ status=ConfigurationSourceStatus.LOADED,
492
+ location=location,
493
+ )
494
+
495
+
496
+ def _skipped_diagnostic(
497
+ source: ConfigurationSource,
498
+ location: SourceLocation,
499
+ reason: str,
500
+ ) -> ConfigurationSourceDiagnostic:
501
+ return ConfigurationSourceDiagnostic(
502
+ source=source,
503
+ status=ConfigurationSourceStatus.SKIPPED,
504
+ location=location,
505
+ reason=reason,
506
+ )
507
+
508
+
509
+ def _raise_source_error(
510
+ source: ConfigurationSource,
511
+ location: SourceLocation,
512
+ original_error: BaseException,
513
+ ) -> NoReturn:
514
+ error = ConfigurationSourceError(
515
+ source_id=source.value,
516
+ location=location,
517
+ original_error=original_error,
518
+ )
519
+ raise error from original_error