pz-cli 5.0.0__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 (399) hide show
  1. pz_cli-5.0.0.dist-info/METADATA +51 -0
  2. pz_cli-5.0.0.dist-info/RECORD +399 -0
  3. pz_cli-5.0.0.dist-info/WHEEL +4 -0
  4. pz_cli-5.0.0.dist-info/entry_points.txt +5 -0
  5. pz_core/__init__.py +64 -0
  6. pz_core/__main__.py +7 -0
  7. pz_core/app.py +45 -0
  8. pz_core/assembly/__init__.py +17 -0
  9. pz_core/assembly/app_discovery.py +1240 -0
  10. pz_core/assembly/apps/__init__.py +25 -0
  11. pz_core/assembly/apps/compiler.py +824 -0
  12. pz_core/assembly/apps/providers.py +188 -0
  13. pz_core/assembly/cli/__init__.py +9 -0
  14. pz_core/assembly/cli/compiler.py +166 -0
  15. pz_core/assembly/diagnostics/__init__.py +21 -0
  16. pz_core/assembly/diagnostics/checks.py +257 -0
  17. pz_core/assembly/diagnostics/dev_sources.py +569 -0
  18. pz_core/assembly/events/__init__.py +5 -0
  19. pz_core/assembly/events/compiler.py +219 -0
  20. pz_core/assembly/extensions/__init__.py +5 -0
  21. pz_core/assembly/extensions/compiler.py +696 -0
  22. pz_core/assembly/infra/__init__.py +6 -0
  23. pz_core/assembly/infra/compiler.py +272 -0
  24. pz_core/assembly/lifecycle/__init__.py +5 -0
  25. pz_core/assembly/lifecycle/compiler.py +95 -0
  26. pz_core/assembly/middlewares/__init__.py +19 -0
  27. pz_core/assembly/middlewares/compiler.py +508 -0
  28. pz_core/assembly/project/__init__.py +7 -0
  29. pz_core/assembly/project/compiler.py +9 -0
  30. pz_core/assembly/project/context.py +17 -0
  31. pz_core/assembly/project/discovery.py +227 -0
  32. pz_core/assembly/resources/__init__.py +19 -0
  33. pz_core/assembly/resources/compiler.py +310 -0
  34. pz_core/assembly/resources/settings.py +129 -0
  35. pz_core/assembly/routes/__init__.py +7 -0
  36. pz_core/assembly/routes/compiler.py +400 -0
  37. pz_core/assembly/runtime_manifests/__init__.py +13 -0
  38. pz_core/assembly/runtime_manifests/compiler.py +159 -0
  39. pz_core/assembly/settings/__init__.py +17 -0
  40. pz_core/assembly/settings/compiler.py +786 -0
  41. pz_core/assembly/settings/host_project_overrides.py +82 -0
  42. pz_core/assembly/settings/host_runtime_endpoint.py +207 -0
  43. pz_core/authoring/__init__.py +79 -0
  44. pz_core/authoring/app_init_service.py +946 -0
  45. pz_core/authoring/app_versions.py +814 -0
  46. pz_core/authoring/cli.py +192 -0
  47. pz_core/authoring/commands.py +1810 -0
  48. pz_core/authoring/conflicts.py +180 -0
  49. pz_core/authoring/dev_bindings.py +916 -0
  50. pz_core/authoring/dev_git.py +1087 -0
  51. pz_core/authoring/dev_import.py +1061 -0
  52. pz_core/authoring/dev_manifest.py +522 -0
  53. pz_core/authoring/dev_sources.py +1638 -0
  54. pz_core/authoring/env_cli.py +454 -0
  55. pz_core/authoring/files.py +109 -0
  56. pz_core/authoring/ide_projection.py +1129 -0
  57. pz_core/authoring/naming.py +58 -0
  58. pz_core/authoring/official_dev_sources.py +432 -0
  59. pz_core/authoring/official_sources.json +156 -0
  60. pz_core/authoring/orchestration_service.py +558 -0
  61. pz_core/authoring/project_default.jsonc +53 -0
  62. pz_core/authoring/project_defaults.py +88 -0
  63. pz_core/authoring/project_metadata_service.py +199 -0
  64. pz_core/authoring/project_settings_checks.py +51 -0
  65. pz_core/authoring/project_settings_cli.py +299 -0
  66. pz_core/authoring/project_settings_service.py +1700 -0
  67. pz_core/authoring/project_state.py +399 -0
  68. pz_core/authoring/publish_git.py +423 -0
  69. pz_core/authoring/publish_lock.py +188 -0
  70. pz_core/authoring/publish_source.py +195 -0
  71. pz_core/authoring/release.py +1382 -0
  72. pz_core/authoring/repo_keys.py +29 -0
  73. pz_core/authoring/root_cli.py +28 -0
  74. pz_core/authoring/scaffold_service.py +325 -0
  75. pz_core/authoring/sync_service.py +787 -0
  76. pz_core/authoring/templates/app/__init__.py +1 -0
  77. pz_core/authoring/templates/app/api.py +25 -0
  78. pz_core/authoring/templates/app/apps.py +31 -0
  79. pz_core/authoring/templates/app/auth_package_init.py +14 -0
  80. pz_core/authoring/templates/app/auth_readme.py +29 -0
  81. pz_core/authoring/templates/app/configs_package_init.py +14 -0
  82. pz_core/authoring/templates/app/default_env.py +18 -0
  83. pz_core/authoring/templates/app/default_jsonc.py +25 -0
  84. pz_core/authoring/templates/app/locales_readme.py +25 -0
  85. pz_core/authoring/templates/app/migrations_readme.py +19 -0
  86. pz_core/authoring/templates/app/models.py +14 -0
  87. pz_core/authoring/templates/app/package_init.py +8 -0
  88. pz_core/authoring/templates/app/pages_base.py +161 -0
  89. pz_core/authoring/templates/app/pages_readme.py +25 -0
  90. pz_core/authoring/templates/app/public.py +31 -0
  91. pz_core/authoring/templates/app/pyproject.py +77 -0
  92. pz_core/authoring/templates/app/readme.py +74 -0
  93. pz_core/authoring/templates/app/settings.py +46 -0
  94. pz_core/authoring/templates/app/static_app_js.py +24 -0
  95. pz_core/authoring/templates/app/static_readme.py +31 -0
  96. pz_core/authoring/templates/app/template_config_page.py +23 -0
  97. pz_core/authoring/templates/app/template_index_html.py +28 -0
  98. pz_core/authoring/templates/app/templates_readme.py +32 -0
  99. pz_core/authoring/templates/app/urls.py +36 -0
  100. pz_core/authoring/templates/app/views.py +36 -0
  101. pz_core/authoring/templates/project/__init__.py +1 -0
  102. pz_core/authoring/templates/project/agents.py +51 -0
  103. pz_core/authoring/templates/project/apps_container_readme.py +37 -0
  104. pz_core/authoring/templates/project/main_py.py +59 -0
  105. pz_core/authoring/templates/project/project_env.py +19 -0
  106. pz_core/authoring/templates/project/pyproject.py +26 -0
  107. pz_core/authoring/templates/project/pz_env_env.py +65 -0
  108. pz_core/authoring/templates/project/pz_env_jsonc.py +69 -0
  109. pz_core/authoring/templates/project/pz_jsonc.py +12 -0
  110. pz_core/authoring/templates/project/readme.py +94 -0
  111. pz_core/authoring/templates/project/root_gitkeep.py +8 -0
  112. pz_core/authoring/templates/shared/__init__.py +5 -0
  113. pz_core/authoring/templates/shared/text.py +10 -0
  114. pz_core/authoring/venv_service.py +87 -0
  115. pz_core/authoring/workspace.py +167 -0
  116. pz_core/cli.py +291 -0
  117. pz_core/cli_options.py +74 -0
  118. pz_core/core/__init__.py +3 -0
  119. pz_core/core/errors/__init__.py +5 -0
  120. pz_core/core/errors/models.py +117 -0
  121. pz_core/core/events/__init__.py +5 -0
  122. pz_core/core/events/registry.py +54 -0
  123. pz_core/core/extensions/__init__.py +51 -0
  124. pz_core/core/extensions/registry.py +790 -0
  125. pz_core/core/health/__init__.py +6 -0
  126. pz_core/core/health/contracts.py +10 -0
  127. pz_core/core/middlewares/__init__.py +6 -0
  128. pz_core/core/middlewares/models.py +24 -0
  129. pz_core/core/routing/__init__.py +5 -0
  130. pz_core/core/routing/models.py +21 -0
  131. pz_core/core/settings_contracts.py +25 -0
  132. pz_core/docs/README.md +20 -0
  133. pz_core/docs/distribution-README.md +438 -0
  134. pz_core/docs/pz/345/221/275/344/273/244/346/200/273/350/247/210.md +1011 -0
  135. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/00-pz/346/236/266/346/236/204/346/200/273/347/272/262.md +240 -0
  136. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/01-/347/233/256/345/275/225/344/270/216/345/210/206/345/261/202/350/247/204/350/214/203.md +216 -0
  137. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/02-/345/214/205/344/270/216/345/210/206/345/217/221/350/247/204/350/214/203.md +403 -0
  138. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/03-App/346/234/200/345/260/217/345/255/220/345/214/205/350/247/204/350/214/203.md +571 -0
  139. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/04-Settings/344/270/216/351/205/215/347/275/256/350/247/204/350/214/203.md +1177 -0
  140. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/05-/350/265/204/346/272/220/344/270/216/350/277/220/350/241/214/347/233/256/345/275/225/350/247/204/350/214/203.md +575 -0
  141. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/06-/346/225/260/346/215/256/345/272/223/344/270/216/345/217/257/351/200/211/345/237/272/347/241/200/350/256/276/346/226/275/350/247/204/350/214/203.md +499 -0
  142. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/07-App/346/233/277/346/215/242/350/247/204/350/214/203.md +206 -0
  143. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/08-/345/271/263/345/217/260/347/272/247App/344/270/216/350/277/220/350/241/214/346/227/266/345/215/225/344/276/213/350/247/204/350/214/203.md +445 -0
  144. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/09-CLI/344/270/216Worker/345/205/245/345/217/243/350/247/204/350/214/203.md +950 -0
  145. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/10-/346/265/213/350/257/225/344/270/216/345/245/221/347/272/246/350/247/204/350/214/203.md +1001 -0
  146. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/11-/344/273/223/345/272/223/347/234/237/347/233/270/346/272/220/345/234/260/345/233/276.md +102 -0
  147. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/12-/346/236/204/345/273/272/344/270/216/345/217/221/345/270/203/346/243/200/346/237/245/346/270/205/345/215/225.md +171 -0
  148. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/13-/346/255/243/345/274/217Facade/346/200/273/350/241/250.md +402 -0
  149. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/14-/345/210/206/345/261/202/344/270/216/346/211/251/345/261/225/350/247/204/350/214/203.md +550 -0
  150. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/15-/345/255/220/345/214/205/345/274/200/345/217/221/344/275/277/347/224/250/346/211/213/345/206/214.md +1004 -0
  151. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/16-/346/211/251/345/261/225/347/202/271/346/255/243/345/274/217/345/215/217/350/256/256/346/270/205/345/215/225.md +352 -0
  152. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/17-pz.protocols.v1/346/255/243/345/274/217/345/215/217/350/256/256.md +266 -0
  153. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/18-/346/211/251/345/261/225/347/202/271/345/206/262/347/252/201/350/243/201/345/206/263/346/200/273/350/241/250.md +131 -0
  154. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/19-/351/224/231/350/257/257/347/240/201/344/270/216/350/257/212/346/226/255/345/215/217/350/256/256.md +176 -0
  155. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/20-/347/247/201/346/234/211/345/214/205/344/273/223/344/270/216/347/273/237/344/270/200/350/256/244/350/257/201/351/203/250/347/275/262/350/247/204/350/214/203.md +243 -0
  156. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/21-contrib/347/272/246/345/256/232/344/270/216Provider/346/216/245/345/205/245/350/247/204/350/214/203.md +365 -0
  157. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/22-/345/244/232/345/233/275/350/257/255/350/250/200/350/265/204/346/272/220/346/262/273/347/220/206/344/270/216/346/214/211/351/234/200/345/212/240/350/275/275/350/247/204/350/214/203.md +912 -0
  158. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/23-/351/246/226/345/261/217/345/261/225/347/244/272/344/270/216/351/246/226/345/270/247/347/250/263/345/256/232/350/247/204/350/214/203.md +269 -0
  159. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/24-pz/345/220/257/345/212/250/344/270/216/350/277/220/350/241/214/346/227/266/347/274/226/350/257/221/346/265/201/347/250/213.md +245 -0
  160. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/25-/351/235/242/345/220/221/345/257/271/350/261/241/345/274/200/345/217/221/350/247/204/350/214/203.md +300 -0
  161. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/README.md +98 -0
  162. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/archive//344/270/255/351/227/264/346/200/201/00-legacy_core/346/240/207/345/207/206pip/345/214/205/345/214/226/351/207/215/346/236/204/346/200/273/347/272/262.md +1073 -0
  163. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/archive//344/270/255/351/227/264/346/200/201/01-/347/233/256/345/275/225/344/270/216/345/210/206/345/261/202/351/207/215/346/236/204/346/211/247/350/241/214/346/226/271/346/241/210.md +224 -0
  164. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/archive//344/270/255/351/227/264/346/200/201/02-/345/214/205/344/270/216/345/210/206/345/217/221/350/247/204/350/214/203-/345/220/253/345/216/206/345/217/262/345/217/243/345/276/204.md +671 -0
  165. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/archive//344/270/255/351/227/264/346/200/201/13-/344/270/200/346/254/241/346/200/247/345/256/236/346/226/275/346/270/205/345/215/225.md +387 -0
  166. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/archive//344/270/255/351/227/264/346/200/201/14-/345/255/220/345/214/205/351/200/202/351/205/215/346/224/271/351/200/240/346/270/205/345/215/225.md +592 -0
  167. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/archive//344/270/255/351/227/264/346/200/201/15-infra/350/243/201/345/211/252/344/270/216/350/220/275/344/275/215/346/270/205/345/215/225.md +81 -0
  168. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/archive//344/270/255/351/227/264/346/200/201/19-core/345/215/225/344/270/200/346/255/243/345/274/217/346/226/271/346/241/210/346/224/266/346/225/233/346/211/247/350/241/214/346/270/205/345/215/225.md +161 -0
  169. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/archive//344/270/255/351/227/264/346/200/201/25-/345/215/217/350/256/256/345/210/260/346/272/220/347/240/201/345/267/256/350/267/235/346/270/205/345/215/225.md +377 -0
  170. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/archive//344/270/255/351/227/264/346/200/201/26-cutover/345/256/214/345/267/245/346/243/200/346/237/245/346/270/205/345/215/225.md +117 -0
  171. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/archive//344/270/255/351/227/264/346/200/201/29-/345/244/232/345/233/275/350/257/255/350/250/200/350/265/204/346/272/220/346/262/273/347/220/206/344/270/216/346/214/211/351/234/200/345/212/240/350/275/275/350/247/204/350/214/203-/345/220/253/350/277/201/347/247/273/347/255/226/347/225/245.md +956 -0
  172. pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/archive//344/270/255/351/227/264/346/200/201/99-/350/265/204/346/272/220/346/224/266/345/217/243/345/220/216/347/273/255TODO.md +84 -0
  173. pz_core/docs//345/217/221/347/211/210/345/211/215/346/270/205/345/215/225-4.0.11.md +84 -0
  174. pz_core/docs//346/226/260/346/211/213/346/214/207/345/215/227.md +422 -0
  175. pz_core/infra/__init__.py +17 -0
  176. pz_core/infra/db/__init__.py +23 -0
  177. pz_core/infra/db/contracts.py +73 -0
  178. pz_core/infra/health/__init__.py +5 -0
  179. pz_core/infra/health/contracts.py +8 -0
  180. pz_core/infra/kv/__init__.py +5 -0
  181. pz_core/infra/kv/contracts.py +17 -0
  182. pz_core/infra/observability/__init__.py +23 -0
  183. pz_core/infra/observability/contracts.py +142 -0
  184. pz_core/infra/resilience/__init__.py +5 -0
  185. pz_core/infra/resilience/contracts.py +17 -0
  186. pz_core/infra/resources/__init__.py +5 -0
  187. pz_core/infra/resources/contracts.py +16 -0
  188. pz_core/infra/settings/__init__.py +17 -0
  189. pz_core/infra/settings/models.py +81 -0
  190. pz_core/infra/storage/__init__.py +5 -0
  191. pz_core/infra/storage/ports.py +27 -0
  192. pz_core/project_root_commands.py +282 -0
  193. pz_core/project_runtime_cli.py +344 -0
  194. pz_core/project_sources.py +253 -0
  195. pz_core/pz_launcher.py +209 -0
  196. pz_core/root_cli.py +70 -0
  197. pz_core/runtime/__init__.py +229 -0
  198. pz_core/runtime/access.py +86 -0
  199. pz_core/runtime/adapters/__init__.py +5 -0
  200. pz_core/runtime/adapters/base.py +18 -0
  201. pz_core/runtime/asyncio_bridge.py +18 -0
  202. pz_core/runtime/bindings/__init__.py +5 -0
  203. pz_core/runtime/bindings/container.py +50 -0
  204. pz_core/runtime/cli/__init__.py +5 -0
  205. pz_core/runtime/cli/_args.py +67 -0
  206. pz_core/runtime/cli/app_commands.py +417 -0
  207. pz_core/runtime/cli/check.py +214 -0
  208. pz_core/runtime/cli/db.py +297 -0
  209. pz_core/runtime/cli/main.py +89 -0
  210. pz_core/runtime/cli/resources.py +280 -0
  211. pz_core/runtime/discovery.py +23 -0
  212. pz_core/runtime/environment_integrity.py +157 -0
  213. pz_core/runtime/errors.py +5 -0
  214. pz_core/runtime/events.py +87 -0
  215. pz_core/runtime/facades/__init__.py +38 -0
  216. pz_core/runtime/facades/_capabilities.py +46 -0
  217. pz_core/runtime/facades/cache.py +103 -0
  218. pz_core/runtime/facades/database.py +71 -0
  219. pz_core/runtime/facades/events.py +380 -0
  220. pz_core/runtime/facades/health.py +101 -0
  221. pz_core/runtime/facades/mail.py +121 -0
  222. pz_core/runtime/facades/observability.py +102 -0
  223. pz_core/runtime/facades/providers.py +75 -0
  224. pz_core/runtime/facades/resources.py +52 -0
  225. pz_core/runtime/facades/storage.py +296 -0
  226. pz_core/runtime/health/__init__.py +5 -0
  227. pz_core/runtime/health/service.py +102 -0
  228. pz_core/runtime/host.py +188 -0
  229. pz_core/runtime/host_runner.py +282 -0
  230. pz_core/runtime/http/__init__.py +20 -0
  231. pz_core/runtime/http/app.py +576 -0
  232. pz_core/runtime/http_timing.py +173 -0
  233. pz_core/runtime/invocation.py +29 -0
  234. pz_core/runtime/json_safe.py +47 -0
  235. pz_core/runtime/lifecycle/__init__.py +25 -0
  236. pz_core/runtime/lifecycle/manager.py +533 -0
  237. pz_core/runtime/plan.py +214 -0
  238. pz_core/runtime/plan_context.py +33 -0
  239. pz_core/runtime/plan_snapshot.py +177 -0
  240. pz_core/runtime/registries/__init__.py +27 -0
  241. pz_core/runtime/registries/capabilities.py +101 -0
  242. pz_core/runtime/registries/providers.py +396 -0
  243. pz_core/runtime/resource_assets.py +344 -0
  244. pz_core/runtime/run_cli.py +211 -0
  245. pz_core/runtime/settings.py +888 -0
  246. pz_core/runtime/worker/__init__.py +21 -0
  247. pz_core/runtime/worker/engine.py +921 -0
  248. pz_core/runtime/worker/runtime.py +25 -0
  249. pz_core/testing/__init__.py +15 -0
  250. pz_core/testing/doc_route_contracts.py +305 -0
  251. pz_core/utils/__init__.py +44 -0
  252. pz_core/utils/env.py +65 -0
  253. pz_core/utils/project_jsonc/__init__.py +29 -0
  254. pz_core/utils/project_jsonc/document.py +420 -0
  255. pz_core/utils/project_settings.py +99 -0
  256. pz_foundation/__init__.py +50 -0
  257. pz_foundation/_contracts.py +81 -0
  258. pz_foundation/_errors.py +34 -0
  259. pz_foundation/api.py +14 -0
  260. pz_foundation/apps.py +127 -0
  261. pz_foundation/auth/__init__.py +1 -0
  262. pz_foundation/cache_control.py +233 -0
  263. pz_foundation/cli/__init__.py +2 -0
  264. pz_foundation/cli/workers.py +565 -0
  265. pz_foundation/configs/__init__.py +0 -0
  266. pz_foundation/configs/default.env +15 -0
  267. pz_foundation/configs/default.jsonc +452 -0
  268. pz_foundation/db/__init__.py +94 -0
  269. pz_foundation/db/aerich_executor.py +664 -0
  270. pz_foundation/db/config.py +358 -0
  271. pz_foundation/db/connection_manager.py +689 -0
  272. pz_foundation/db/connection_probe.py +204 -0
  273. pz_foundation/db/database.py +1291 -0
  274. pz_foundation/db/deps.py +47 -0
  275. pz_foundation/db/lifecycle_status.py +85 -0
  276. pz_foundation/db/migrations.py +477 -0
  277. pz_foundation/db/mysql.py +57 -0
  278. pz_foundation/db/postgresql.py +409 -0
  279. pz_foundation/db/production_pool_fallback.py +143 -0
  280. pz_foundation/db/public.py +45 -0
  281. pz_foundation/db/session.py +369 -0
  282. pz_foundation/db/sqlite.py +15 -0
  283. pz_foundation/db/tortoise_manager.py +724 -0
  284. pz_foundation/db/tortoise_psycopg.py +148 -0
  285. pz_foundation/docs/README.md +18 -0
  286. pz_foundation/docs/distribution-README.md +319 -0
  287. pz_foundation/docs/infra/350/243/201/345/211/252/344/270/216/350/220/275/344/275/215/350/257/264/346/230/216.md +39 -0
  288. pz_foundation/docs//345/205/254/345/274/200/345/205/215/350/264/271/346/216/245/345/217/243/351/230/262/346/273/245/347/224/250/344/270/216/344/272/272/346/234/272/346/240/241/351/252/214.md +680 -0
  289. pz_foundation/docs//350/256/276/350/256/241/346/226/207/346/241/243.md +288 -0
  290. pz_foundation/docs//350/267/257/347/224/261/344/270/216URL/345/244/204/347/220/206/350/247/204/350/214/203.md +119 -0
  291. pz_foundation/health.py +105 -0
  292. pz_foundation/host/__init__.py +11 -0
  293. pz_foundation/host/builder.py +34 -0
  294. pz_foundation/host/cache_runtime.py +44 -0
  295. pz_foundation/host/db/__init__.py +17 -0
  296. pz_foundation/host/db/aerich_config.py +36 -0
  297. pz_foundation/host/db/config.py +106 -0
  298. pz_foundation/host/db/connection_manager.py +48 -0
  299. pz_foundation/host/db/deps.py +5 -0
  300. pz_foundation/host/db/orm_retry_patch.py +566 -0
  301. pz_foundation/host/db/request_adapter.py +325 -0
  302. pz_foundation/host/db/router.py +28 -0
  303. pz_foundation/host/db/session.py +284 -0
  304. pz_foundation/host/db/tortoise_manager.py +10 -0
  305. pz_foundation/host/idempotency.py +132 -0
  306. pz_foundation/host/lifespan.py +247 -0
  307. pz_foundation/host/redis_runtime.py +100 -0
  308. pz_foundation/host/web.py +790 -0
  309. pz_foundation/infra_provider.py +236 -0
  310. pz_foundation/kv/__init__.py +25 -0
  311. pz_foundation/kv/cache.py +1166 -0
  312. pz_foundation/kv/deps.py +84 -0
  313. pz_foundation/kv/public.py +25 -0
  314. pz_foundation/kv/redis_manager.py +1531 -0
  315. pz_foundation/lazy_exports.py +43 -0
  316. pz_foundation/lazy_imports.py +124 -0
  317. pz_foundation/lifecycle.py +392 -0
  318. pz_foundation/managed_workers.py +1117 -0
  319. pz_foundation/middlewares/__init__.py +26 -0
  320. pz_foundation/middlewares/_settings.py +250 -0
  321. pz_foundation/middlewares/analytics.py +124 -0
  322. pz_foundation/middlewares/cache_control.py +156 -0
  323. pz_foundation/middlewares/compression.py +202 -0
  324. pz_foundation/middlewares/cors.py +62 -0
  325. pz_foundation/middlewares/geo_country.py +44 -0
  326. pz_foundation/middlewares/inner_router.py +50 -0
  327. pz_foundation/middlewares/rate_limited/__init__.py +3 -0
  328. pz_foundation/middlewares/rate_limited/key_limiter.py +115 -0
  329. pz_foundation/middlewares/request_logging.py +104 -0
  330. pz_foundation/middlewares/route_owner.py +309 -0
  331. pz_foundation/middlewares/security_csrf.py +44 -0
  332. pz_foundation/middlewares/security_headers/__init__.py +4 -0
  333. pz_foundation/middlewares/security_headers/conflicts.py +44 -0
  334. pz_foundation/middlewares/security_headers/csp_nonce.py +200 -0
  335. pz_foundation/middlewares/security_headers/security_headers.py +44 -0
  336. pz_foundation/migrations/__init__.py +1 -0
  337. pz_foundation/migrations/pz_foundation/__init__.py +1 -0
  338. pz_foundation/models.py +22 -0
  339. pz_foundation/notifications/__init__.py +23 -0
  340. pz_foundation/notifications/email.py +542 -0
  341. pz_foundation/notifications/public.py +31 -0
  342. pz_foundation/observability/__init__.py +71 -0
  343. pz_foundation/observability/http.py +34 -0
  344. pz_foundation/observability/logger/__init__.py +49 -0
  345. pz_foundation/observability/logger/_bot_settings.py +126 -0
  346. pz_foundation/observability/logger/bot_message.py +592 -0
  347. pz_foundation/observability/logger/core.py +385 -0
  348. pz_foundation/observability/logger/delivery_audit.py +84 -0
  349. pz_foundation/observability/logger/feishu_bot.py +199 -0
  350. pz_foundation/observability/logger/telegram_bot.py +194 -0
  351. pz_foundation/observability/metrics.py +365 -0
  352. pz_foundation/observability/runtime.py +68 -0
  353. pz_foundation/public.py +405 -0
  354. pz_foundation/resources/__init__.py +12 -0
  355. pz_foundation/resources/builder.py +900 -0
  356. pz_foundation/resources/images.py +363 -0
  357. pz_foundation/root_cli.py +20 -0
  358. pz_foundation/routing/__init__.py +203 -0
  359. pz_foundation/routing/canonical.py +486 -0
  360. pz_foundation/routing/convertors.py +82 -0
  361. pz_foundation/routing/declarations.py +843 -0
  362. pz_foundation/routing/exports.py +148 -0
  363. pz_foundation/routing/loaders.py +138 -0
  364. pz_foundation/routing/manifest.py +215 -0
  365. pz_foundation/routing/manifest_provider.py +47 -0
  366. pz_foundation/routing/mounts.py +101 -0
  367. pz_foundation/routing/ordering.py +172 -0
  368. pz_foundation/routing/registry.py +133 -0
  369. pz_foundation/routing/utils.py +28 -0
  370. pz_foundation/runtime.py +234 -0
  371. pz_foundation/settings.py +253 -0
  372. pz_foundation/settings_normalization.py +130 -0
  373. pz_foundation/storage/__init__.py +25 -0
  374. pz_foundation/storage/local.py +209 -0
  375. pz_foundation/storage/public.py +106 -0
  376. pz_foundation/storage/s3.py +116 -0
  377. pz_foundation/storage/service.py +234 -0
  378. pz_foundation/urls.py +19 -0
  379. pz_foundation/utils/__init__.py +6 -0
  380. pz_foundation/utils/merge.py +160 -0
  381. pz_foundation/worker_replicas.py +55 -0
  382. pz_settings_center/__init__.py +5 -0
  383. pz_settings_center/api/__init__.py +5 -0
  384. pz_settings_center/api/runtime_overrides.py +360 -0
  385. pz_settings_center/apps.py +28 -0
  386. pz_settings_center/configs/__init__.py +0 -0
  387. pz_settings_center/configs/default.env +2 -0
  388. pz_settings_center/configs/default.jsonc +1 -0
  389. pz_settings_center/docs/01-settings_center/350/203/275/345/212/233/350/276/271/347/225/214.md +124 -0
  390. pz_settings_center/docs/README.md +12 -0
  391. pz_settings_center/docs/distribution-README.md +150 -0
  392. pz_settings_center/docs//350/256/276/350/256/241/346/226/207/346/241/243.md +48 -0
  393. pz_settings_center/integration_payload.py +18 -0
  394. pz_settings_center/project_helpers.py +405 -0
  395. pz_settings_center/public.py +139 -0
  396. pz_settings_center/routing_helpers.py +73 -0
  397. pz_settings_center/runtime_override_provider.py +61 -0
  398. pz_settings_center/typed_settings.py +324 -0
  399. pz_settings_center/urls.py +9 -0
@@ -0,0 +1,888 @@
1
+ from __future__ import annotations
2
+
3
+ import copy
4
+ from contextlib import contextmanager
5
+ from contextvars import ContextVar
6
+ from dataclasses import dataclass, field
7
+ from functools import lru_cache
8
+ import os
9
+ from pathlib import Path
10
+ from typing import Any, Callable, Iterator, TypeVar
11
+
12
+ from pz_core.assembly.project import compile_project_context
13
+ from pz_core.assembly.settings import SettingsSnapshot, compile_settings_snapshot, load_project_env_file
14
+ from pz_core.core.extensions import SettingsPatch, SettingsProviderContext, SettingsProviderSpec
15
+ from pz_core.runtime.errors import PzError
16
+ from pz_core.runtime.plan_context import current_runtime_plan
17
+ from pz_core.utils import (
18
+ has_env_value,
19
+ iter_env_items,
20
+ load_jsonc_file,
21
+ read_env_bool,
22
+ read_env_float,
23
+ read_env_int,
24
+ read_env_string,
25
+ )
26
+ from pz_core.utils.project_jsonc import PROJECT_JSONC_NAME
27
+
28
+
29
+ TAppSettings = TypeVar("TAppSettings")
30
+
31
+ _SETTINGS_SNAPSHOTS: dict[str, SettingsSnapshot] = {}
32
+ _SETTINGS_SNAPSHOT_REFCOUNTS: dict[str, int] = {}
33
+ _SETTINGS_NAMESPACE_CACHE: dict[int, tuple[SettingsSnapshot, frozenset[str]]] = {}
34
+ _SETTINGS_PROVIDER_SPECS_CACHE: dict[int, tuple[object, tuple[SettingsProviderSpec, ...]]] = {}
35
+ _SETTINGS_PROVIDER_VALUES_CACHE: dict[tuple[int, str | None], tuple[SettingsSnapshot, dict[str, dict[str, Any]]]] = {}
36
+ _RESOLVED_SETTINGS_CACHE: dict[tuple[int, str | None], tuple[SettingsSnapshot, "ResolvedSettings"]] = {}
37
+ _ACTIVE_SETTINGS_PROJECT_DIR: ContextVar[str | None] = ContextVar(
38
+ "pz_runtime_settings_active_project_dir",
39
+ default=None,
40
+ )
41
+ _SETTINGS_PROVIDER_RESOLUTION_DEPTH: ContextVar[int] = ContextVar(
42
+ "pz_runtime_settings_provider_resolution_depth",
43
+ default=0,
44
+ )
45
+
46
+ _ROUTE_TOKEN_CATALOG_PROVIDER: Callable[[str | Path | None], Any] | None = None
47
+
48
+
49
+ def _invalidate_settings_runtime_caches() -> None:
50
+ _SETTINGS_NAMESPACE_CACHE.clear()
51
+ _SETTINGS_PROVIDER_SPECS_CACHE.clear()
52
+ _SETTINGS_PROVIDER_VALUES_CACHE.clear()
53
+ _RESOLVED_SETTINGS_CACHE.clear()
54
+
55
+
56
+ @lru_cache(maxsize=None)
57
+ def _resolve_path_text(value: str) -> str:
58
+ return str(Path(value).resolve())
59
+
60
+
61
+ def _local_default_config_path(anchor: str | os.PathLike[str]) -> Path:
62
+ location = Path(anchor).resolve()
63
+ base = location.parent if location.is_file() else location
64
+ if base.name == "settings":
65
+ base = base.parent
66
+ return base / "configs" / "default.jsonc"
67
+
68
+
69
+ @lru_cache(maxsize=None)
70
+ def _cached_local_default_settings(anchor: str) -> dict[str, Any]:
71
+ jsonc_path = _local_default_config_path(anchor)
72
+ if not jsonc_path.is_file():
73
+ return {}
74
+ return load_jsonc_file(
75
+ jsonc_path,
76
+ code="PZ_APP_DEFAULTS_INVALID",
77
+ owner="runtime.settings",
78
+ action="load_local_default_settings",
79
+ )
80
+
81
+
82
+ def load_local_default_settings(anchor: str | os.PathLike[str]) -> dict[str, Any]:
83
+ """Read package default templates for authoring-time settings tools.
84
+
85
+ This helper is kept for compatibility with settings init/update style code.
86
+ Runtime settings compilation must not call it.
87
+ """
88
+ return copy.deepcopy(_cached_local_default_settings(_resolve_path_text(str(anchor))))
89
+
90
+
91
+ def build_local_default_settings(anchor: str | os.PathLike[str], settings_type: type[TAppSettings]) -> TAppSettings:
92
+ return settings_type(**load_local_default_settings(anchor))
93
+
94
+
95
+ def _deep_merge_local_defaults(base: dict[str, Any], incoming: dict[str, Any]) -> dict[str, Any]:
96
+ merged = dict(base)
97
+ for key, value in incoming.items():
98
+ if isinstance(value, dict) and isinstance(merged.get(key), dict):
99
+ merged[key] = _deep_merge_local_defaults(merged[key], value)
100
+ continue
101
+ merged[key] = copy.deepcopy(value)
102
+ return merged
103
+
104
+
105
+ def load_local_default_settings_chain(*anchors: str | os.PathLike[str]) -> dict[str, Any]:
106
+ merged: dict[str, Any] = {}
107
+ for anchor in anchors:
108
+ merged = _deep_merge_local_defaults(merged, load_local_default_settings(anchor))
109
+ return merged
110
+
111
+
112
+ def build_local_default_settings_chain(
113
+ settings_type: type[TAppSettings],
114
+ *anchors: str | os.PathLike[str],
115
+ ) -> TAppSettings:
116
+ return settings_type(**load_local_default_settings_chain(*anchors))
117
+
118
+
119
+ def _deep_merge_payload(base: dict[str, Any], incoming: dict[str, Any]) -> dict[str, Any]:
120
+ merged = dict(base)
121
+ for key, value in incoming.items():
122
+ if isinstance(value, dict) and isinstance(merged.get(key), dict):
123
+ merged[key] = _deep_merge_payload(dict(merged[key]), value)
124
+ continue
125
+ merged[key] = copy.deepcopy(value)
126
+ return merged
127
+
128
+
129
+ def _normalize_project_dir(project_dir: str | Path | None) -> str | None:
130
+ if project_dir is None:
131
+ return None
132
+ value = str(project_dir).strip()
133
+ if not value:
134
+ return None
135
+ return _resolve_path_text(value)
136
+
137
+
138
+ def _runtime_plan_project_dir() -> str | None:
139
+ runtime_plan = current_runtime_plan()
140
+ return _normalize_project_dir(getattr(getattr(runtime_plan, "project", None), "project_dir", None))
141
+
142
+
143
+ def _configured_project_dir() -> str | None:
144
+ raw_env_project_dir = os.getenv("PZ_PROJECT_DIR")
145
+ if raw_env_project_dir is not None:
146
+ return _normalize_project_dir(compile_project_context(project_dir=None).project_dir)
147
+
148
+ current = Path.cwd().resolve()
149
+ for candidate in (current, *current.parents):
150
+ if (candidate / PROJECT_JSONC_NAME).is_file():
151
+ return _normalize_project_dir(candidate)
152
+ return None
153
+
154
+
155
+ def _effective_project_dir(project_dir: str | Path | None = None) -> str | None:
156
+ explicit_project_dir = _normalize_project_dir(project_dir)
157
+ if explicit_project_dir:
158
+ return explicit_project_dir
159
+ active = _ACTIVE_SETTINGS_PROJECT_DIR.get()
160
+ if active:
161
+ return active
162
+ runtime_project_dir = _runtime_plan_project_dir()
163
+ if runtime_project_dir:
164
+ return runtime_project_dir
165
+ configured_project_dir = _configured_project_dir()
166
+ if configured_project_dir:
167
+ return configured_project_dir
168
+ if len(_SETTINGS_SNAPSHOTS) == 1:
169
+ return next(iter(_SETTINGS_SNAPSHOTS.keys()))
170
+ return None
171
+
172
+
173
+ def current_settings_project_dir() -> str | None:
174
+ active = _ACTIVE_SETTINGS_PROJECT_DIR.get()
175
+ if active:
176
+ return active
177
+ runtime_project_dir = _runtime_plan_project_dir()
178
+ if runtime_project_dir:
179
+ return runtime_project_dir
180
+ if len(_SETTINGS_SNAPSHOTS) == 1:
181
+ return next(iter(_SETTINGS_SNAPSHOTS.keys()))
182
+ return None
183
+
184
+
185
+ def _normalize_current_pz_env(value: object, *, default: str = "develop") -> str:
186
+ raw = str(value or "").strip().lower()
187
+ if raw in {"dev", "development"}:
188
+ return "develop"
189
+ if raw in {"prod", "production"}:
190
+ return "product"
191
+ if raw in {"develop", "test", "product"}:
192
+ return raw
193
+ fallback = str(default or "develop").strip().lower()
194
+ if fallback in {"test", "product"}:
195
+ return fallback
196
+ return "develop"
197
+
198
+
199
+ def current_pz_env(*, project_dir: str | Path | None = None, default: str = "develop") -> str:
200
+ bound_snapshot = _resolve_bound_snapshot(project_dir)
201
+ if bound_snapshot is not None:
202
+ try:
203
+ resolved = resolve_settings(project_dir=_normalize_project_dir(project_dir))
204
+ return _normalize_current_pz_env(getattr(resolved.project, "pz_env", None), default=default)
205
+ except Exception:
206
+ pass
207
+ payload = getattr(bound_snapshot, "data", {}) or {}
208
+ project_payload = payload.get("project") if isinstance(payload, dict) else {}
209
+ return _normalize_current_pz_env(
210
+ project_payload.get("pz_env") if isinstance(project_payload, dict) else None,
211
+ default=default,
212
+ )
213
+ resolved = resolve_settings(project_dir=_normalize_project_dir(project_dir))
214
+ return _normalize_current_pz_env(getattr(resolved.project, "pz_env", None), default=default)
215
+
216
+
217
+ def bind_settings_snapshot(
218
+ snapshot: SettingsSnapshot,
219
+ *,
220
+ project_dir: str | Path | None = None,
221
+ ) -> SettingsSnapshot:
222
+ normalized_project_dir = _normalize_project_dir(
223
+ project_dir if project_dir is not None else _snapshot_project_dir(snapshot)
224
+ )
225
+ if normalized_project_dir:
226
+ _SETTINGS_SNAPSHOTS[normalized_project_dir] = snapshot
227
+ _SETTINGS_SNAPSHOT_REFCOUNTS[normalized_project_dir] = (
228
+ int(_SETTINGS_SNAPSHOT_REFCOUNTS.get(normalized_project_dir, 0)) + 1
229
+ )
230
+ _invalidate_settings_runtime_caches()
231
+ return snapshot
232
+
233
+
234
+ def reset_settings_binding(project_dir: str | Path | None = None) -> None:
235
+ normalized_project_dir = _normalize_project_dir(project_dir)
236
+ if normalized_project_dir is None:
237
+ _SETTINGS_SNAPSHOTS.clear()
238
+ _SETTINGS_SNAPSHOT_REFCOUNTS.clear()
239
+ _invalidate_settings_runtime_caches()
240
+ return
241
+ remaining = int(_SETTINGS_SNAPSHOT_REFCOUNTS.get(normalized_project_dir, 0)) - 1
242
+ if remaining > 0:
243
+ _SETTINGS_SNAPSHOT_REFCOUNTS[normalized_project_dir] = remaining
244
+ return
245
+ _SETTINGS_SNAPSHOT_REFCOUNTS.pop(normalized_project_dir, None)
246
+ _SETTINGS_SNAPSHOTS.pop(normalized_project_dir, None)
247
+ _invalidate_settings_runtime_caches()
248
+
249
+
250
+ @contextmanager
251
+ def active_settings_project(project_dir: str | Path | None) -> Iterator[None]:
252
+ token = _ACTIVE_SETTINGS_PROJECT_DIR.set(_normalize_project_dir(project_dir))
253
+ try:
254
+ yield
255
+ finally:
256
+ _ACTIVE_SETTINGS_PROJECT_DIR.reset(token)
257
+
258
+
259
+ @contextmanager
260
+ def bound_settings_snapshot(
261
+ snapshot: SettingsSnapshot,
262
+ *,
263
+ project_dir: str | Path | None = None,
264
+ ) -> Iterator[None]:
265
+ bind_settings_snapshot(snapshot, project_dir=project_dir)
266
+ try:
267
+ yield
268
+ finally:
269
+ reset_settings_binding(project_dir=project_dir)
270
+
271
+
272
+ def _env_string(*names: str) -> str:
273
+ return read_env_string("", *names)
274
+
275
+
276
+ def _env_int(default: int, *names: str) -> int:
277
+ return read_env_int(default, *names)
278
+
279
+
280
+ def has_runtime_env_value(*names: str) -> bool:
281
+ return has_env_value(*names)
282
+
283
+
284
+ def iter_runtime_env_items() -> tuple[tuple[str, str], ...]:
285
+ return iter_env_items()
286
+
287
+
288
+ def read_runtime_env_string(*names: str) -> str:
289
+ return _env_string(*names)
290
+
291
+
292
+ def read_runtime_env_int(default: int, *names: str) -> int:
293
+ return _env_int(default, *names)
294
+
295
+
296
+ def read_runtime_env_bool(default: bool, *names: str) -> bool:
297
+ return read_env_bool(default, *names)
298
+
299
+
300
+ def read_runtime_env_float(default: float, *names: str) -> float:
301
+ return read_env_float(default, *names)
302
+
303
+
304
+ def register_route_token_catalog_provider(provider: Callable[[str | Path | None], Any]) -> None:
305
+ global _ROUTE_TOKEN_CATALOG_PROVIDER
306
+ _ROUTE_TOKEN_CATALOG_PROVIDER = provider
307
+
308
+
309
+ def reset_route_token_catalog_provider() -> None:
310
+ global _ROUTE_TOKEN_CATALOG_PROVIDER
311
+ _ROUTE_TOKEN_CATALOG_PROVIDER = None
312
+
313
+
314
+ def _ensure_route_token_catalog_provider() -> Callable[[str | Path | None], Any]:
315
+ provider = _ROUTE_TOKEN_CATALOG_PROVIDER
316
+ if provider is not None:
317
+ return provider
318
+ raise PzError(
319
+ code="PZ_ROUTE_TOKEN_CATALOG_PROVIDER_MISSING",
320
+ message=(
321
+ "Route token catalog provider is not registered. "
322
+ "Owner packages must register it from their own formal surface."
323
+ ),
324
+ stage="settings",
325
+ owner="runtime.settings",
326
+ action="get_route_token_catalog",
327
+ )
328
+
329
+
330
+ class _RouteTokenCatalogProxy:
331
+ def __init__(self, project_dir: str | Path | None = None) -> None:
332
+ self._project_dir = _normalize_project_dir(project_dir)
333
+
334
+ def _facade(self) -> Any:
335
+ return _ensure_route_token_catalog_provider()(self._project_dir)
336
+
337
+ def __getattr__(self, name: str) -> Any:
338
+ return getattr(self._facade(), name)
339
+
340
+
341
+ def get_route_token_catalog(*, project_dir: str | Path | None = None) -> _RouteTokenCatalogProxy:
342
+ return _RouteTokenCatalogProxy(project_dir=project_dir)
343
+
344
+
345
+ def _resolve_bound_snapshot(project_dir: str | Path | None = None) -> SettingsSnapshot | None:
346
+ normalized_project_dir = _normalize_project_dir(project_dir)
347
+ runtime_plan = current_runtime_plan()
348
+ runtime_project_dir = _runtime_plan_project_dir()
349
+ runtime_settings = getattr(runtime_plan, "settings", None) if runtime_plan is not None else None
350
+ if runtime_settings is not None and (normalized_project_dir is None or runtime_project_dir == normalized_project_dir):
351
+ return runtime_settings
352
+ if normalized_project_dir is not None:
353
+ return _SETTINGS_SNAPSHOTS.get(normalized_project_dir)
354
+ active_project_dir = current_settings_project_dir()
355
+ if active_project_dir and active_project_dir in _SETTINGS_SNAPSHOTS:
356
+ return _SETTINGS_SNAPSHOTS[active_project_dir]
357
+ if len(_SETTINGS_SNAPSHOTS) == 1:
358
+ return next(iter(_SETTINGS_SNAPSHOTS.values()))
359
+ return None
360
+
361
+
362
+ def load_project_env_values(project_dir: str | None = None) -> dict[str, str]:
363
+ project = compile_project_context(project_dir=project_dir)
364
+ return load_project_env_file(project)
365
+
366
+
367
+ def _provider_values(snapshot: SettingsSnapshot, app_id: str, *, project_dir: str | None = None) -> dict[str, Any]:
368
+ if _SETTINGS_PROVIDER_RESOLUTION_DEPTH.get() > 0:
369
+ return {}
370
+ structured_values = _structured_provider_values(
371
+ snapshot,
372
+ project_dir=_normalize_project_dir(project_dir if project_dir is not None else _snapshot_project_dir(snapshot)),
373
+ )
374
+ return copy.deepcopy(structured_values.get(str(app_id).strip(), {}) or {})
375
+
376
+
377
+ def _settings_provider_specs(
378
+ snapshot: SettingsSnapshot,
379
+ *,
380
+ project_dir: str | None,
381
+ ) -> tuple[SettingsProviderSpec, ...]:
382
+ runtime_plan = current_runtime_plan()
383
+ normalized_project_dir = _normalize_project_dir(
384
+ project_dir if project_dir is not None else _snapshot_project_dir(snapshot)
385
+ )
386
+ if runtime_plan is not None:
387
+ runtime_project_dir = _normalize_project_dir(getattr(getattr(runtime_plan, "project", None), "project_dir", None))
388
+ if runtime_project_dir == normalized_project_dir:
389
+ return tuple(getattr(runtime_plan, "settings_providers", ()) or ())
390
+
391
+ return _discover_settings_provider_extensions(snapshot)
392
+
393
+
394
+ def _discover_settings_provider_extensions(snapshot: SettingsSnapshot) -> tuple[SettingsProviderSpec, ...]:
395
+ from pz_core.assembly.apps import compile_app_registry
396
+ from pz_core.assembly.extensions import compile_extension_registry
397
+
398
+ cache_key = id(snapshot)
399
+ cached = _SETTINGS_PROVIDER_SPECS_CACHE.get(cache_key)
400
+ if cached is not None and cached[0] is snapshot:
401
+ return cached[1]
402
+
403
+ app_registry = compile_app_registry(
404
+ snapshot,
405
+ project_dir=_snapshot_project_dir(snapshot),
406
+ run_init_hooks=False,
407
+ )
408
+ extension_registry = compile_extension_registry(snapshot, app_registry)
409
+ discovered = tuple(
410
+ spec
411
+ for spec in extension_registry.by_kind("settings.provider")
412
+ if isinstance(spec, SettingsProviderSpec)
413
+ )
414
+ _SETTINGS_PROVIDER_SPECS_CACHE[cache_key] = (snapshot, discovered)
415
+ return discovered
416
+
417
+
418
+ def _normalize_settings_patch(
419
+ payload: Any,
420
+ *,
421
+ owner: str,
422
+ default_priority: int,
423
+ ) -> tuple[SettingsPatch, ...]:
424
+ def _coerce_patch(item: Any) -> SettingsPatch:
425
+ if isinstance(item, SettingsPatch):
426
+ return item
427
+ app_id = getattr(item, "app_id", None)
428
+ patch_payload = getattr(item, "payload", None)
429
+ source = getattr(item, "source", None)
430
+ priority = getattr(item, "priority", default_priority)
431
+ if not isinstance(app_id, str) or not app_id.strip():
432
+ raise PzError(
433
+ code="PZ_SETTINGS_PROVIDER_RESULT_INVALID",
434
+ message=f"Settings provider '{owner}' must return SettingsPatch objects.",
435
+ stage="settings",
436
+ owner=owner,
437
+ action="load_app_settings",
438
+ )
439
+ if not isinstance(patch_payload, dict):
440
+ raise PzError(
441
+ code="PZ_SETTINGS_PROVIDER_RESULT_INVALID",
442
+ message=f"Settings provider '{owner}' must return SettingsPatch objects.",
443
+ stage="settings",
444
+ owner=owner,
445
+ action="load_app_settings",
446
+ )
447
+ if not isinstance(source, str) or not source.strip():
448
+ raise PzError(
449
+ code="PZ_SETTINGS_PROVIDER_RESULT_INVALID",
450
+ message=f"Settings provider '{owner}' must return SettingsPatch objects.",
451
+ stage="settings",
452
+ owner=owner,
453
+ action="load_app_settings",
454
+ )
455
+ return SettingsPatch(
456
+ app_id=app_id.strip(),
457
+ payload=dict(patch_payload),
458
+ source=source.strip(),
459
+ priority=int(priority if priority is not None else default_priority),
460
+ )
461
+
462
+ if payload in (None, (), []):
463
+ return ()
464
+ if payload is not None and not isinstance(payload, (list, tuple)):
465
+ return (_coerce_patch(payload),)
466
+ if isinstance(payload, (list, tuple)):
467
+ out: list[SettingsPatch] = []
468
+ for item in payload:
469
+ out.append(_coerce_patch(item))
470
+ return tuple(out)
471
+ raise PzError(
472
+ code="PZ_SETTINGS_PROVIDER_RESULT_INVALID",
473
+ message=f"Settings provider '{owner}' must return a SettingsPatch or tuple of SettingsPatch.",
474
+ stage="settings",
475
+ owner=owner,
476
+ action="load_app_settings",
477
+ )
478
+
479
+
480
+ def _iter_patch_paths(payload: dict[str, Any], prefix: tuple[str, ...] = ()) -> tuple[tuple[str, ...], ...]:
481
+ paths: list[tuple[str, ...]] = []
482
+ for key, value in payload.items():
483
+ current = prefix + (str(key),)
484
+ if isinstance(value, dict) and value:
485
+ paths.extend(_iter_patch_paths(value, current))
486
+ continue
487
+ paths.append(current)
488
+ return tuple(paths)
489
+
490
+
491
+ def _structured_provider_values(
492
+ snapshot: SettingsSnapshot,
493
+ *,
494
+ project_dir: str | None,
495
+ ) -> dict[str, dict[str, Any]]:
496
+ normalized_project_dir = _normalize_project_dir(project_dir)
497
+ cache_key = (id(snapshot), normalized_project_dir)
498
+ cached = _SETTINGS_PROVIDER_VALUES_CACHE.get(cache_key)
499
+ if cached is not None and cached[0] is snapshot:
500
+ return cached[1]
501
+ token = _SETTINGS_PROVIDER_RESOLUTION_DEPTH.set(_SETTINGS_PROVIDER_RESOLUTION_DEPTH.get() + 1)
502
+ try:
503
+ providers = sorted(
504
+ _settings_provider_specs(snapshot, project_dir=normalized_project_dir),
505
+ key=lambda item: (int(item.priority), str(item.owner_app), str(item.id)),
506
+ )
507
+ if not providers:
508
+ return {}
509
+
510
+ project = compile_project_context(project_dir=normalized_project_dir)
511
+ resolved_settings = ResolvedSettings(
512
+ snapshot=snapshot,
513
+ project_dir=normalized_project_dir or _snapshot_project_dir(snapshot),
514
+ )
515
+ merged_by_app: dict[str, dict[str, Any]] = {}
516
+ seen_paths: dict[tuple[str, tuple[str, ...]], tuple[int, str]] = {}
517
+
518
+ for spec in providers:
519
+ provide = getattr(spec.provider, "provide", None)
520
+ if not callable(provide):
521
+ raise PzError(
522
+ code="PZ_SETTINGS_PROVIDER_INVALID",
523
+ message=f"Settings provider '{spec.id}' must declare a provider object with provide(ctx).",
524
+ stage="settings",
525
+ owner=spec.owner_app,
526
+ action="load_app_settings",
527
+ )
528
+ try:
529
+ patches = _normalize_settings_patch(
530
+ provide(
531
+ SettingsProviderContext(
532
+ project=project,
533
+ snapshot=snapshot,
534
+ settings=resolved_settings,
535
+ )
536
+ ),
537
+ owner=spec.id,
538
+ default_priority=int(spec.priority),
539
+ )
540
+ except PzError:
541
+ raise
542
+ except Exception as exc:
543
+ raise PzError(
544
+ code="PZ_SETTINGS_PROVIDER_FAILED",
545
+ message=f"Settings provider '{spec.id}' failed: {exc}",
546
+ stage="settings",
547
+ owner=spec.owner_app,
548
+ action="load_app_settings",
549
+ ) from exc
550
+ for patch in patches:
551
+ app_name = _read_app_id(patch.app_id, action="load_app_settings")
552
+ payload = dict(patch.payload or {})
553
+ priority = int(patch.priority)
554
+ for path in _iter_patch_paths(payload):
555
+ key = (app_name, path)
556
+ previous = seen_paths.get(key)
557
+ if previous is not None and previous[0] == priority:
558
+ raise PzError(
559
+ code="PZ_SETTINGS_PROVIDER_CONFLICT",
560
+ message=(
561
+ f"Settings providers '{previous[1]}' and '{patch.source}' both write "
562
+ f"'{app_name}.{'.'.join(path)}' at priority {priority}."
563
+ ),
564
+ stage="settings",
565
+ owner=app_name,
566
+ action="load_app_settings",
567
+ )
568
+ seen_paths[key] = (priority, patch.source)
569
+ merged_by_app[app_name] = _deep_merge_payload(dict(merged_by_app.get(app_name, {}) or {}), payload)
570
+ _SETTINGS_PROVIDER_VALUES_CACHE[cache_key] = (snapshot, merged_by_app)
571
+ return merged_by_app
572
+ finally:
573
+ _SETTINGS_PROVIDER_RESOLUTION_DEPTH.reset(token)
574
+
575
+
576
+ def _snapshot_project_dir(snapshot: SettingsSnapshot) -> str | None:
577
+ value = getattr(snapshot, "project_dir", None)
578
+ if value is None:
579
+ return None
580
+ try:
581
+ return str(value)
582
+ except Exception:
583
+ return None
584
+
585
+
586
+ def _load_snapshot(project_dir: str | None = None) -> SettingsSnapshot:
587
+ project = compile_project_context(project_dir=project_dir)
588
+ return compile_settings_snapshot(project)
589
+
590
+
591
+ def _snapshot() -> SettingsSnapshot:
592
+ return _load_snapshot()
593
+
594
+
595
+ def _lookup_key(mapping: dict[str, Any], name: str) -> Any:
596
+ if name in mapping:
597
+ return mapping[name]
598
+ raise KeyError(name)
599
+
600
+
601
+ def _declared_settings_namespaces(snapshot: SettingsSnapshot) -> frozenset[str]:
602
+ cache_key = id(snapshot)
603
+ cached = _SETTINGS_NAMESPACE_CACHE.get(cache_key)
604
+ if cached is not None and cached[0] is snapshot:
605
+ return cached[1]
606
+ namespaces: set[str] = set()
607
+ for app_id in dict(getattr(snapshot, "apps", {}) or {}).keys():
608
+ normalized = str(app_id or "").strip()
609
+ if normalized:
610
+ namespaces.add(normalized)
611
+ result = frozenset(namespaces)
612
+ _SETTINGS_NAMESPACE_CACHE[cache_key] = (snapshot, result)
613
+ return result
614
+
615
+
616
+ def _merged_app_settings(
617
+ snapshot: SettingsSnapshot,
618
+ app_id: str,
619
+ *,
620
+ project_dir: str | None = None,
621
+ ) -> tuple[str, dict[str, Any]]:
622
+ values = dict(snapshot.app_settings(app_id))
623
+ provider_values = _provider_values(
624
+ snapshot,
625
+ app_id,
626
+ project_dir=project_dir if project_dir is not None else _snapshot_project_dir(snapshot),
627
+ )
628
+ if provider_values:
629
+ values.update(provider_values)
630
+ return app_id, values
631
+
632
+
633
+ def _root_settings_payload(snapshot: SettingsSnapshot) -> dict[str, Any]:
634
+ payload = dict(getattr(snapshot, "data", {}) or {})
635
+ payload.setdefault("project_dir", getattr(snapshot, "project_dir", None))
636
+ payload.setdefault("installed_apps", tuple(getattr(snapshot, "installed_apps", ()) or ()))
637
+ payload.setdefault("app_replacements", dict(getattr(snapshot, "app_replacements", {}) or {}))
638
+ payload.setdefault("apps", dict(getattr(snapshot, "apps", {}) or {}))
639
+ payload["project"] = _project_settings_payload(snapshot, payload)
640
+ return payload
641
+
642
+
643
+ def _project_settings_payload(snapshot: SettingsSnapshot, root_payload: dict[str, Any]) -> dict[str, Any]:
644
+ project_payload: dict[str, Any] = {}
645
+ for key, value in root_payload.items():
646
+ if key in {"project", "apps", "app_replacements"}:
647
+ continue
648
+ project_payload[key] = copy.deepcopy(value)
649
+
650
+ declared_project = root_payload.get("project")
651
+ if isinstance(declared_project, dict):
652
+ project_payload = _deep_merge_payload(project_payload, copy.deepcopy(declared_project))
653
+
654
+ if "pz_env" not in project_payload and root_payload.get("PZ_ENV") is not None:
655
+ project_payload["pz_env"] = copy.deepcopy(root_payload.get("PZ_ENV"))
656
+ project_payload.setdefault("project_dir", getattr(snapshot, "project_dir", None))
657
+ project_payload.setdefault("installed_apps", tuple(getattr(snapshot, "installed_apps", ()) or ()))
658
+ project_payload.setdefault("app_replacements", dict(getattr(snapshot, "app_replacements", {}) or {}))
659
+ return project_payload
660
+
661
+
662
+ def _mapping_path_value(mapping: dict[str, Any], path: tuple[str, ...], *, default: Any = None) -> Any:
663
+ current: Any = mapping
664
+ for raw_key in path:
665
+ key = str(raw_key)
666
+ if not isinstance(current, dict):
667
+ return default
668
+ try:
669
+ current = _lookup_key(current, key)
670
+ except KeyError:
671
+ return default
672
+ return default if current is None else current
673
+
674
+
675
+ @dataclass(frozen=True, slots=True)
676
+ class ResolvedSettings:
677
+ snapshot: SettingsSnapshot
678
+ project_dir: str | None = None
679
+ _root_settings_cache: dict[str, Any] | None = field(default=None, init=False, repr=False, compare=False)
680
+ _project_namespace_cache: "NamespaceProxy | None" = field(default=None, init=False, repr=False, compare=False)
681
+ _app_settings_cache: dict[str, dict[str, Any]] = field(default_factory=dict, init=False, repr=False, compare=False)
682
+
683
+ def _root_settings_cached(self) -> dict[str, Any]:
684
+ cached = self._root_settings_cache
685
+ if cached is None:
686
+ cached = _root_settings_payload(self.snapshot)
687
+ object.__setattr__(self, "_root_settings_cache", cached)
688
+ return cached
689
+
690
+ def _app_settings_payload(self, app_id: str) -> tuple[str, dict[str, Any]]:
691
+ normalized_app_id = _read_app_id(app_id, action="resolved_settings.app_settings")
692
+ cached = self._app_settings_cache.get(normalized_app_id)
693
+ if cached is not None:
694
+ return normalized_app_id, cached
695
+ resolved_app_id, values = _merged_app_settings(
696
+ self.snapshot,
697
+ normalized_app_id,
698
+ project_dir=self.project_dir if self.project_dir is not None else _snapshot_project_dir(self.snapshot),
699
+ )
700
+ cached_values = dict(values)
701
+ self._app_settings_cache[normalized_app_id] = cached_values
702
+ return resolved_app_id, cached_values
703
+
704
+ def root_settings(self) -> dict[str, Any]:
705
+ return dict(self._root_settings_cached())
706
+
707
+ @property
708
+ def project(self) -> "NamespaceProxy":
709
+ cached = self._project_namespace_cache
710
+ if cached is not None:
711
+ return cached
712
+ payload = _mapping_path_value(self._root_settings_cached(), ("project",), default={})
713
+ namespace = NamespaceProxy(payload if isinstance(payload, dict) else {})
714
+ object.__setattr__(self, "_project_namespace_cache", namespace)
715
+ return namespace
716
+
717
+ @property
718
+ def route_token_catalog(self) -> _RouteTokenCatalogProxy:
719
+ return get_route_token_catalog(
720
+ project_dir=self.project_dir if self.project_dir is not None else _snapshot_project_dir(self.snapshot)
721
+ )
722
+
723
+ def app_settings(self, app_id: str) -> dict[str, Any]:
724
+ _resolved, values = self._app_settings_payload(app_id)
725
+ return dict(values)
726
+
727
+ def typed_app_settings(
728
+ self,
729
+ app_id: str,
730
+ settings_type: type[TAppSettings],
731
+ *,
732
+ normalize: Callable[[dict[str, Any]], dict[str, Any]] | None = None,
733
+ ) -> TAppSettings:
734
+ normalized_app_id = _read_app_id(app_id, action="resolved_settings.typed_app_settings")
735
+ payload = self.app_settings(normalized_app_id)
736
+ if isinstance(payload, settings_type):
737
+ return payload
738
+ values = dict(payload or {})
739
+ if normalize is not None:
740
+ try:
741
+ values = dict(normalize(values) or {})
742
+ except Exception as exc:
743
+ raise PzError(
744
+ code="PZ_APP_SETTINGS_NORMALIZE_FAILED",
745
+ message=f"Failed to normalize typed app settings for '{normalized_app_id}': {exc}",
746
+ stage="settings",
747
+ owner=normalized_app_id,
748
+ action="load_typed_app_settings",
749
+ ) from exc
750
+ try:
751
+ return settings_type(**values)
752
+ except Exception as exc:
753
+ raise PzError(
754
+ code="PZ_APP_SETTINGS_TYPE_INVALID",
755
+ message=f"Failed to build typed app settings for '{normalized_app_id}': {exc}",
756
+ stage="settings",
757
+ owner=normalized_app_id,
758
+ action="load_typed_app_settings",
759
+ ) from exc
760
+
761
+ def get(self, *path: str, default: Any = None) -> Any:
762
+ if not path:
763
+ return default
764
+ head = str(path[0]).strip()
765
+ if head in _declared_settings_namespaces(self.snapshot):
766
+ _resolved, app_values = self._app_settings_payload(head)
767
+ if len(path) == 1:
768
+ return dict(app_values)
769
+ return _mapping_path_value(app_values, tuple(str(item) for item in path[1:]), default=default)
770
+ return _mapping_path_value(self._root_settings_cached(), tuple(str(item) for item in path), default=default)
771
+
772
+ def to_dict(self) -> dict[str, Any]:
773
+ payload = dict(self._root_settings_cached())
774
+ payload["apps"] = {
775
+ str(app_id): self.app_settings(str(app_id))
776
+ for app_id in dict(getattr(self.snapshot, "apps", {}) or {}).keys()
777
+ }
778
+ return payload
779
+
780
+ def __getattr__(self, name: str) -> Any:
781
+ if name in _declared_settings_namespaces(self.snapshot):
782
+ _resolved, values = self._app_settings_payload(name)
783
+ return NamespaceProxy(values)
784
+ raise AttributeError(name)
785
+
786
+
787
+ class NamespaceProxy:
788
+ def __init__(self, payload: Any) -> None:
789
+ self._payload = payload
790
+
791
+ def _mapping(self) -> dict[str, Any]:
792
+ if isinstance(self._payload, dict):
793
+ return self._payload
794
+ return getattr(self._payload, "__dict__", {})
795
+
796
+ def get(self, key: str, default: Any = None) -> Any:
797
+ try:
798
+ value = _lookup_key(self._mapping(), key)
799
+ except KeyError:
800
+ return default
801
+ return _wrap(value)
802
+
803
+ def to_dict(self) -> dict[str, Any]:
804
+ return dict(self._mapping())
805
+
806
+ def __getattr__(self, name: str) -> Any:
807
+ try:
808
+ value = _lookup_key(self._mapping(), name)
809
+ except KeyError as exc:
810
+ raise AttributeError(name) from exc
811
+ return _wrap(value)
812
+
813
+ def __bool__(self) -> bool:
814
+ return bool(self._payload)
815
+
816
+ def __iter__(self):
817
+ return iter(self._mapping())
818
+
819
+ def items(self):
820
+ return self._mapping().items()
821
+
822
+
823
+ def _wrap(value: Any) -> Any:
824
+ if isinstance(value, dict):
825
+ return NamespaceProxy(value)
826
+ return value
827
+
828
+
829
+ def _read_app_id(app_id: object, *, action: str) -> str:
830
+ if not isinstance(app_id, str) or not app_id.strip():
831
+ raise PzError(
832
+ code="PZ_APP_SETTINGS_IDENTIFIER_INVALID",
833
+ message="app_id must be a non-empty string.",
834
+ stage="settings",
835
+ owner="runtime.settings",
836
+ action=action,
837
+ )
838
+ return app_id.strip()
839
+
840
+
841
+ def resolve_settings(project_dir: str | None = None) -> ResolvedSettings:
842
+ effective_project_dir = _effective_project_dir(project_dir)
843
+ snapshot = ensure_full_settings(project_dir=effective_project_dir)
844
+ resolved_project_dir = effective_project_dir or _snapshot_project_dir(snapshot)
845
+ cache_key = (id(snapshot), resolved_project_dir)
846
+ cached = _RESOLVED_SETTINGS_CACHE.get(cache_key)
847
+ if cached is not None and cached[0] is snapshot:
848
+ return cached[1]
849
+ resolved = ResolvedSettings(
850
+ snapshot=snapshot,
851
+ project_dir=resolved_project_dir,
852
+ )
853
+ _RESOLVED_SETTINGS_CACHE[cache_key] = (snapshot, resolved)
854
+ return resolved
855
+
856
+
857
+ def ensure_full_settings(_current: Any = None, *, project_dir: str | None = None):
858
+ effective_project_dir = _effective_project_dir(project_dir)
859
+ snapshot = _resolve_bound_snapshot(effective_project_dir)
860
+ if snapshot is not None:
861
+ return snapshot
862
+ return _load_snapshot(project_dir=effective_project_dir)
863
+
864
+ __all__ = [
865
+ "active_settings_project",
866
+ "bound_settings_snapshot",
867
+ "build_local_default_settings",
868
+ "build_local_default_settings_chain",
869
+ "bind_settings_snapshot",
870
+ "current_pz_env",
871
+ "current_settings_project_dir",
872
+ "ensure_full_settings",
873
+ "get_route_token_catalog",
874
+ "has_runtime_env_value",
875
+ "iter_runtime_env_items",
876
+ "load_local_default_settings",
877
+ "load_local_default_settings_chain",
878
+ "load_project_env_values",
879
+ "ResolvedSettings",
880
+ "resolve_settings",
881
+ "register_route_token_catalog_provider",
882
+ "read_runtime_env_bool",
883
+ "read_runtime_env_float",
884
+ "read_runtime_env_int",
885
+ "read_runtime_env_string",
886
+ "reset_route_token_catalog_provider",
887
+ "reset_settings_binding",
888
+ ]