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,24 @@
1
+ """修改业务名称、权限和约束的单一位置。"""
2
+
3
+ from enum import Enum
4
+
5
+ from python_ddd_framework import PermissionName
6
+ from python_ddd_framework.errors import BusinessErrorDefinition
7
+
8
+ MODULE_NAME = "{{ cookiecutter.module_name }}"
9
+ ORDER_CHANGED_MESSAGE = f"{MODULE_NAME}.changed.v1"
10
+ ORDER_SNAPSHOT_MESSAGE = f"{MODULE_NAME}.snapshot.v1"
11
+ TITLE_MAX_LENGTH = 120
12
+ ORDERS_READ = PermissionName(f"{MODULE_NAME}.read")
13
+ ORDERS_WRITE = PermissionName(f"{MODULE_NAME}.write")
14
+ ALREADY_APPROVED = BusinessErrorDefinition(
15
+ f"{MODULE_NAME}.already_approved", "Order is already approved"
16
+ )
17
+ APPROVAL_DISABLED = BusinessErrorDefinition(
18
+ f"{MODULE_NAME}.approval_disabled", "Order approval is disabled"
19
+ )
20
+
21
+
22
+ class OrderStatus(str, Enum):
23
+ PENDING = "pending"
24
+ APPROVED = "approved"
@@ -0,0 +1,7 @@
1
+ """共享层只发布业务常量与权限,不引用基础设施。"""
2
+
3
+ from python_ddd_framework import AppModule
4
+
5
+
6
+ class {{ cookiecutter.class_prefix }}DomainSharedModule(AppModule):
7
+ scan_packages = (__package__,)
@@ -0,0 +1,15 @@
1
+ """权限名称由共享定义拥有;Provider 仅发布定义。"""
2
+
3
+ from python_ddd_framework import (
4
+ PermissionDefinition,
5
+ PermissionDefinitionContext,
6
+ PermissionDefinitionProvider,
7
+ )
8
+
9
+ from .definitions import ORDERS_READ, ORDERS_WRITE
10
+
11
+
12
+ class {{ cookiecutter.class_prefix }}PermissionDefinitionProvider(PermissionDefinitionProvider):
13
+ def define(self, context: PermissionDefinitionContext) -> None:
14
+ context.add(PermissionDefinition(ORDERS_READ, "Read orders"))
15
+ context.add(PermissionDefinition(ORDERS_WRITE, "Create and approve orders"))
@@ -0,0 +1 @@
1
+ """HTTP adapter 只依赖服务契约,不反向导入 application 或 sqlalchemy。"""
@@ -0,0 +1,73 @@
1
+ """原生上传与流响应;业务查询在返回响应前完成,不跨网络发送持有事务。"""
2
+
3
+ import asyncio
4
+ import hashlib
5
+ import logging
6
+ from collections.abc import AsyncIterator, Awaitable, Callable
7
+ from typing import Annotated
8
+ from uuid import UUID
9
+
10
+ from fastapi import File, Form, UploadFile
11
+ from python_ddd_framework.authorization import authorize
12
+ from python_ddd_framework.fastapi import ActionFilterContext, HttpRouter, service_filter
13
+ from starlette.responses import Response, StreamingResponse
14
+
15
+ from ..application_contracts.orders import CreateOrder, {{ cookiecutter.class_prefix }}ApplicationService
16
+ from ..domain_shared.definitions import MODULE_NAME, ORDERS_READ, ORDERS_WRITE
17
+
18
+ router = HttpRouter(use_api_prefix=True, prefix=f"/{MODULE_NAME}/files", permissions=(ORDERS_READ,))
19
+
20
+
21
+ class ExportFilter:
22
+ async def invoke(
23
+ self, context: ActionFilterContext, next_action: Callable[[], Awaitable[object]]
24
+ ) -> object:
25
+ # Filter 的作用域只覆盖已绑定参数到响应准备;不假定流传输已经结束。
26
+ result = await next_action()
27
+ logging.getLogger(__name__).info("File response prepared")
28
+ return result
29
+
30
+
31
+ @router.post("/upload", status_code=201)
32
+ @authorize(ORDERS_WRITE)
33
+ async def upload(
34
+ service: {{ cookiecutter.class_prefix }}ApplicationService,
35
+ title: Annotated[str, Form()],
36
+ file: Annotated[UploadFile, File()],
37
+ ) -> dict[str, object]:
38
+ # File/Form 使用框架读取期限额及原生 parser;UploadFile 资源由 REQUEST 关闭。
39
+ content = await file.read()
40
+ order = await service.create(CreateOrder(title=title))
41
+ return {
42
+ "order": order.model_dump(mode="json"),
43
+ "bytes": len(content),
44
+ "sha256": hashlib.sha256(content).hexdigest(),
45
+ }
46
+
47
+
48
+ @router.get("/{id}/download")
49
+ @service_filter(ExportFilter)
50
+ async def download(id: UUID, service: {{ cookiecutter.class_prefix }}ApplicationService) -> Response:
51
+ order = await service.get(id)
52
+ return Response(
53
+ order.model_dump_json(),
54
+ media_type="application/json",
55
+ headers={"Content-Disposition": f'attachment; filename="{id}.json"'},
56
+ )
57
+
58
+
59
+ @router.get("/export/stream")
60
+ @service_filter(ExportFilter)
61
+ async def export(service: {{ cookiecutter.class_prefix }}ApplicationService) -> StreamingResponse:
62
+ orders = await service.get_list()
63
+
64
+ async def produce() -> AsyncIterator[bytes]:
65
+ try:
66
+ for order in orders:
67
+ yield (order.model_dump_json() + "\n").encode()
68
+ await asyncio.sleep(0)
69
+ finally:
70
+ # 此示例只持有不可变 DTO,无悬挂 Session/文件;扩展外部资源时在此 shield 异步关闭。
71
+ logging.getLogger(__name__).info("Order export producer closed")
72
+
73
+ return StreamingResponse(produce(), media_type="application/x-ndjson")
@@ -0,0 +1,25 @@
1
+ """自动服务暴露与手写路由共用正式认证/REQUEST 管线。"""
2
+
3
+ from dishka import Scope
4
+ from python_ddd_framework import AppModule, ConfigureContext
5
+ from python_ddd_framework.fastapi import (
6
+ FastApiRealtimeModule,
7
+ contributes_routers,
8
+ exposes_application_services,
9
+ )
10
+
11
+ from ..application_contracts.module import {{ cookiecutter.class_prefix }}ApplicationContractsModule
12
+ from .files import ExportFilter
13
+ from .files import router as files_router
14
+ from .realtime import router as realtime_router
15
+
16
+
17
+ @contributes_routers(files_router, realtime_router)
18
+ @exposes_application_services(
19
+ {{ cookiecutter.class_prefix }}ApplicationContractsModule,
20
+ )
21
+ class {{ cookiecutter.class_prefix }}HttpApiModule(AppModule):
22
+ dependencies = ({{ cookiecutter.class_prefix }}ApplicationContractsModule, FastApiRealtimeModule)
23
+
24
+ def configure(self, context: ConfigureContext) -> None:
25
+ context.services.add_scoped(ExportFilter, scope=Scope.REQUEST)
@@ -0,0 +1,36 @@
1
+ """单进程在线通知;连接认证由框架管理,业务授权仍经过服务契约。"""
2
+
3
+ from typing import Annotated
4
+
5
+ from fastapi import APIRouter, Depends
6
+ from pydantic import BaseModel, ConfigDict
7
+ from python_ddd_framework import FrameworkError
8
+ from python_ddd_framework.fastapi import WebSocketConnection, websocket_connection
9
+ from starlette.websockets import WebSocketDisconnect
10
+
11
+ from ..application_contracts.orders import {{ cookiecutter.class_prefix }}ApplicationService
12
+ from ..domain_shared.definitions import MODULE_NAME, ORDER_SNAPSHOT_MESSAGE
13
+
14
+ router = APIRouter()
15
+
16
+
17
+ class RefreshOrders(BaseModel):
18
+ model_config = ConfigDict(frozen=True, extra="forbid")
19
+
20
+
21
+ @router.websocket(f"/ws/{MODULE_NAME}")
22
+ async def orders_socket(
23
+ connection: Annotated[WebSocketConnection, Depends(websocket_connection)],
24
+ ) -> None:
25
+ try:
26
+ while True:
27
+ orders = await connection.invoke({{ cookiecutter.class_prefix }}ApplicationService.get_list)
28
+ await connection.send(
29
+ ORDER_SNAPSHOT_MESSAGE,
30
+ {"orders": [item.model_dump(mode="json") for item in orders]},
31
+ )
32
+ await connection.receive(RefreshOrders)
33
+ except WebSocketDisconnect:
34
+ return
35
+ except FrameworkError as error:
36
+ await connection.send_problem(error)
@@ -0,0 +1 @@
1
+ """数据库实现由 consuming Host 选择。"""
@@ -0,0 +1 @@
1
+ """初始无业务 revision;执行 pddd db revision --module {{ cookiecutter.module_name }} 后人工审查。"""
@@ -0,0 +1,7 @@
1
+ """本模块模型的原生 MetaData owner;具体模型由 Module 的包声明加载。"""
2
+
3
+ from sqlalchemy.orm import DeclarativeBase
4
+
5
+
6
+ class {{ cookiecutter.class_prefix }}Base(DeclarativeBase):
7
+ pass
@@ -0,0 +1,25 @@
1
+ """修改模型后显式生成并审查迁移,不在 Host 启动时建表。"""
2
+
3
+ from uuid import UUID
4
+
5
+ from python_ddd_framework.sqlalchemy import AuditedEntityMixin
6
+ from sqlalchemy import Integer, String, Uuid
7
+ from sqlalchemy.orm import Mapped, mapped_column
8
+
9
+ from ...domain_shared.definitions import MODULE_NAME, TITLE_MAX_LENGTH, OrderStatus
10
+ from . import {{ cookiecutter.class_prefix }}Base
11
+
12
+
13
+ class OrderRow(AuditedEntityMixin, {{ cookiecutter.class_prefix }}Base):
14
+ __tablename__ = MODULE_NAME
15
+ id: Mapped[UUID] = mapped_column(Uuid, primary_key=True)
16
+ title: Mapped[str] = mapped_column(String(TITLE_MAX_LENGTH), nullable=False)
17
+ status: Mapped[str] = mapped_column(
18
+ String(max(len(item.value) for item in OrderStatus)), nullable=False
19
+ )
20
+ version: Mapped[int] = mapped_column(Integer, nullable=False)
21
+ # SQLAlchemy 以行版本检查并发;聚合暂存版本与数据库提交版本保持一致。
22
+ __mapper_args__ = { # noqa: RUF012 -- SQLAlchemy 原生声明属性,基类不允许 ClassVar。
23
+ "version_id_col": version,
24
+ "version_id_generator": False,
25
+ }
@@ -0,0 +1,26 @@
1
+ """provider 依赖领域契约;Host 显式选择此实现。"""
2
+
3
+ from python_ddd_framework import AppModule
4
+ from python_ddd_framework.sqlalchemy import (
5
+ SqlAlchemyModelRegistration,
6
+ SqlAlchemyPersistenceModule,
7
+ contributes_sqlalchemy_models,
8
+ )
9
+
10
+ from ..domain.module import {{ cookiecutter.class_prefix }}DomainModule
11
+ from ..domain_shared.definitions import MODULE_NAME
12
+ from .models import {{ cookiecutter.class_prefix }}Base
13
+
14
+
15
+ @contributes_sqlalchemy_models(
16
+ SqlAlchemyModelRegistration.from_package(
17
+ models_package=__package__ + ".models",
18
+ metadata={{ cookiecutter.class_prefix }}Base.metadata,
19
+ migrations_package=__package__ + ".migrations",
20
+ branch_label=MODULE_NAME,
21
+ )
22
+ )
23
+ class {{ cookiecutter.class_prefix }}SqlAlchemyModule(AppModule):
24
+ dependencies = ({{ cookiecutter.class_prefix }}DomainModule, SqlAlchemyPersistenceModule)
25
+
26
+ scan_packages = (__package__ + ".repositories",)
@@ -0,0 +1,49 @@
1
+ """仓储参与当前 UoW,成功暂存后由 operation 收集聚合事件。"""
2
+
3
+ from uuid import UUID
4
+
5
+ from python_ddd_framework import OptimisticConcurrencyError
6
+ from python_ddd_framework.sqlalchemy import SqlAlchemySessionProvider
7
+ from sqlalchemy import select
8
+
9
+ from ...domain.orders import Order, OrderTitle
10
+ from ...domain.repository import OrderRepository
11
+ from ...domain_shared.definitions import OrderStatus
12
+ from ..models.orders import OrderRow
13
+
14
+
15
+ class SqlAlchemyOrderRepository(OrderRepository):
16
+ def __init__(self, provider: SqlAlchemySessionProvider) -> None:
17
+ self._provider = provider
18
+
19
+ async def get(self, id: UUID) -> Order | None:
20
+ async with self._provider.operation("get"):
21
+ row = await (await self._provider.get_session()).get(OrderRow, id)
22
+ return None if row is None else _order(row)
23
+
24
+ async def get_list(self) -> tuple[Order, ...]:
25
+ async with self._provider.operation("get_list"):
26
+ rows = await (await self._provider.get_session()).scalars(
27
+ select(OrderRow).order_by(OrderRow.created_at, OrderRow.id)
28
+ )
29
+ return tuple(_order(row) for row in rows)
30
+
31
+ async def save(self, order: Order) -> None:
32
+ async with self._provider.operation("save", aggregate=order):
33
+ session = await self._provider.get_session()
34
+ row = await session.get(OrderRow, order.id)
35
+ if row is None:
36
+ if order.version:
37
+ raise OptimisticConcurrencyError
38
+ row = OrderRow(id=order.id)
39
+ session.add(row)
40
+ elif row.version != order.version:
41
+ raise OptimisticConcurrencyError
42
+ order.stage_next_version(order.version)
43
+ row.title, row.status, row.version = order.title, order.status.value, order.version
44
+
45
+
46
+ def _order(row: OrderRow) -> Order:
47
+ return Order(
48
+ row.id, OrderTitle(value=row.title), status=OrderStatus(row.status), version=row.version
49
+ )
@@ -0,0 +1,18 @@
1
+ """模块内领域验收,不随生产 wheel 打包或参与服务扫描。"""
2
+
3
+ from uuid import uuid4
4
+
5
+ import pytest
6
+ from python_ddd_framework.errors import BusinessError
7
+
8
+ from modules.{{ cookiecutter.module_name }}.domain.orders import Order, OrderTitle
9
+ from modules.{{ cookiecutter.module_name }}.domain_shared.definitions import OrderStatus
10
+
11
+
12
+ def test_order_approval_rejects_repeat():
13
+ order = Order.create(uuid4(), OrderTitle(value="Example order"))
14
+ order.approve()
15
+ assert order.status is OrderStatus.APPROVED
16
+ assert len(order.release_local_events()) == 2
17
+ with pytest.raises(BusinessError):
18
+ order.approve()
@@ -0,0 +1,6 @@
1
+ {
2
+ "project_name": "application", "package_name": "application", "class_prefix": "Application",
3
+ "framework_version": "", "framework_source": "", "python_version": "", "build_version": "",
4
+ "database_password": "", "admin_password": "", "jwt_key": "",
5
+ "host_environment_variable": ""
6
+ }
@@ -0,0 +1,9 @@
1
+ .venv
2
+ .git
3
+ .env
4
+ .env.*
5
+ app.*.yaml
6
+ tests
7
+ __pycache__
8
+ .pytest_cache
9
+ dist
@@ -0,0 +1,6 @@
1
+ .venv/
2
+ __pycache__/
3
+ .pytest_cache/
4
+ .env
5
+ app.production.yaml
6
+ dist/
@@ -0,0 +1,38 @@
1
+ # Project development guidance
2
+
3
+ These instructions apply to this application and its business modules. Follow the user's task scope and any more specific instructions in the affected directory.
4
+
5
+ ## Start with the affected capability
6
+
7
+ 1. Check the branch, worktree, requested outcome, and existing changes before editing. Analysis and review requests are read-only unless implementation is requested.
8
+ 2. Use [README](README.md) for setup and commands, [architecture](docs/architecture.md) for ownership and dependencies, and the relevant section of [development](docs/development.md) for framework usage.
9
+ 3. Before changing a business module, read its `src/modules/<name>/README.md`, then inspect the affected source and direct callers. Expand the investigation only when those facts reveal another affected boundary.
10
+ 4. Read versions and dependencies from `pyproject.toml`, exact resolutions from `uv.lock`, and commands from `uv run pddd --help`. Verify behavior against the installed framework version when the application documentation is insufficient.
11
+
12
+ ## Preserve ownership and contracts
13
+
14
+ - The Host composes modules, selects providers, and supplies configuration. Keep business state and rules in their owning modules.
15
+ - Domain and application contracts stay independent of Host, HTTP, and persistence implementations. Cross-module calls use explicitly declared dependencies and public contracts; do not access another module's implementation, ORM models, or tables directly.
16
+ - Use framework application services, repositories, Options, permissions, units of work, events, jobs, and lifecycle hooks before adding custom infrastructure. Do not introduce a parallel DI container, service locator, transaction manager, or wrapper without a missing business capability.
17
+ - Keep public contracts, mutable state, orchestration, and external I/O under clear owners. Split for independent responsibilities, not a fixed line count. Avoid generic `common`, `utils`, or `helpers` collections and speculative abstractions.
18
+ - Resolve conflicting requirements, documented contracts, and implementation before changing the affected boundary. Describe the conflict to the user; do not rewrite documentation to justify an unapproved design.
19
+ - Confirm changes to dependencies, public contracts, persistent data, external protocols, or deployment infrastructure when they extend beyond the requested task. Internal simplification does not authorize breaking consumers or stored data.
20
+
21
+ ## Use the framework safely
22
+
23
+ - Invoke application services through their injected public contracts or the application's public invocation methods. Manual construction and self-calls do not create a new managed invocation.
24
+ - Keep transactions short. Never carry a Session, unit of work, or request scope into an ordinary child task, external thread, network wait, or streamed response.
25
+ - Keep aggregate rules and optimistic concurrency checks intact. Use repository operations to stage changes and collect events; do not publish the same aggregate events twice.
26
+ - Review generated migrations. Do not edit applied revisions, bypass migration guards, or treat Host startup as migration or seeding.
27
+ - Keep background side effects idempotent. Local events and WebSocket messages do not provide durable distributed delivery; distributed locks do not replace database concurrency or idempotency.
28
+ - Validate external inputs, propagate meaningful failures, and preserve cancellation and cleanup. Add defaults, retries, or fallbacks only for an identified requirement.
29
+ - Use typed public APIs and native framework extension points. Keep dynamic data narrowing at external boundaries; do not use `Any`, unchecked casts, or broad exception handlers to hide contract mistakes.
30
+
31
+ ## Verify and maintain
32
+
33
+ - Preserve unrelated changes. Do not edit the installed framework, generated dependency files by hand, or another repository to make an application change pass.
34
+ - Use the smallest relevant existing checks, and add focused regression coverage when the requested behavior needs it. Read fixtures before running tests that start infrastructure. See [verification](docs/development.md#verification) for available commands.
35
+ - Distinguish source checks, unit tests, real infrastructure tests, and distribution validation. Report actual results and anything not verified; never claim success from a mock or an unexecuted command.
36
+ - Update the owning module README when business rules or public behavior change. Update architecture for dependency or ownership changes, development for usage changes, and README for setup or operations changes. Replace outdated statements rather than appending a work log.
37
+ - Documentation guides development; it is not an executable architecture gate. Use type checks and appropriate boundary tests when the project has them.
38
+ - Commit or push only when requested. Review the final diff, choose owned paths explicitly, and exclude secrets, caches, temporary artifacts, and unrelated changes.
@@ -0,0 +1,22 @@
1
+ # syntax=docker/dockerfile:1
2
+ FROM debian:bookworm-slim AS build
3
+ COPY --from=ghcr.io/astral-sh/uv:{{ cookiecutter.build_version }} /uv /usr/local/bin/uv
4
+ RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates build-essential
5
+ ENV UV_PYTHON_INSTALL_DIR=/opt/python UV_LINK_MODE=copy UV_COMPILE_BYTECODE=1
6
+ WORKDIR /app
7
+ COPY . .
8
+ # Python 版本只从项目 .python-version 读取;PyPI 或 vendor wheel 均由 uv.lock 固定。
9
+ RUN uv python install && uv sync --locked --no-default-groups --no-editable
10
+
11
+ FROM debian:bookworm-slim AS runtime
12
+ RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates libstdc++6 && useradd --create-home app
13
+ COPY --from=build /opt/python /opt/python
14
+ COPY --from=build /app/.venv /app/.venv
15
+ COPY --from=build /app/pyproject.toml /app/
16
+ WORKDIR /app
17
+ ENV PATH="/app/.venv/bin:$PATH" {{ cookiecutter.host_environment_variable }}=production PYTHONUNBUFFERED=1
18
+ RUN mkdir /app/.python-ddd-framework && chown app:app /app/.python-ddd-framework
19
+ USER app
20
+ EXPOSE 8000
21
+ # 独立 access handler 会绕过安全 formatter;请求诊断由框架审计和追踪负责。
22
+ CMD ["python", "-m", "host.main"]
@@ -0,0 +1,130 @@
1
+ # {{ cookiecutter.project_name }}
2
+
3
+ A modular Python application generated with Python DDD Framework {{ cookiecutter.framework_version }}. The initial project contains a Host; add business modules with `pddd add module` and adapt their examples to your requirements.
4
+
5
+ | Document | Purpose |
6
+ | --- | --- |
7
+ | [AGENTS.md](AGENTS.md) | Development instructions and reading order for Codex and other contributors. |
8
+ | [Architecture](docs/architecture.md) | Ownership, dependency direction, transaction boundaries, and lifecycle guarantees. |
9
+ | [Development guide](docs/development.md) | Recipes for services, permissions, persistence, configuration, events, and background work. |
10
+
11
+ Each generated business module also has its own README under `src/modules/<name>/`. This README owns application setup and operations.
12
+
13
+ ## Prerequisites
14
+
15
+ Use the Python version declared by this project's metadata, uv, and a running Docker installation with Compose. `pddd new` has already synchronized dependencies. On an existing checkout, run `uv sync --locked`.
16
+
17
+ Run project commands from this directory with `uv run pddd` so they use the project's framework version. Use `uv run pddd` or a subcommand's `--help` to see available commands and options.
18
+
19
+ ## Prepare local development
20
+
21
+ Review `app.development.yaml`, then start the local infrastructure:
22
+
23
+ ```sh
24
+ uv run pddd dev-init
25
+ ```
26
+
27
+ This starts or reuses PostgreSQL and Redis from the Host's development configuration and preserves data. It does not start the application, migrate, seed, or create an `.env` file. The bundled Compose setup requires local URLs with explicit ports; its Redis service does not configure authentication or TLS.
28
+
29
+ To add the example business module:
30
+
31
+ ```sh
32
+ uv run pddd add module orders
33
+ ```
34
+
35
+ Read `src/modules/orders/README.md` before changing the sample. The project is also usable as a pure Host without this step.
36
+
37
+ ## Initialize the database
38
+
39
+ Apply the migrations for the providers selected by the generated Host:
40
+
41
+ ```sh
42
+ uv run pddd db upgrade --module identity
43
+ uv run pddd db upgrade --module settings
44
+ uv run pddd db upgrade --module auditing
45
+ uv run pddd db upgrade --module background_jobs
46
+ uv run pddd db seed --module identity
47
+ ```
48
+
49
+ If you added `orders`, generate its first revision:
50
+
51
+ ```sh
52
+ uv run pddd db revision --module orders
53
+ ```
54
+
55
+ Review the generated file in `src/modules/orders/sqlalchemy/migrations/`, then apply and seed it:
56
+
57
+ ```sh
58
+ uv run pddd db upgrade --module orders
59
+ uv run pddd db status --module orders
60
+ uv run pddd db seed --module orders
61
+ ```
62
+
63
+ All database operations select an explicit module. Upgrade prerequisite owners explicitly; commands do not migrate unrelated modules. Host startup does not migrate or seed. Database commands do not start workers or hosted services. See [persistence and migrations](docs/development.md#persistence-and-migrations) before changing models or existing data.
64
+
65
+ ## Run and inspect
66
+
67
+ ```sh
68
+ uv run pddd dev
69
+ ```
70
+
71
+ Open **http://127.0.0.1:8000/docs**. Call `/api/auth/login` using the generated `identity.seed_admin_username` and `identity.seed_admin_password` from `app.development.yaml`, then enter the access token in **Authorize**. Local credentials must not be reused for production.
72
+
73
+ With the `orders` example installed, `/api/orders` supports create/query, `/api/orders/{id}/approve` approves an order, and `/api/orders/{id}/queue-approval` enqueues approval. Read the module README for its rules and OpenAPI for request schemas. A stale version produces a conflict response.
74
+
75
+ `http.api_prefix` controls business HTTP addresses and defaults to `/api`; `/ws`, `/health/live`, `/health/ready`, and `/docs` retain independent paths. Readiness reports Application lifecycle state, not continuous infrastructure health.
76
+
77
+ To inspect module composition, service registrations, and redacted configuration inputs:
78
+
79
+ ```sh
80
+ uv run pddd inspect --environment development
81
+ ```
82
+
83
+ Inspection composes and closes a new Application without starting it. It does not query an existing process, and its input snapshot is not the final Options view. Configuration, workers, jobs, real-time communication, and logging are covered in the [development guide](docs/development.md).
84
+
85
+ ## Test and package
86
+
87
+ ```sh
88
+ uv run pytest
89
+ uv build --no-sources
90
+ ```
91
+
92
+ The application tests include real Host infrastructure and require Docker. Module domain tests can be run separately; see [verification](docs/development.md#verification). Module tests are excluded from production wheels and service discovery.
93
+
94
+ ## Deploy
95
+
96
+ The deployment supplies `app.production.yaml`, following the development file's key structure with independent database, Redis, JWT, and administrator credentials. Set the production logging environment and format. Keep the file out of Git and the image; production Compose mounts it read-only at `/app/app.production.yaml` for both web and migration services.
97
+
98
+ Build an image, validate it, and set `APP_IMAGE` to its immutable digest. In PowerShell, run the following for each required module, replacing the module alias and digest:
99
+
100
+ ```powershell
101
+ docker build -t {{ cookiecutter.project_name }}:candidate .
102
+ $env:APP_IMAGE = "<verified-image>@sha256:<digest>"
103
+ $env:MODULE = "orders"
104
+ docker compose -f compose.production.yaml run --rm migrate
105
+ ```
106
+
107
+ Complete all required module migrations in dependency order and run initial seeds explicitly with `pddd db seed --module <alias> --environment production` in the same application environment. Then start the web service:
108
+
109
+ ```sh
110
+ docker compose -f compose.production.yaml up -d web
111
+ ```
112
+
113
+ The image starts `python -m host.main`, using the public framework server entry point without reload. Web, migration, and background processes use the same application image. Shutdown waits for actual cleanup. `APP_IMAGE`, `MODULE`, and `WEB_PORT` are deployment controls; they do not replace the application's configuration sources.
114
+
115
+ The base framework dependency includes runtime capabilities. Production does not require `developer-kit`; generation tools, tests, and development tools are excluded from the final image. Configure TLS, secrets, external health monitoring, capacity, and database recovery for the deployment's actual requirements.
116
+
117
+ ## Upgrade the framework
118
+
119
+ Read the target framework release's migration instructions. Pin `python-ddd-framework` in runtime dependencies and `python-ddd-framework[developer-kit]` in the development group to the same target version. Preserve the application's own dependencies and source overrides.
120
+
121
+ For a first move from a local wheel or old Git source to PyPI, remove only the framework entry in `[tool.uv.sources]`; retain other dependencies and vendor files. Then run:
122
+
123
+ ```sh
124
+ uv lock --upgrade-package python-ddd-framework
125
+ uv sync --locked
126
+ uv run pytest
127
+ uv build --no-sources
128
+ ```
129
+
130
+ A framework upgrade does not regenerate or overwrite application source, migrations, or these documents. A global CLI upgrade also does not update this project. Apply required adaptations and update the relevant application and module documentation deliberately.
@@ -0,0 +1,30 @@
1
+ # pddd new 生成的随机凭据仅供本机开发;共享环境和生产必须使用独立凭据。
2
+ development:
3
+ compose:
4
+ postgres_image: postgres:18-bookworm
5
+ redis_image: redis:7-bookworm
6
+ connection_strings:
7
+ default: "postgresql+asyncpg://{{ cookiecutter.package_name }}:{{ cookiecutter.database_password }}@127.0.0.1:5432/{{ cookiecutter.package_name }}"
8
+ identity:
9
+ issuer: "{{ cookiecutter.project_name }}"
10
+ audience: "{{ cookiecutter.project_name }}"
11
+ active_kid: development
12
+ keys:
13
+ development: "{{ cookiecutter.jwt_key }}"
14
+ seed_admin_username: admin
15
+ seed_admin_password: "{{ cookiecutter.admin_password }}"
16
+ redis:
17
+ url: redis://127.0.0.1:6379/0
18
+ key_prefix: "{{ cookiecutter.project_name }}"
19
+ logging:
20
+ application: "{{ cookiecutter.project_name }}"
21
+ environment: development
22
+ format: text
23
+ level: INFO
24
+ tracing:
25
+ service_name: "{{ cookiecutter.project_name }}"
26
+ export_enabled: false
27
+ pgqueuer_background_jobs:
28
+ connection_name: default
29
+ http:
30
+ api_prefix: /api
@@ -0,0 +1,24 @@
1
+ services:
2
+ postgres:
3
+ image: ${POSTGRES_IMAGE:?Run pddd dev-init to load development configuration}
4
+ environment:
5
+ POSTGRES_DB: ${POSTGRES_DB}
6
+ POSTGRES_USER: ${POSTGRES_USER}
7
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
8
+ ports: ["127.0.0.1:${POSTGRES_PORT}:5432"]
9
+ healthcheck:
10
+ test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
11
+ interval: 2s
12
+ timeout: 2s
13
+ retries: 20
14
+ volumes: ["postgres-data:/var/lib/postgresql"]
15
+ redis:
16
+ image: ${REDIS_IMAGE:?Run pddd dev-init to load development configuration}
17
+ ports: ["127.0.0.1:${REDIS_PORT}:6379"]
18
+ healthcheck:
19
+ test: ["CMD", "redis-cli", "ping"]
20
+ interval: 2s
21
+ timeout: 2s
22
+ retries: 20
23
+ volumes:
24
+ postgres-data:
@@ -0,0 +1,20 @@
1
+ # APP_IMAGE 使用验收通过的同一 digest;显式选择模块迁移,成功后启动 Host。
2
+ x-configuration: &configuration
3
+ type: bind
4
+ source: ./app.production.yaml
5
+ target: /app/app.production.yaml
6
+ read_only: true
7
+ bind:
8
+ create_host_path: false
9
+ services:
10
+ migrate:
11
+ image: ${APP_IMAGE:?Provide the verified application image digest}
12
+ volumes: [*configuration]
13
+ command: ["pddd", "db", "--environment", "production", "upgrade", "--module", "${MODULE:?Select the migration module alias}"]
14
+ profiles: ["operations"]
15
+ web:
16
+ image: ${APP_IMAGE:?Provide the verified application image digest}
17
+ volumes: [*configuration]
18
+ ports: ["${WEB_PORT:-8000}:8000"]
19
+ stop_grace_period: 45s
20
+ restart: unless-stopped