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,824 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from dataclasses import dataclass
5
+ from importlib import import_module
6
+ import inspect
7
+ from pathlib import Path
8
+ from types import ModuleType, SimpleNamespace
9
+ from typing import Any
10
+
11
+ from pz_core.assembly.app_discovery import (
12
+ discover_leaf_app_packages,
13
+ import_app_entrypoint_module_from_decl,
14
+ read_static_app_declaration,
15
+ )
16
+ from pz_core.assembly.project import active_project_import_root
17
+ from pz_core.assembly.settings import SettingsSnapshot
18
+ from pz_core.core.errors import PzError
19
+ from pz_core.runtime.environment_integrity import validate_installed_app_identities
20
+
21
+
22
+ @dataclass(frozen=True, slots=True)
23
+ class AppDecl:
24
+ app_id: str
25
+ name: str
26
+ version: str
27
+ required_pz: str
28
+ module_path: str
29
+ entrypoint: str
30
+ owner: str
31
+ priority: int = 100
32
+ requires_apps: tuple[str, ...] = ()
33
+ required_protocols: tuple[str, ...] = ()
34
+
35
+
36
+ @dataclass(frozen=True, slots=True)
37
+ class AppRegistry:
38
+ apps: tuple[AppDecl, ...]
39
+ initialized_app_ids: tuple[str, ...] = ()
40
+
41
+ def by_id(self) -> dict[str, AppDecl]:
42
+ return {app.app_id: app for app in self.apps}
43
+
44
+
45
+ @dataclass(frozen=True, slots=True)
46
+ class AppInitContext:
47
+ app: AppDecl
48
+ settings: SettingsSnapshot
49
+ project_dir: Path | None
50
+ phase: str = "declared"
51
+
52
+
53
+ _DEFAULT_APP_PRIORITY = 100
54
+
55
+
56
+ def _loaded_pz_version() -> str:
57
+ from pz_core import __version__
58
+
59
+ return __version__
60
+
61
+
62
+ def _read_app_module_path(module_path: object, *, owner: str) -> str:
63
+ if not isinstance(module_path, str) or not module_path.strip():
64
+ raise PzError(
65
+ code="PZ_APP_INVALID_PATH",
66
+ message="installed_apps contains an empty or invalid app path.",
67
+ stage="apps",
68
+ owner=owner,
69
+ action="compile_app_registry",
70
+ )
71
+ return module_path.strip()
72
+
73
+
74
+ def _normalize_app_path(module_path: str) -> str:
75
+ value = _read_app_module_path(module_path, owner="apps")
76
+ if ":" in value or value.endswith(".apps"):
77
+ raise PzError(
78
+ code="PZ_APP_INVALID_PATH",
79
+ message=(
80
+ f"App path '{value}' must declare a directory/package name only; "
81
+ "installed_apps does not accept '<pkg>.apps' or '<pkg>.apps:app'."
82
+ ),
83
+ stage="apps",
84
+ owner=value,
85
+ action="compile_app_registry",
86
+ )
87
+ return f"{value}.apps:app"
88
+
89
+
90
+ def _import_app_module(entrypoint: str) -> tuple[ModuleType, Any]:
91
+ module_name, _, attr_name = entrypoint.partition(":")
92
+ try:
93
+ module = import_module(module_name)
94
+ except Exception as exc:
95
+ raise PzError(
96
+ code="PZ_APP_IMPORT_FAILED",
97
+ message=f"Failed to import app module '{module_name}': {exc}",
98
+ stage="apps",
99
+ owner=module_name,
100
+ action="compile_app_registry",
101
+ ) from exc
102
+ try:
103
+ app = getattr(module, attr_name)
104
+ except AttributeError as exc:
105
+ raise PzError(
106
+ code="PZ_APP_ENTRY_MISSING",
107
+ message=f"App module '{module_name}' does not expose '{attr_name}'.",
108
+ stage="apps",
109
+ owner=module_name,
110
+ action="compile_app_registry",
111
+ ) from exc
112
+ return module, app
113
+
114
+
115
+ def load_app_object_from_decl(decl: AppDecl) -> Any:
116
+ _, app = _import_app_module(decl.entrypoint)
117
+ return app
118
+
119
+
120
+ def load_app_entrypoint_object_from_decl(decl: AppDecl) -> Any:
121
+ try:
122
+ module = import_app_entrypoint_module_from_decl(
123
+ decl.module_path,
124
+ action="load_app_entrypoint_object",
125
+ )
126
+ except PzError:
127
+ raise
128
+ attr_name = decl.entrypoint.partition(":")[2] or "app"
129
+ try:
130
+ return getattr(module, attr_name)
131
+ except AttributeError as exc:
132
+ raise PzError(
133
+ code="PZ_APP_ENTRY_MISSING",
134
+ message=f"App module '{decl.module_path}.apps' does not expose '{attr_name}'.",
135
+ stage="apps",
136
+ owner=decl.module_path,
137
+ action="load_app_entrypoint_object",
138
+ ) from exc
139
+
140
+
141
+ def _declaration_fingerprint(app: Any) -> tuple[object, ...]:
142
+ requires_apps = getattr(app, "requires_apps", ())
143
+ if isinstance(requires_apps, (list, tuple)):
144
+ normalized_requires_apps = tuple(requires_apps)
145
+ else:
146
+ normalized_requires_apps = requires_apps
147
+
148
+ required_protocols = getattr(app, "required_protocols", ())
149
+ if isinstance(required_protocols, (list, tuple)):
150
+ normalized_required_protocols = tuple(required_protocols)
151
+ else:
152
+ normalized_required_protocols = required_protocols
153
+
154
+ return (
155
+ getattr(app, "id", None),
156
+ getattr(app, "name", None),
157
+ getattr(app, "version", None),
158
+ getattr(app, "required_pz", None),
159
+ getattr(app, "priority", None),
160
+ normalized_requires_apps,
161
+ normalized_required_protocols,
162
+ )
163
+
164
+
165
+ def _read_required_str(app: Any, field_name: str, *, owner: str) -> str:
166
+ value = getattr(app, field_name, None)
167
+ if not isinstance(value, str) or not value.strip():
168
+ raise PzError(
169
+ code=f"PZ_APP_{field_name.upper()}_MISSING",
170
+ message=f"App '{owner}' must declare non-empty '{field_name}'.",
171
+ stage="apps",
172
+ owner=owner,
173
+ action="compile_app_registry",
174
+ )
175
+ return value.strip()
176
+
177
+
178
+ def _read_priority(app: Any, *, owner: str) -> int:
179
+ value = getattr(app, "priority", None)
180
+ if value is None:
181
+ return _DEFAULT_APP_PRIORITY
182
+ if isinstance(value, bool):
183
+ raise PzError(
184
+ code="PZ_APP_PRIORITY_INVALID",
185
+ message=f"App '{owner}' must declare integer priority when provided.",
186
+ stage="apps",
187
+ owner=owner,
188
+ action="compile_app_registry",
189
+ )
190
+ if isinstance(value, int):
191
+ return int(value)
192
+ if isinstance(value, str) and value.strip():
193
+ try:
194
+ return int(value.strip())
195
+ except ValueError as exc:
196
+ raise PzError(
197
+ code="PZ_APP_PRIORITY_INVALID",
198
+ message=f"App '{owner}' must declare integer priority when provided.",
199
+ stage="apps",
200
+ owner=owner,
201
+ action="compile_app_registry",
202
+ ) from exc
203
+ raise PzError(
204
+ code="PZ_APP_PRIORITY_INVALID",
205
+ message=f"App '{owner}' must declare integer priority when provided.",
206
+ stage="apps",
207
+ owner=owner,
208
+ action="compile_app_registry",
209
+ )
210
+
211
+
212
+ def _read_sequence(app: Any, field_name: str) -> tuple[str, ...]:
213
+ value = getattr(app, field_name, ())
214
+ if value is None:
215
+ return ()
216
+ if not isinstance(value, (tuple, list)):
217
+ raise PzError(
218
+ code=f"PZ_APP_{field_name.upper()}_INVALID",
219
+ message=f"App field '{field_name}' must be declared as a list or tuple of strings.",
220
+ stage="apps",
221
+ owner=getattr(app, "id", "<unknown>"),
222
+ action="compile_app_registry",
223
+ )
224
+ out: list[str] = []
225
+ for index, item in enumerate(value):
226
+ if not isinstance(item, str):
227
+ raise PzError(
228
+ code=f"PZ_APP_{field_name.upper()}_ITEM_INVALID",
229
+ message=(
230
+ f"App field '{field_name}' entry at index {index} must be a non-empty string."
231
+ ),
232
+ stage="apps",
233
+ owner=getattr(app, "id", "<unknown>"),
234
+ action="compile_app_registry",
235
+ )
236
+ current = item.strip()
237
+ if not current:
238
+ raise PzError(
239
+ code=f"PZ_APP_{field_name.upper()}_ITEM_INVALID",
240
+ message=(
241
+ f"App field '{field_name}' entry at index {index} must be a non-empty string."
242
+ ),
243
+ stage="apps",
244
+ owner=getattr(app, "id", "<unknown>"),
245
+ action="compile_app_registry",
246
+ )
247
+ out.append(current)
248
+ return tuple(out)
249
+
250
+
251
+ def _parse_version_tuple(value: str, *, owner: str) -> tuple[int, ...]:
252
+ normalized = value.strip()
253
+ if not normalized:
254
+ raise PzError(
255
+ code="PZ_APP_REQUIRED_PZ_INVALID",
256
+ message=f"App '{owner}' declares an empty required_pz version segment.",
257
+ stage="apps",
258
+ owner=owner,
259
+ action="compile_app_registry",
260
+ )
261
+ parts: list[int] = []
262
+ for raw in normalized.split("."):
263
+ segment = raw.strip()
264
+ if not segment or not segment.isdigit():
265
+ raise PzError(
266
+ code="PZ_APP_REQUIRED_PZ_INVALID",
267
+ message=(
268
+ f"App '{owner}' declares invalid required_pz version segment '{raw}'. "
269
+ "Use dot-separated numeric versions."
270
+ ),
271
+ stage="apps",
272
+ owner=owner,
273
+ action="compile_app_registry",
274
+ )
275
+ parts.append(int(segment))
276
+ return tuple(parts)
277
+
278
+
279
+ def _compare_versions(left: str, right: str, *, owner: str) -> int:
280
+ l_parts = list(_parse_version_tuple(left, owner=owner))
281
+ r_parts = list(_parse_version_tuple(right, owner=owner))
282
+ width = max(len(l_parts), len(r_parts), 1)
283
+ l_parts.extend([0] * (width - len(l_parts)))
284
+ r_parts.extend([0] * (width - len(r_parts)))
285
+ if tuple(l_parts) < tuple(r_parts):
286
+ return -1
287
+ if tuple(l_parts) > tuple(r_parts):
288
+ return 1
289
+ return 0
290
+
291
+
292
+ def _satisfies_required_pz(required_pz: str, *, current_pz: str, owner: str) -> bool:
293
+ for raw_spec in required_pz.split(","):
294
+ spec = raw_spec.strip()
295
+ if not spec:
296
+ continue
297
+ for operator in (">=", "<=", "==", "!=", ">", "<"):
298
+ if spec.startswith(operator):
299
+ target = spec[len(operator):].strip()
300
+ if not target:
301
+ raise PzError(
302
+ code="PZ_APP_REQUIRED_PZ_INVALID",
303
+ message=(
304
+ f"App '{owner}' declares invalid required_pz constraint "
305
+ f"'{required_pz}'."
306
+ ),
307
+ stage="apps",
308
+ owner=owner,
309
+ action="compile_app_registry",
310
+ )
311
+ cmp = _compare_versions(current_pz, target, owner=owner)
312
+ if operator == ">=":
313
+ ok = cmp >= 0
314
+ elif operator == "<=":
315
+ ok = cmp <= 0
316
+ elif operator == "==":
317
+ ok = cmp == 0
318
+ elif operator == "!=":
319
+ ok = cmp != 0
320
+ elif operator == ">":
321
+ ok = cmp > 0
322
+ else:
323
+ ok = cmp < 0
324
+ if not ok:
325
+ return False
326
+ break
327
+ else:
328
+ raise PzError(
329
+ code="PZ_APP_REQUIRED_PZ_INVALID",
330
+ message=(
331
+ f"App '{owner}' declares invalid required_pz constraint "
332
+ f"'{required_pz}'."
333
+ ),
334
+ stage="apps",
335
+ owner=owner,
336
+ action="compile_app_registry",
337
+ )
338
+ return True
339
+
340
+
341
+ def _validate_protocol_names(app: AppDecl) -> None:
342
+ invalid = tuple(
343
+ item for item in app.required_protocols if ":" in item or "/" in item or "\\" in item
344
+ )
345
+ if invalid:
346
+ raise PzError(
347
+ code="PZ_APP_PROTOCOL_INVALID",
348
+ message=(
349
+ f"App '{app.app_id}' has invalid required_protocols entries: "
350
+ + ", ".join(invalid)
351
+ ),
352
+ stage="apps",
353
+ owner=app.app_id,
354
+ action="compile_app_registry",
355
+ )
356
+
357
+
358
+ def _run_init_hook(
359
+ decl: AppDecl,
360
+ app_obj: Any,
361
+ *,
362
+ settings: SettingsSnapshot,
363
+ project_dir: Path | None,
364
+ ) -> None:
365
+ hook = getattr(app_obj, "init", None)
366
+ if hook is None:
367
+ return
368
+ if not callable(hook):
369
+ raise PzError(
370
+ code="PZ_APP_INIT_INVALID",
371
+ message=f"App '{decl.app_id}' exposes non-callable hook 'init'.",
372
+ stage="apps",
373
+ owner=decl.app_id,
374
+ action="compile_app_registry",
375
+ )
376
+ before = _declaration_fingerprint(app_obj)
377
+ signature = inspect.signature(hook)
378
+ param_count = len(signature.parameters)
379
+ context = AppInitContext(app=decl, settings=settings, project_dir=project_dir)
380
+ if param_count == 0:
381
+ result = hook()
382
+ elif param_count == 1:
383
+ result = hook(context)
384
+ else:
385
+ raise PzError(
386
+ code="PZ_APP_INIT_SIGNATURE_INVALID",
387
+ message=f"App '{decl.app_id}' init() must accept zero or one argument.",
388
+ stage="apps",
389
+ owner=decl.app_id,
390
+ action="compile_app_registry",
391
+ )
392
+ try:
393
+ if inspect.isawaitable(result):
394
+ try:
395
+ asyncio.get_running_loop()
396
+ except RuntimeError:
397
+ asyncio.run(result)
398
+ else:
399
+ raise PzError(
400
+ code="PZ_APP_INIT_ASYNC_IN_RUNNING_LOOP",
401
+ message=(
402
+ f"App '{decl.app_id}' init() returned an awaitable inside a running loop. "
403
+ "Compile-time init must complete synchronously."
404
+ ),
405
+ stage="apps",
406
+ owner=decl.app_id,
407
+ action="compile_app_registry",
408
+ )
409
+ except PzError:
410
+ raise
411
+ except Exception as exc:
412
+ raise PzError(
413
+ code="PZ_APP_INIT_FAILED",
414
+ message=f"App '{decl.app_id}' init() failed: {exc}",
415
+ stage="apps",
416
+ owner=decl.app_id,
417
+ action="compile_app_registry",
418
+ ) from exc
419
+ after = _declaration_fingerprint(app_obj)
420
+ if after != before:
421
+ raise PzError(
422
+ code="PZ_APP_INIT_MUTATES_DECLARATION",
423
+ message=(
424
+ f"App '{decl.app_id}' init() mutated formal declaration fields. "
425
+ "Declaration metadata must stay stable across declared -> compiled."
426
+ ),
427
+ stage="apps",
428
+ owner=decl.app_id,
429
+ action="compile_app_registry",
430
+ )
431
+
432
+
433
+ def _load_decl(module_path: str, *, current_pz: str) -> AppDecl:
434
+ module_path = _read_app_module_path(module_path, owner="apps")
435
+ entrypoint = _normalize_app_path(module_path)
436
+ try:
437
+ static_decl = read_static_app_declaration(module_path, action="compile_app_registry")
438
+ except PzError as exc:
439
+ if exc.code == "PZ_APP_DECLARATION_NOT_FOUND":
440
+ raise PzError(
441
+ code="PZ_APP_IMPORT_FAILED",
442
+ message=f"Failed to locate app module '{module_path}.apps'.",
443
+ stage="apps",
444
+ owner=module_path,
445
+ action="compile_app_registry",
446
+ detail=exc.detail,
447
+ ) from exc
448
+ raise
449
+ app = SimpleNamespace(
450
+ id=static_decl.app_id,
451
+ name=static_decl.name,
452
+ version=static_decl.version,
453
+ required_pz=static_decl.required_pz,
454
+ priority=static_decl.priority,
455
+ requires_apps=static_decl.requires_apps,
456
+ required_protocols=static_decl.required_protocols,
457
+ )
458
+
459
+ app_id = _read_declared_app_id(app, module_path=module_path)
460
+ decl = AppDecl(
461
+ app_id=app_id,
462
+ name=_read_required_str(app, "name", owner=app_id),
463
+ version=_read_required_str(app, "version", owner=app_id),
464
+ required_pz=_read_required_str(app, "required_pz", owner=app_id),
465
+ module_path=module_path,
466
+ entrypoint=entrypoint,
467
+ owner=app_id,
468
+ priority=_read_priority(app, owner=app_id),
469
+ requires_apps=_read_sequence(app, "requires_apps"),
470
+ required_protocols=_read_sequence(app, "required_protocols"),
471
+ )
472
+ _validate_protocol_names(decl)
473
+ if not _satisfies_required_pz(
474
+ decl.required_pz,
475
+ current_pz=current_pz,
476
+ owner=decl.app_id,
477
+ ):
478
+ raise PzError(
479
+ code="PZ_APP_REQUIRED_PZ_MISMATCH",
480
+ message=(
481
+ f"App '{decl.app_id}' requires pz '{decl.required_pz}', "
482
+ f"current pz is '{current_pz}'."
483
+ ),
484
+ stage="apps",
485
+ owner=decl.app_id,
486
+ action="compile_app_registry",
487
+ )
488
+ return decl
489
+
490
+
491
+ def _read_declared_app_id(app: Any, *, module_path: str) -> str:
492
+ candidate = getattr(app, "id", None)
493
+ if isinstance(candidate, str) and candidate.strip():
494
+ app_id = candidate.strip()
495
+ else:
496
+ app_id = ""
497
+ if not app_id:
498
+ raise PzError(
499
+ code="PZ_APP_ID_MISSING",
500
+ message=f"App '{module_path}' must explicitly declare app.id.",
501
+ stage="apps",
502
+ owner=module_path,
503
+ action="compile_app_registry",
504
+ )
505
+ if not app_id.isidentifier():
506
+ raise PzError(
507
+ code="PZ_APP_ID_INVALID",
508
+ message=f"App '{module_path}' must declare a valid app.id identifier.",
509
+ stage="apps",
510
+ owner=module_path,
511
+ action="compile_app_registry",
512
+ )
513
+ return app_id
514
+
515
+
516
+ def _validate_replacements(
517
+ installed_by_path: dict[str, AppDecl],
518
+ app_replacements: dict[str, str],
519
+ ) -> tuple[dict[str, str], set[str]]:
520
+ target_to_replacement: dict[str, str] = {}
521
+ replacement_sources: set[str] = set()
522
+ for target_path, replacement_path in app_replacements.items():
523
+ if target_path not in installed_by_path:
524
+ raise PzError(
525
+ code="PZ_APP_REPLACEMENT_TARGET_MISSING",
526
+ message=f"Replacement target '{target_path}' is not in installed_apps.",
527
+ stage="apps",
528
+ owner=target_path,
529
+ action="compile_app_registry",
530
+ )
531
+ if replacement_path not in installed_by_path:
532
+ raise PzError(
533
+ code="PZ_APP_REPLACEMENT_SOURCE_MISSING",
534
+ message=f"Replacement source '{replacement_path}' is not in installed_apps.",
535
+ stage="apps",
536
+ owner=replacement_path,
537
+ action="compile_app_registry",
538
+ )
539
+ if target_path in target_to_replacement:
540
+ raise PzError(
541
+ code="PZ_APP_REPLACEMENT_DUPLICATE_TARGET",
542
+ message=f"Replacement target '{target_path}' is declared more than once.",
543
+ stage="apps",
544
+ owner=target_path,
545
+ action="compile_app_registry",
546
+ )
547
+ if replacement_path in replacement_sources:
548
+ raise PzError(
549
+ code="PZ_APP_REPLACEMENT_DUPLICATE_SOURCE",
550
+ message=f"Replacement source '{replacement_path}' replaces more than one app.",
551
+ stage="apps",
552
+ owner=replacement_path,
553
+ action="compile_app_registry",
554
+ )
555
+ if target_path == replacement_path:
556
+ raise PzError(
557
+ code="PZ_APP_REPLACEMENT_SELF_REFERENCE",
558
+ message=f"App '{target_path}' cannot replace itself.",
559
+ stage="apps",
560
+ owner=target_path,
561
+ action="compile_app_registry",
562
+ )
563
+ target_to_replacement[target_path] = replacement_path
564
+ replacement_sources.add(replacement_path)
565
+ for target_path, replacement_path in target_to_replacement.items():
566
+ current = replacement_path
567
+ seen = {target_path}
568
+ while current in target_to_replacement:
569
+ if current in seen:
570
+ raise PzError(
571
+ code="PZ_APP_REPLACEMENT_CYCLE",
572
+ message="app_replacements must be acyclic.",
573
+ stage="apps",
574
+ owner=target_path,
575
+ action="compile_app_registry",
576
+ )
577
+ seen.add(current)
578
+ current = target_to_replacement[current]
579
+ target_decl = installed_by_path[target_path]
580
+ replacement_decl = installed_by_path[replacement_path]
581
+ if target_decl.app_id != replacement_decl.app_id:
582
+ raise PzError(
583
+ code="PZ_APP_REPLACEMENT_ID_MISMATCH",
584
+ message=(
585
+ f"Replacement '{replacement_path}' must keep app_id '{target_decl.app_id}', "
586
+ f"got '{replacement_decl.app_id}'."
587
+ ),
588
+ stage="apps",
589
+ owner=replacement_decl.app_id,
590
+ action="compile_app_registry",
591
+ )
592
+ return target_to_replacement, replacement_sources
593
+
594
+
595
+ def _toposort_apps(apps: tuple[AppDecl, ...]) -> tuple[AppDecl, ...]:
596
+ seen_ids: set[str] = set()
597
+ for app in apps:
598
+ if app.app_id in seen_ids:
599
+ raise PzError(
600
+ code="PZ_APP_DUPLICATE_ID",
601
+ message=f"Duplicate app_id '{app.app_id}' in installed_apps.",
602
+ stage="apps",
603
+ owner=app.app_id,
604
+ action="compile_app_registry",
605
+ )
606
+ seen_ids.add(app.app_id)
607
+ by_id = {app.app_id: app for app in apps}
608
+ incoming: dict[str, set[str]] = {app.app_id: set() for app in apps}
609
+ outgoing: dict[str, set[str]] = {app.app_id: set() for app in apps}
610
+ order_index = {app.app_id: idx for idx, app in enumerate(apps)}
611
+
612
+ def _sort_key(app_id: str) -> tuple[int, int]:
613
+ current = by_id[app_id]
614
+ return (int(current.priority), order_index[app_id])
615
+
616
+ for app in apps:
617
+ for dep_id in app.requires_apps:
618
+ if dep_id not in by_id:
619
+ raise PzError(
620
+ code="PZ_APP_DEPENDENCY_MISSING",
621
+ message=f"App '{app.app_id}' requires missing app '{dep_id}'.",
622
+ stage="apps",
623
+ owner=app.app_id,
624
+ action="compile_app_registry",
625
+ )
626
+ incoming[app.app_id].add(dep_id)
627
+ outgoing[dep_id].add(app.app_id)
628
+
629
+ ready = [app_id for app_id, deps in incoming.items() if not deps]
630
+ ready.sort(key=_sort_key)
631
+ out: list[AppDecl] = []
632
+ while ready:
633
+ current_id = ready.pop(0)
634
+ out.append(by_id[current_id])
635
+ for dependent_id in sorted(outgoing[current_id], key=_sort_key):
636
+ incoming[dependent_id].discard(current_id)
637
+ if not incoming[dependent_id] and dependent_id not in [item.app_id for item in out] and dependent_id not in ready:
638
+ ready.append(dependent_id)
639
+ ready.sort(key=_sort_key)
640
+
641
+ if len(out) != len(apps):
642
+ raise PzError(
643
+ code="PZ_APP_DEPENDENCY_CYCLE",
644
+ message="App dependency graph contains a cycle.",
645
+ stage="apps",
646
+ owner="apps",
647
+ action="compile_app_registry",
648
+ )
649
+ return tuple(out)
650
+
651
+
652
+ def _load_active_apps(
653
+ settings: SettingsSnapshot,
654
+ *,
655
+ project_dir: Path | str | None = None,
656
+ ) -> tuple[AppDecl, ...]:
657
+ current_pz = _loaded_pz_version()
658
+ resolved_project_dir = Path(project_dir).resolve() if project_dir is not None else None
659
+ import_context_project_dir = resolved_project_dir or settings.project_dir
660
+ with active_project_import_root(
661
+ import_context_project_dir,
662
+ extra_import_roots=getattr(settings, "import_roots", ()),
663
+ preserve_loaded_modules=True,
664
+ ):
665
+ loaded_apps: list[AppDecl] = []
666
+ seen_paths: set[str] = set()
667
+ for module_path in settings.installed_apps:
668
+ normalized_path = _read_app_module_path(module_path, owner="installed_apps")
669
+ discovered_paths = discover_leaf_app_packages(
670
+ normalized_path,
671
+ project_dir=import_context_project_dir,
672
+ ) or (normalized_path,)
673
+ for discovered_path in discovered_paths:
674
+ if discovered_path in seen_paths:
675
+ raise PzError(
676
+ code="PZ_APP_DUPLICATE_PATH",
677
+ message=f"Duplicate app path '{discovered_path}' in installed_apps.",
678
+ stage="apps",
679
+ owner=discovered_path,
680
+ action="compile_app_registry",
681
+ )
682
+ seen_paths.add(discovered_path)
683
+ decl = _load_decl(discovered_path, current_pz=current_pz)
684
+ loaded_apps.append(decl)
685
+
686
+ installed_by_path = {app.module_path: app for app in loaded_apps}
687
+ replacements, _ = _validate_replacements(installed_by_path, settings.app_replacements)
688
+ return tuple(app for app in loaded_apps if app.module_path not in replacements)
689
+
690
+
691
+ def compile_conventional_app_order(
692
+ settings: SettingsSnapshot,
693
+ *,
694
+ project_dir: Path | str | None = None,
695
+ ) -> tuple[str, ...]:
696
+ return tuple(app.module_path for app in _toposort_apps(_load_active_apps(settings, project_dir=project_dir)))
697
+
698
+
699
+ def merge_explicit_app_order(
700
+ existing: tuple[str, ...] | list[str],
701
+ conventional: tuple[str, ...] | list[str],
702
+ ) -> tuple[str, ...]:
703
+ conventional_items = tuple(str(item).strip() for item in conventional if str(item).strip())
704
+ conventional_set = set(conventional_items)
705
+ existing_items = tuple(
706
+ str(item).strip()
707
+ for item in existing
708
+ if str(item).strip() and str(item).strip() in conventional_set
709
+ )
710
+ if not existing_items:
711
+ return conventional_items
712
+
713
+ existing_set = set(existing_items)
714
+ inserted: set[str] = set()
715
+ out: list[str] = []
716
+ cursor = 0
717
+
718
+ for current in existing_items:
719
+ while cursor < len(conventional_items) and conventional_items[cursor] != current:
720
+ candidate = conventional_items[cursor]
721
+ cursor += 1
722
+ if candidate in existing_set or candidate in inserted:
723
+ continue
724
+ out.append(candidate)
725
+ inserted.add(candidate)
726
+ if cursor < len(conventional_items) and conventional_items[cursor] == current:
727
+ cursor += 1
728
+ out.append(current)
729
+
730
+ while cursor < len(conventional_items):
731
+ candidate = conventional_items[cursor]
732
+ cursor += 1
733
+ if candidate in existing_set or candidate in inserted:
734
+ continue
735
+ out.append(candidate)
736
+ inserted.add(candidate)
737
+ return tuple(out)
738
+
739
+
740
+ def _validate_explicit_app_order(apps: tuple[AppDecl, ...]) -> None:
741
+ _toposort_apps(apps)
742
+ explicit_index = {app.app_id: idx for idx, app in enumerate(apps)}
743
+ for app in apps:
744
+ for dep_id in app.requires_apps:
745
+ dep_index = explicit_index.get(dep_id)
746
+ app_index = explicit_index.get(app.app_id)
747
+ if dep_index is None or app_index is None or dep_index < app_index:
748
+ continue
749
+ raise PzError(
750
+ code="PZ_APP_DEPENDENCY_ORDER_INVALID",
751
+ message=(
752
+ f"App '{app.app_id}' must appear after required app '{dep_id}' in installed_apps."
753
+ ),
754
+ stage="apps",
755
+ owner=app.app_id,
756
+ action="compile_app_registry",
757
+ )
758
+
759
+
760
+ def _declared_installed_apps(
761
+ settings: SettingsSnapshot,
762
+ ) -> tuple[str, ...]:
763
+ declared = tuple(str(item).strip() for item in getattr(settings, "declared_installed_apps", ()) if str(item).strip())
764
+ if declared:
765
+ return declared
766
+ return tuple(str(item).strip() for item in settings.installed_apps if str(item).strip())
767
+
768
+
769
+ def _uses_explicit_leaf_app_sequence(
770
+ settings: SettingsSnapshot,
771
+ *,
772
+ project_dir: Path | None,
773
+ ) -> bool:
774
+ declared_apps = _declared_installed_apps(settings)
775
+ if not declared_apps:
776
+ return True
777
+ discovery_project_dir = project_dir if project_dir is not None else settings.project_dir
778
+ for module_path in declared_apps:
779
+ discovered_paths = discover_leaf_app_packages(module_path, project_dir=discovery_project_dir)
780
+ if not discovered_paths:
781
+ continue
782
+ if len(discovered_paths) != 1 or discovered_paths[0] != module_path:
783
+ return False
784
+ return True
785
+
786
+
787
+ def compile_app_registry(
788
+ settings: SettingsSnapshot,
789
+ *,
790
+ project_dir: Path | str | None = None,
791
+ run_init_hooks: bool = True,
792
+ ) -> AppRegistry:
793
+ resolved_project_dir = Path(project_dir).resolve() if project_dir is not None else None
794
+ validate_installed_app_identities(
795
+ (*_declared_installed_apps(settings), *tuple(settings.installed_apps))
796
+ )
797
+ active_apps = _load_active_apps(settings, project_dir=resolved_project_dir)
798
+ validate_installed_app_identities(app.app_id for app in active_apps)
799
+ if _uses_explicit_leaf_app_sequence(settings, project_dir=resolved_project_dir):
800
+ _validate_explicit_app_order(active_apps)
801
+ ordered_apps = active_apps
802
+ else:
803
+ ordered_apps = _toposort_apps(active_apps)
804
+ import_context_project_dir = resolved_project_dir or settings.project_dir
805
+ with active_project_import_root(
806
+ import_context_project_dir,
807
+ extra_import_roots=getattr(settings, "import_roots", ()),
808
+ preserve_loaded_modules=True,
809
+ ):
810
+ initialized_app_ids: list[str] = []
811
+ if run_init_hooks:
812
+ for decl in ordered_apps:
813
+ app_obj = load_app_object_from_decl(decl)
814
+ _run_init_hook(
815
+ decl,
816
+ app_obj,
817
+ settings=settings,
818
+ project_dir=resolved_project_dir,
819
+ )
820
+ initialized_app_ids.append(decl.app_id)
821
+ return AppRegistry(
822
+ apps=ordered_apps,
823
+ initialized_app_ids=tuple(initialized_app_ids),
824
+ )