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,790 @@
1
+ from __future__ import annotations
2
+
3
+ from contextlib import asynccontextmanager
4
+ from dataclasses import replace
5
+ import importlib
6
+ from pathlib import Path
7
+ from types import SimpleNamespace
8
+ from typing import Any
9
+
10
+ from fastapi import APIRouter, FastAPI
11
+ from fastapi.encoders import jsonable_encoder
12
+ from fastapi.responses import JSONResponse
13
+ from fastapi.routing import APIRoute
14
+ from fastapi.staticfiles import StaticFiles
15
+ from starlette.routing import compile_path
16
+
17
+ from pz_core.assembly.project import active_project_import_root
18
+ from pz_settings_center.public import active_settings_project, bound_settings_snapshot
19
+
20
+ from ..runtime import bind_runtime_context, reset_runtime_context
21
+ from ..routing import (
22
+ DeclaredUrlSpec,
23
+ build_route_registry_entry,
24
+ clean_and_reorder_routes,
25
+ compile_route_manifest,
26
+ graft_router,
27
+ load_declared_url_specs,
28
+ )
29
+ from ..routing.canonical import is_file_like_path
30
+ from ..routing.exports import _merge_lifespan_context
31
+ from ..routing.manifest import require_foundation_url_manifest, route_path_decls_from_router
32
+ from ..routing.utils import _join_path_prefix
33
+ from .db.request_adapter import wrap_db_request_scope
34
+ from .lifespan import start_infra_background_tasks
35
+
36
+
37
+ class _HostAsgiWrapper:
38
+ def __init__(self, app: FastAPI, wrapped: Any):
39
+ self._app = app
40
+ self._wrapped = wrapped
41
+ self.state = app.state
42
+ self.router = app.router
43
+ self.title = getattr(app, "title", "")
44
+
45
+ def __getattr__(self, name: str) -> Any:
46
+ return getattr(self._app, name)
47
+
48
+ async def __call__(self, scope: dict[str, Any], receive: Any, send: Any) -> None:
49
+ await self._wrapped(scope, receive, send)
50
+
51
+
52
+ def _bind_host_state_surface(target: Any, app: FastAPI) -> Any:
53
+ for key, value in (
54
+ ("state", app.state),
55
+ ("router", app.router),
56
+ ("title", getattr(app, "title", "")),
57
+ ("_pz_host_app", app),
58
+ ):
59
+ try:
60
+ setattr(target, key, value)
61
+ except Exception:
62
+ continue
63
+ return target
64
+
65
+ def _resolve_dotted(target: Any) -> Any:
66
+ if not isinstance(target, str) or not target.strip():
67
+ return target
68
+ raw = target.strip()
69
+ if ":" in raw:
70
+ module_name, attr_name = raw.split(":", 1)
71
+ else:
72
+ module_name, attr_name = raw.rsplit(".", 1)
73
+ module = importlib.import_module(module_name)
74
+ return getattr(module, attr_name)
75
+
76
+ def _iter_payload_items(payload: Any) -> list[Any]:
77
+ if payload is None:
78
+ return []
79
+ if isinstance(payload, (list, tuple)):
80
+ return [item for item in payload if item is not None]
81
+ return [payload]
82
+
83
+
84
+ def _load_host_extensions(plan: Any, *, kinds: set[str] | None = None) -> list[Any]:
85
+ loaded: list[Any] = []
86
+ for extension in plan.extension_registry.extensions:
87
+ kind = str(getattr(extension, "kind", "") or "").strip()
88
+ if kinds is not None and kind not in kinds:
89
+ continue
90
+ loaded.append(
91
+ SimpleNamespace(
92
+ id=getattr(extension, "id", ""),
93
+ owner=getattr(extension, "owner_app", ""),
94
+ kind=kind,
95
+ title=getattr(extension, "title", ""),
96
+ priority=int(getattr(extension, "priority", 100) or 100),
97
+ payload=dict(getattr(extension, "payload", None) or {}),
98
+ source=getattr(extension, "source", ""),
99
+ provides=tuple(getattr(extension, "provides", ()) or ()),
100
+ conflicts=tuple(getattr(extension, "conflicts", ()) or ()),
101
+ )
102
+ )
103
+ loaded.sort(key=lambda ext: (int(getattr(ext, "priority", 100) or 100), str(getattr(ext, "id", "") or "")))
104
+ return loaded
105
+
106
+
107
+ def _router_has_catchall(router: Any) -> bool:
108
+ for route in getattr(router, "routes", []) or []:
109
+ path = str(getattr(route, "path", "") or "")
110
+ if ":path}" in path:
111
+ return True
112
+ return False
113
+
114
+
115
+ def _set_route_path(route: APIRoute, path: str) -> None:
116
+ path_regex, path_format, param_convertors = compile_path(path)
117
+ route.path = path
118
+ route.path_regex = path_regex
119
+ route.path_format = path_format
120
+ route.param_convertors = param_convertors
121
+ dependant = getattr(route, "dependant", None)
122
+ if dependant is not None:
123
+ try:
124
+ dependant.path = path_format
125
+ except Exception:
126
+ pass
127
+
128
+
129
+ def _is_directory_page_route(route: APIRoute, *, path_prefix: str = "") -> bool:
130
+ path = str(getattr(route, "path", "") or "").strip()
131
+ if not path or path == "/" or path.endswith("/"):
132
+ return False
133
+ effective_path = _join_path_prefix(path_prefix, path)
134
+ if (
135
+ effective_path.startswith("/api/")
136
+ or effective_path.startswith("/static/")
137
+ or is_file_like_path(effective_path)
138
+ ):
139
+ return False
140
+ methods = {str(method).upper() for method in (getattr(route, "methods", None) or ())}
141
+ if methods and not methods.issubset({"GET", "HEAD"}):
142
+ return False
143
+ return effective_path == "/admin" or effective_path.startswith("/admin/")
144
+
145
+
146
+ def _normalize_directory_page_routes(router: APIRouter, *, path_prefix: str = "") -> None:
147
+ for route in list(getattr(router, "routes", []) or []):
148
+ if not isinstance(route, APIRoute) or not _is_directory_page_route(route, path_prefix=path_prefix):
149
+ continue
150
+ path = str(getattr(route, "path", "") or "").strip()
151
+ _set_route_path(route, f"{path}/")
152
+
153
+
154
+ def _ensure_route_registry(app: FastAPI) -> list[dict[str, Any]]:
155
+ registry = getattr(app.state, "pz_route_registry", None)
156
+ if isinstance(registry, list):
157
+ return registry
158
+ registry = []
159
+ app.state.pz_route_registry = registry
160
+ return registry
161
+
162
+
163
+ def _record_route_mount(
164
+ app: FastAPI,
165
+ *,
166
+ namespace: str,
167
+ path_prefix: str,
168
+ router: APIRouter,
169
+ source: str,
170
+ route_hints: tuple[str, ...] = (),
171
+ conflict_policy: str = "fail",
172
+ ) -> None:
173
+ registry = _ensure_route_registry(app)
174
+ mounted_paths: list[str] = []
175
+ for route in getattr(router, "routes", ()) or ():
176
+ path = getattr(route, "path", None)
177
+ if isinstance(path, str) and path:
178
+ mounted_paths.append(path)
179
+ try:
180
+ setattr(route, "pz_route_namespace", namespace)
181
+ setattr(route, "pz_route_source", source)
182
+ except Exception:
183
+ continue
184
+ registry.append(
185
+ {
186
+ "namespace": namespace,
187
+ "path_prefix": path_prefix,
188
+ "source": source,
189
+ "paths": tuple(mounted_paths),
190
+ "route_hints": tuple(route_hints or ()),
191
+ "conflict_policy": str(conflict_policy or "fail"),
192
+ }
193
+ )
194
+
195
+
196
+ def _validate_route_registry(app: FastAPI) -> None:
197
+ registry = _ensure_route_registry(app)
198
+ seen: dict[str, dict[str, Any]] = {}
199
+ for item in registry:
200
+ namespace = str(item.get("namespace") or "").strip()
201
+ if not namespace:
202
+ raise RuntimeError("mounted route specs must carry a non-empty namespace.")
203
+ previous = seen.get(namespace)
204
+ if previous is not None:
205
+ raise RuntimeError(
206
+ "duplicate mounted route namespace "
207
+ f"{namespace!r}: {previous.get('source', '<unknown>')} and {item.get('source', '<unknown>')}"
208
+ )
209
+ seen[namespace] = item
210
+
211
+
212
+ def _host_route_routers(
213
+ *,
214
+ app: FastAPI,
215
+ runtime: Any,
216
+ ) -> list[DeclaredUrlSpec]:
217
+ routers: list[tuple[int, DeclaredUrlSpec]] = []
218
+ for ext in _load_host_extensions(runtime.plan, kinds={"host.route"}):
219
+ payload = dict(getattr(ext, "payload", {}) or {})
220
+ target = _resolve_dotted(payload.get("router"))
221
+ if target is None:
222
+ raise RuntimeError(f"Host route extension '{getattr(ext, 'id', '<unknown>')}' must declare payload.router.")
223
+ if isinstance(target, APIRouter):
224
+ resolved = target
225
+ else:
226
+ resolved = target(
227
+ app=app,
228
+ host=app,
229
+ runtime=runtime,
230
+ plan=runtime.plan,
231
+ bindings=runtime.bindings,
232
+ )
233
+ path_prefix = str(payload.get("path_prefix") or "").strip()
234
+ for router in _iter_payload_items(resolved):
235
+ if not isinstance(router, APIRouter):
236
+ raise RuntimeError(
237
+ f"Host route extension '{getattr(ext, 'id', '<unknown>')}' must resolve to APIRouter instances."
238
+ )
239
+ _normalize_directory_page_routes(router, path_prefix=path_prefix)
240
+ routers.append(
241
+ (
242
+ int(getattr(ext, "priority", 100) or 100),
243
+ DeclaredUrlSpec(
244
+ router=router,
245
+ path_prefix=path_prefix,
246
+ namespace=str(getattr(ext, "id", "") or getattr(ext, "owner", "") or "host.route"),
247
+ ),
248
+ )
249
+ )
250
+ return [router for _, router in sorted(routers, key=lambda item: item[0])]
251
+
252
+
253
+ def _iter_router_paths(router: APIRouter) -> list[str]:
254
+ out: list[str] = []
255
+ for route in list(getattr(router, "routes", []) or []):
256
+ path = getattr(route, "path", None)
257
+ if isinstance(path, str) and path:
258
+ out.append(path)
259
+ return out
260
+
261
+
262
+ def _admin_panel_routers(
263
+ *,
264
+ app: FastAPI,
265
+ runtime: Any,
266
+ ) -> list[DeclaredUrlSpec]:
267
+ routers: list[tuple[int, DeclaredUrlSpec]] = []
268
+ for ext in _load_host_extensions(runtime.plan, kinds={"admin.panel"}):
269
+ payload = dict(getattr(ext, "payload", {}) or {})
270
+ target = _resolve_dotted(payload.get("router"))
271
+ if target is None:
272
+ continue
273
+ if isinstance(target, APIRouter):
274
+ resolved = target
275
+ else:
276
+ resolved = target(
277
+ app=app,
278
+ host=app,
279
+ runtime=runtime,
280
+ plan=runtime.plan,
281
+ bindings=runtime.bindings,
282
+ )
283
+ for router in _iter_payload_items(resolved):
284
+ if not isinstance(router, APIRouter):
285
+ continue
286
+ route_paths = _iter_router_paths(router)
287
+ if not route_paths or not all(path.startswith("/admin") for path in route_paths):
288
+ continue
289
+ _normalize_directory_page_routes(router)
290
+ routers.append(
291
+ (
292
+ int(getattr(ext, "priority", 100) or 100),
293
+ DeclaredUrlSpec(
294
+ router=router,
295
+ path_prefix="",
296
+ namespace=str(getattr(ext, "id", "") or getattr(ext, "owner", "") or "admin.panel"),
297
+ ),
298
+ )
299
+ )
300
+ return [router for _, router in sorted(routers, key=lambda item: item[0])]
301
+
302
+
303
+ def _declared_url_specs_from_manifest(runtime: Any) -> list[tuple[str, DeclaredUrlSpec]]:
304
+ manifest = require_foundation_url_manifest(getattr(runtime, "plan", None))
305
+ specs: list[tuple[str, DeclaredUrlSpec]] = []
306
+ specs_by_namespace: dict[str, DeclaredUrlSpec] = {}
307
+ loaded_by_entrypoint: dict[str, tuple[DeclaredUrlSpec, ...]] = {}
308
+ for entry in manifest.entries:
309
+ entrypoint = str(entry.entrypoint or "").strip()
310
+ if not entrypoint:
311
+ raise RuntimeError(f"compiled URL manifest entry {entry.namespace!r} is missing its app entrypoint.")
312
+ if entrypoint in loaded_by_entrypoint:
313
+ continue
314
+ declared_url_specs = load_declared_url_specs(entrypoint)
315
+ loaded_by_entrypoint[entrypoint] = declared_url_specs
316
+ for spec in declared_url_specs:
317
+ specs_by_namespace[str(spec.namespace or "").strip()] = spec
318
+ for entry in manifest.entries:
319
+ spec = specs_by_namespace.get(str(entry.namespace or "").strip())
320
+ if spec is None:
321
+ raise RuntimeError(
322
+ f"compiled URL manifest entry {entry.namespace!r} does not have a matching materialized router."
323
+ )
324
+ if route_path_decls_from_router(spec.router) != tuple(entry.routes):
325
+ raise RuntimeError(
326
+ f"materialized router for compiled URL manifest entry {entry.namespace!r} "
327
+ "does not match the RuntimePlan URL manifest."
328
+ )
329
+ specs.append((str(entry.owner_app or ""), spec))
330
+ return specs
331
+
332
+
333
+ def _mount_app_routers(
334
+ app: FastAPI,
335
+ *,
336
+ runtime: Any,
337
+ ) -> None:
338
+ normal_routers: list[DeclaredUrlSpec] = []
339
+ catchall_routers: list[DeclaredUrlSpec] = []
340
+ registry_entries = []
341
+ manifest_specs = _declared_url_specs_from_manifest(runtime)
342
+ for owner_app, declared_spec in manifest_specs:
343
+ if not isinstance(declared_spec, DeclaredUrlSpec):
344
+ raise RuntimeError(f"router mount for {owner_app} must resolve to DeclaredUrlSpec instances.")
345
+ router = declared_spec.router
346
+ if not isinstance(router, APIRouter):
347
+ raise RuntimeError(f"router mount for {owner_app} must resolve to APIRouter instances.")
348
+ _normalize_directory_page_routes(router, path_prefix=str(declared_spec.path_prefix or ""))
349
+ spec = DeclaredUrlSpec(
350
+ router=router,
351
+ path_prefix=str(declared_spec.path_prefix or ""),
352
+ dependencies=tuple(declared_spec.dependencies or ()),
353
+ namespace=str(declared_spec.namespace or owner_app),
354
+ replace_target=str(declared_spec.replace_target or ""),
355
+ route_hints=tuple(declared_spec.route_hints or ()),
356
+ conflict_policy=str(declared_spec.conflict_policy or "fail"),
357
+ )
358
+ registry_entries.append(
359
+ build_route_registry_entry(
360
+ owner_app=str(owner_app),
361
+ spec=spec,
362
+ )
363
+ )
364
+ bucket = catchall_routers if _router_has_catchall(router) else normal_routers
365
+ bucket.append(spec)
366
+ for spec in _host_route_routers(app=app, runtime=runtime):
367
+ registry_entries.append(
368
+ build_route_registry_entry(
369
+ owner_app=str(spec.namespace or "host.route"),
370
+ spec=spec,
371
+ route_hints=("host.route",),
372
+ )
373
+ )
374
+ bucket = catchall_routers if _router_has_catchall(spec.router) else normal_routers
375
+ bucket.append(spec)
376
+ for spec in _admin_panel_routers(app=app, runtime=runtime):
377
+ registry_entries.append(
378
+ build_route_registry_entry(
379
+ owner_app=str(spec.namespace or "admin.panel"),
380
+ spec=spec,
381
+ route_hints=("admin.panel",),
382
+ )
383
+ )
384
+ bucket = catchall_routers if _router_has_catchall(spec.router) else normal_routers
385
+ bucket.append(spec)
386
+ app.state.pz_route_manifest = compile_route_manifest(registry_entries)
387
+ app.state.pz_compiled_route_registry = app.state.pz_route_manifest.entries
388
+ for spec in normal_routers + catchall_routers:
389
+ graft_router(
390
+ app,
391
+ spec.router,
392
+ path_prefix=spec.path_prefix,
393
+ dependencies=spec.dependencies,
394
+ )
395
+ _record_route_mount(
396
+ app,
397
+ namespace=spec.namespace,
398
+ path_prefix=spec.path_prefix,
399
+ router=spec.router,
400
+ source=spec.namespace,
401
+ route_hints=tuple(spec.route_hints or ()),
402
+ conflict_policy=str(spec.conflict_policy or "fail"),
403
+ )
404
+ _validate_route_registry(app)
405
+
406
+
407
+ def _attach_runtime_state(
408
+ app: FastAPI,
409
+ *,
410
+ project_dir: Path,
411
+ plan: Any,
412
+ bindings: Any,
413
+ lifecycle_state: Any,
414
+ settings_snapshot: Any,
415
+ bind_context: bool = True,
416
+ ) -> None:
417
+ app.state.project_dir = str(project_dir)
418
+ app.state.app_name = str(settings_snapshot.get("project", "name") or "PZ Project")
419
+ app.state.settings_snapshot = settings_snapshot
420
+ app.state.pz_runtime_plan = plan
421
+ app.state.pz_runtime_bindings = bindings
422
+ app.state.pz_runtime_state = lifecycle_state
423
+ if bind_context:
424
+ owner = getattr(app.state, "pz_foundation_owner", None)
425
+ if owner:
426
+ bind_runtime_context(
427
+ owner=str(owner),
428
+ project_dir=project_dir,
429
+ bindings=bindings,
430
+ plan=plan,
431
+ lifecycle_state=lifecycle_state,
432
+ )
433
+
434
+
435
+ def _to_jsonable(value: Any) -> Any:
436
+ return jsonable_encoder(value)
437
+
438
+
439
+ def _requires_database_readiness(payload: dict[str, Any]) -> bool:
440
+ statuses = payload.get("capability_statuses")
441
+ if not isinstance(statuses, dict):
442
+ return False
443
+ return statuses.get("database") not in (None, "disabled")
444
+
445
+
446
+ def _database_lifecycle_payload() -> dict[str, Any]:
447
+ try:
448
+ from ..db.lifecycle_status import connection_lifecycle_status
449
+ from ..db.tortoise_manager import tortoise_manager
450
+
451
+ status = connection_lifecycle_status(tortoise_manager)
452
+ except Exception as exc: # noqa: BLE001
453
+ return {"default_ready": False, "status": "lookup_failed", "error_type": type(exc).__name__}
454
+ payload = _to_jsonable(status)
455
+ if isinstance(payload, dict):
456
+ payload["default_ready"] = status.default_ready
457
+ payload["read_only_ready"] = status.read_only_ready
458
+ return payload
459
+ return {"default_ready": bool(status.default_ready), "read_only_ready": bool(status.read_only_ready)}
460
+
461
+
462
+ def _apply_readiness_lifecycle(payload: dict[str, Any]) -> dict[str, Any]:
463
+ if not _requires_database_readiness(payload):
464
+ return payload
465
+ lifecycle = _database_lifecycle_payload()
466
+ payload["database_lifecycle"] = lifecycle
467
+ if bool(lifecycle.get("default_ready")):
468
+ return payload
469
+ payload["status"] = "unavailable"
470
+ statuses = payload.get("capability_statuses")
471
+ if isinstance(statuses, dict):
472
+ statuses["database"] = "unavailable"
473
+ unavailable = list(payload.get("unavailable_capabilities") or ())
474
+ if "database" not in unavailable:
475
+ unavailable.append("database")
476
+ payload["unavailable_capabilities"] = unavailable
477
+ return payload
478
+
479
+
480
+ def _has_route_path(app: FastAPI, path: str) -> bool:
481
+ for route in getattr(app.router, "routes", []) or []:
482
+ if getattr(route, "path", None) == path:
483
+ return True
484
+ return False
485
+
486
+
487
+ def _resource_output_dir(manifest: Any, name: str) -> Path:
488
+ for item in getattr(manifest, "items", ()) or ():
489
+ if str(getattr(item, "name", "") or "").strip() == name:
490
+ output_dir = getattr(item, "output_dir", None)
491
+ if output_dir is not None:
492
+ return Path(output_dir)
493
+ raise RuntimeError(f"runtime resource item '{name}' is missing from manifest")
494
+
495
+
496
+ def _mount_runtime_static(app: FastAPI, *, bindings: Any) -> None:
497
+ if _has_route_path(app, "/static"):
498
+ return
499
+ manifest = bindings.resources.manifest()
500
+ static_dir = _resource_output_dir(manifest, "static")
501
+ if not static_dir.exists():
502
+ raise RuntimeError("runtime static resources must be built before mounting '/static'")
503
+ app.mount("/static", StaticFiles(directory=str(static_dir), check_dir=False), name="static")
504
+
505
+
506
+ def _build_runtime_resources(*, plan: Any, bindings: Any, host: object | None = None) -> None:
507
+ bindings.providers.activate_declared(
508
+ plan=plan,
509
+ bindings=bindings,
510
+ host=host,
511
+ owner="pz_foundation",
512
+ )
513
+
514
+
515
+ def _apply_host_state_extensions(
516
+ app: FastAPI,
517
+ *,
518
+ runtime: Any,
519
+ ) -> None:
520
+ extensions = _load_host_extensions(runtime.plan, kinds={"host.state"})
521
+ for ext in extensions:
522
+ payload = dict(getattr(ext, "payload", {}) or {})
523
+ if "values" in payload:
524
+ values = dict(payload.get("values") or {})
525
+ else:
526
+ provider = _resolve_dotted(payload.get("provider"))
527
+ values = dict(
528
+ provider(
529
+ app=app,
530
+ host=app,
531
+ runtime=runtime,
532
+ plan=runtime.plan,
533
+ bindings=runtime.bindings,
534
+ )
535
+ or {}
536
+ )
537
+ for key, value in values.items():
538
+ setattr(app.state, str(key), value)
539
+
540
+
541
+ def _apply_host_route_extensions(
542
+ app: FastAPI,
543
+ *,
544
+ runtime: Any,
545
+ ) -> None:
546
+ for spec in _host_route_routers(app=app, runtime=runtime):
547
+ graft_router(
548
+ app,
549
+ spec.router,
550
+ path_prefix=spec.path_prefix,
551
+ dependencies=spec.dependencies,
552
+ )
553
+ _record_route_mount(
554
+ app,
555
+ namespace=spec.namespace,
556
+ path_prefix=spec.path_prefix,
557
+ router=spec.router,
558
+ source=spec.namespace,
559
+ route_hints=tuple(spec.route_hints or ()),
560
+ conflict_policy=str(spec.conflict_policy or "fail"),
561
+ )
562
+ _validate_route_registry(app)
563
+
564
+
565
+ def _apply_host_mount_extensions(
566
+ app: FastAPI,
567
+ *,
568
+ runtime: Any,
569
+ ) -> None:
570
+ kinds = {"host.mount", "host.static"}
571
+ extensions = _load_host_extensions(runtime.plan, kinds=kinds)
572
+ project_dir = Path(getattr(runtime.plan.project, "project_dir", ".")).resolve()
573
+ for ext in extensions:
574
+ payload = dict(getattr(ext, "payload", {}) or {})
575
+ path = str(payload.get("path") or "").strip()
576
+ if not path:
577
+ raise RuntimeError(f"Host mount extension '{getattr(ext, 'id', '<unknown>')}' must declare payload.path.")
578
+ name = str(payload.get("name") or getattr(ext, "id", "host-mount"))
579
+ if getattr(ext, "kind", "") == "host.static":
580
+ raw_directory = str(payload.get("directory") or "").strip()
581
+ if not raw_directory:
582
+ raise RuntimeError(
583
+ f"Host static extension '{getattr(ext, 'id', '<unknown>')}' must declare payload.directory."
584
+ )
585
+ directory = Path(raw_directory)
586
+ if not directory.is_absolute():
587
+ directory = (project_dir / directory).resolve()
588
+ mounted = StaticFiles(
589
+ directory=str(directory),
590
+ check_dir=bool(payload.get("check_dir", False)),
591
+ )
592
+ else:
593
+ target = _resolve_dotted(payload.get("app"))
594
+ if target is None:
595
+ raise RuntimeError(
596
+ f"Host mount extension '{getattr(ext, 'id', '<unknown>')}' must declare payload.app."
597
+ )
598
+ if isinstance(target, (FastAPI, StaticFiles)):
599
+ mounted = target
600
+ else:
601
+ mounted = target(
602
+ app=app,
603
+ host=app,
604
+ runtime=runtime,
605
+ plan=runtime.plan,
606
+ bindings=runtime.bindings,
607
+ )
608
+ app.mount(path, mounted, name=name)
609
+
610
+
611
+ def _build_http_app(
612
+ *,
613
+ plan: Any,
614
+ lifecycle_state: Any,
615
+ bindings: Any,
616
+ project_dir: Path,
617
+ health_facade_cls,
618
+ activate_runtime_plan,
619
+ shutdown_runtime_plan,
620
+ apply_runtime_middlewares,
621
+ active_runtime_plan,
622
+ ) -> Any:
623
+ settings_snapshot = plan.settings
624
+ runtime_refs: dict[str, Any] = {}
625
+
626
+ _build_runtime_resources(plan=plan, bindings=bindings)
627
+
628
+ @asynccontextmanager
629
+ async def _lifespan(host_app: FastAPI):
630
+ started = False
631
+ try:
632
+ with (
633
+ active_settings_project(str(project_dir)),
634
+ active_runtime_plan(plan),
635
+ active_project_import_root(
636
+ project_dir,
637
+ extra_import_roots=getattr(settings_snapshot, "import_roots", ()),
638
+ preserve_loaded_modules=True,
639
+ ),
640
+ ):
641
+ started_state = await activate_runtime_plan(
642
+ plan,
643
+ lifecycle_state,
644
+ bindings=bindings,
645
+ host=host_app,
646
+ )
647
+ started_bindings = replace(
648
+ bindings,
649
+ health=health_facade_cls(plan, started_state, providers=bindings.providers),
650
+ )
651
+ started_runtime = SimpleNamespace(
652
+ plan=plan,
653
+ state=started_state,
654
+ bindings=started_bindings,
655
+ )
656
+ runtime_refs["started_state"] = started_state
657
+ runtime_refs["started_bindings"] = started_bindings
658
+ _attach_runtime_state(
659
+ host_app,
660
+ project_dir=project_dir,
661
+ plan=plan,
662
+ bindings=started_bindings,
663
+ lifecycle_state=started_state,
664
+ settings_snapshot=settings_snapshot,
665
+ )
666
+ start_infra_background_tasks(
667
+ getattr(host_app.state, "pz_foundation_lifecycle_infra_context", None)
668
+ )
669
+ _apply_host_state_extensions(host_app, runtime=started_runtime)
670
+ wrapped = apply_runtime_middlewares(
671
+ host_app,
672
+ plan=plan,
673
+ bindings=started_bindings,
674
+ )
675
+ _bind_host_state_surface(wrapped, host_app)
676
+ wrapped = wrap_db_request_scope(wrapped, project_dir=project_dir)
677
+ _bind_host_state_surface(wrapped, host_app)
678
+ runtime_refs["wrapped_app"] = wrapped
679
+ started = True
680
+ yield
681
+ finally:
682
+ if started:
683
+ owner = str(getattr(host_app.state, "pz_foundation_owner", "pz_foundation"))
684
+ current_bindings = getattr(
685
+ host_app.state,
686
+ "pz_runtime_bindings",
687
+ runtime_refs.get("started_bindings"),
688
+ )
689
+ current_state = getattr(
690
+ host_app.state,
691
+ "pz_runtime_state",
692
+ runtime_refs.get("started_state"),
693
+ )
694
+ if current_bindings is None or current_state is None:
695
+ return
696
+ stopped_state = await shutdown_runtime_plan(
697
+ plan,
698
+ current_state,
699
+ bindings=current_bindings,
700
+ host=host_app,
701
+ )
702
+ stopped_bindings = replace(
703
+ current_bindings,
704
+ health=health_facade_cls(plan, stopped_state, providers=current_bindings.providers),
705
+ )
706
+ _attach_runtime_state(
707
+ host_app,
708
+ project_dir=project_dir,
709
+ plan=plan,
710
+ bindings=stopped_bindings,
711
+ lifecycle_state=stopped_state,
712
+ settings_snapshot=settings_snapshot,
713
+ bind_context=False,
714
+ )
715
+ reset_runtime_context(owner=owner)
716
+
717
+ project_http = settings_snapshot.get("project", "http", default={}) or {}
718
+ if hasattr(project_http, "to_dict") and callable(project_http.to_dict):
719
+ project_http = project_http.to_dict()
720
+ if not isinstance(project_http, dict):
721
+ project_http = {}
722
+ app = FastAPI(
723
+ title=str(settings_snapshot.get("project", "name") or "Manga Translate"),
724
+ **dict(project_http),
725
+ )
726
+ app.router.redirect_slashes = False
727
+ app.router.lifespan_context = _merge_lifespan_context(_lifespan, app.router.lifespan_context)
728
+
729
+ app.state.pz_foundation_owner = f"pz_foundation:{id(app)}"
730
+ _attach_runtime_state(
731
+ app,
732
+ project_dir=project_dir,
733
+ plan=plan,
734
+ bindings=bindings,
735
+ lifecycle_state=lifecycle_state,
736
+ settings_snapshot=settings_snapshot,
737
+ )
738
+ _mount_runtime_static(app, bindings=bindings)
739
+ runtime_refs["wrapped_app"] = app
740
+
741
+ @app.get("/health")
742
+ async def _health() -> JSONResponse:
743
+ snapshot = app.state.pz_runtime_bindings.health.liveness()
744
+ return JSONResponse(_to_jsonable(snapshot))
745
+
746
+ @app.get("/live")
747
+ async def _live() -> JSONResponse:
748
+ snapshot = app.state.pz_runtime_bindings.health.liveness()
749
+ return JSONResponse(_to_jsonable(snapshot))
750
+
751
+ @app.get("/ready")
752
+ async def _ready() -> JSONResponse:
753
+ snapshot = app.state.pz_runtime_bindings.health.readiness()
754
+ payload = _apply_readiness_lifecycle(_to_jsonable(snapshot))
755
+ status_code = 200 if payload.get("status") in {"ok", "disabled"} else 503
756
+ return JSONResponse(payload, status_code=status_code)
757
+
758
+ with (
759
+ active_settings_project(str(project_dir)),
760
+ active_runtime_plan(plan),
761
+ active_project_import_root(
762
+ project_dir,
763
+ extra_import_roots=getattr(settings_snapshot, "import_roots", ()),
764
+ preserve_loaded_modules=True,
765
+ ),
766
+ ):
767
+ pre_start_runtime = SimpleNamespace(
768
+ plan=plan,
769
+ state=lifecycle_state,
770
+ bindings=bindings,
771
+ )
772
+ _apply_host_mount_extensions(app, runtime=pre_start_runtime)
773
+ _mount_app_routers(app, runtime=pre_start_runtime)
774
+ clean_and_reorder_routes(app)
775
+
776
+ async def _scoped(scope, receive, send):
777
+ with (
778
+ bound_settings_snapshot(plan.settings, project_dir=str(project_dir)),
779
+ active_settings_project(str(project_dir)),
780
+ active_runtime_plan(plan),
781
+ ):
782
+ wrapped_app = runtime_refs.get("wrapped_app", app)
783
+ await wrapped_app(scope, receive, send)
784
+
785
+ _bind_host_state_surface(_scoped, app)
786
+
787
+ return _HostAsgiWrapper(app, _scoped)
788
+ __all__ = [
789
+ "_build_http_app",
790
+ ]