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,380 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from dataclasses import dataclass, field
5
+ import inspect
6
+ from typing import Any, Awaitable, Callable
7
+ from uuid import uuid4
8
+
9
+ from pz_core.runtime.errors import PzError
10
+ from pz_core.core.events import EventRegistry
11
+
12
+ EventHandler = Callable[[dict[str, Any]], Any]
13
+
14
+
15
+ @dataclass(frozen=True, slots=True)
16
+ class EventSubscription:
17
+ subscription_id: str
18
+ event_name: str
19
+ owner: str
20
+ handler_name: str
21
+ wildcard: bool = False
22
+
23
+
24
+ @dataclass(frozen=True, slots=True)
25
+ class EventDispatchResult:
26
+ event_name: str
27
+ owner: str
28
+ handler_name: str
29
+ ok: bool
30
+ subscribed_event: str | None = None
31
+ scheduler: str | None = None
32
+ code: str | None = None
33
+ message: str | None = None
34
+
35
+
36
+ @dataclass(frozen=True, slots=True)
37
+ class EventDispatchTicket:
38
+ event_name: str
39
+ owner: str
40
+ scheduled_handlers: int
41
+ scheduler: str
42
+
43
+
44
+ def _coerce_handler_name(handler: EventHandler) -> str:
45
+ return getattr(handler, "__qualname__", getattr(handler, "__name__", handler.__class__.__name__))
46
+
47
+
48
+ def _read_required_event_name(
49
+ value: object,
50
+ *,
51
+ code: str,
52
+ action: str,
53
+ owner: str,
54
+ ) -> str:
55
+ if not isinstance(value, str) or not value.strip():
56
+ raise PzError(
57
+ code=code,
58
+ message="event_name must be a non-empty string.",
59
+ stage="events",
60
+ owner=owner,
61
+ action=action,
62
+ )
63
+ return value.strip()
64
+
65
+
66
+ def _read_required_owner(
67
+ value: object,
68
+ *,
69
+ code: str,
70
+ message: str,
71
+ action: str,
72
+ owner: str,
73
+ ) -> str:
74
+ if not isinstance(value, str) or not value.strip():
75
+ raise PzError(
76
+ code=code,
77
+ message=message,
78
+ stage="events",
79
+ owner=owner,
80
+ action=action,
81
+ )
82
+ return value.strip()
83
+
84
+
85
+ def _read_dispatched_event_name(
86
+ envelope: dict[str, Any],
87
+ *,
88
+ fallback: str,
89
+ ) -> str:
90
+ raw = envelope.get("event")
91
+ if raw is None:
92
+ return fallback
93
+ return _read_required_event_name(
94
+ raw,
95
+ code="PZ_EVENT_NAME_INVALID",
96
+ action="dispatch",
97
+ owner="runtime.events",
98
+ )
99
+
100
+
101
+ @dataclass(slots=True)
102
+ class EventBusFacade:
103
+ registry: EventRegistry
104
+ _subscriptions: dict[str, tuple[EventSubscription, EventHandler]] = field(default_factory=dict)
105
+ _dispatch_results: list[EventDispatchResult] = field(default_factory=list)
106
+
107
+ def __post_init__(self) -> None:
108
+ if not isinstance(self.registry, EventRegistry):
109
+ raise PzError(
110
+ code="PZ_EVENT_REGISTRY_INVALID",
111
+ message="EventBusFacade requires an EventRegistry instance.",
112
+ stage="events",
113
+ owner="runtime.events",
114
+ action="bind_event_bus",
115
+ )
116
+
117
+ def _ensure_declared(self, event_name: str) -> None:
118
+ normalized = _read_required_event_name(
119
+ event_name,
120
+ code="PZ_EVENT_NAME_INVALID",
121
+ action="publish",
122
+ owner="runtime.events",
123
+ )
124
+ if normalized not in self.registry.by_name():
125
+ raise PzError(
126
+ code="PZ_EVENT_UNDECLARED",
127
+ message=f"Event '{normalized}' is not declared in EventRegistry.",
128
+ stage="events",
129
+ owner=normalized,
130
+ action="publish",
131
+ )
132
+
133
+ def subscribe(
134
+ self,
135
+ event_name: str,
136
+ handler: EventHandler,
137
+ *,
138
+ owner: str,
139
+ wildcard: bool = False,
140
+ ) -> str:
141
+ normalized_owner = _read_required_owner(
142
+ owner,
143
+ code="PZ_EVENT_SUBSCRIBER_OWNER_INVALID",
144
+ message="event subscriber must declare owner as a non-empty string.",
145
+ action="subscribe",
146
+ owner="runtime.events",
147
+ )
148
+ normalized_event = _read_required_event_name(
149
+ event_name,
150
+ code="PZ_EVENT_NAME_INVALID",
151
+ action="subscribe",
152
+ owner=normalized_owner,
153
+ )
154
+ if not wildcard:
155
+ self._ensure_declared(normalized_event)
156
+ subscription = EventSubscription(
157
+ subscription_id=uuid4().hex,
158
+ event_name=normalized_event,
159
+ owner=normalized_owner,
160
+ handler_name=_coerce_handler_name(handler),
161
+ wildcard=wildcard,
162
+ )
163
+ self._subscriptions[subscription.subscription_id] = (subscription, handler)
164
+ return subscription.subscription_id
165
+
166
+ def unsubscribe(self, subscription_id: str) -> bool:
167
+ if not isinstance(subscription_id, str) or not subscription_id.strip():
168
+ raise PzError(
169
+ code="PZ_EVENT_SUBSCRIPTION_ID_INVALID",
170
+ message="event subscription_id must be a non-empty string.",
171
+ stage="events",
172
+ owner="runtime.events",
173
+ action="unsubscribe",
174
+ )
175
+ return self._subscriptions.pop(subscription_id, None) is not None
176
+
177
+ def reset(self, *, owner: str | None = None) -> int:
178
+ if owner is None:
179
+ removed = len(self._subscriptions)
180
+ self._subscriptions.clear()
181
+ self._dispatch_results.clear()
182
+ return removed
183
+ target = _read_required_owner(
184
+ owner,
185
+ code="PZ_EVENT_RESET_OWNER_INVALID",
186
+ message="event reset owner must be a non-empty string.",
187
+ action="reset",
188
+ owner="runtime.events",
189
+ )
190
+ to_remove = [
191
+ subscription_id
192
+ for subscription_id, (subscription, _) in self._subscriptions.items()
193
+ if subscription.owner == target
194
+ ]
195
+ for subscription_id in to_remove:
196
+ self._subscriptions.pop(subscription_id, None)
197
+ self._dispatch_results[:] = [
198
+ result for result in self._dispatch_results if result.owner != target
199
+ ]
200
+ return len(to_remove)
201
+
202
+ def subscriptions_for(self, event_name: str) -> tuple[EventSubscription, ...]:
203
+ normalized = _read_required_event_name(
204
+ event_name,
205
+ code="PZ_EVENT_NAME_INVALID",
206
+ action="subscriptions_for",
207
+ owner="runtime.events",
208
+ )
209
+ return tuple(
210
+ subscription
211
+ for subscription, _ in self._subscriptions.values()
212
+ if subscription.event_name == normalized or subscription.wildcard
213
+ )
214
+
215
+ def diagnostics(self) -> tuple[EventDispatchResult, ...]:
216
+ return tuple(self._dispatch_results)
217
+
218
+ async def _invoke_handler(
219
+ self,
220
+ subscription: EventSubscription,
221
+ handler: EventHandler,
222
+ envelope: dict[str, Any],
223
+ *,
224
+ scheduler: str,
225
+ ) -> EventDispatchResult:
226
+ try:
227
+ dispatched_event = _read_dispatched_event_name(
228
+ envelope,
229
+ fallback=subscription.event_name,
230
+ )
231
+ except PzError as exc:
232
+ dispatch = EventDispatchResult(
233
+ event_name=subscription.event_name,
234
+ owner=subscription.owner,
235
+ handler_name=subscription.handler_name,
236
+ ok=False,
237
+ subscribed_event=subscription.event_name,
238
+ scheduler=scheduler,
239
+ code=exc.code,
240
+ message=exc.summary or exc.message,
241
+ )
242
+ self._dispatch_results.append(dispatch)
243
+ return dispatch
244
+ try:
245
+ if inspect.iscoroutinefunction(handler):
246
+ await handler(envelope)
247
+ else:
248
+ result = await asyncio.to_thread(handler, envelope)
249
+ if inspect.isawaitable(result):
250
+ await result
251
+ except PzError as exc:
252
+ dispatch = EventDispatchResult(
253
+ event_name=dispatched_event,
254
+ owner=subscription.owner,
255
+ handler_name=subscription.handler_name,
256
+ ok=False,
257
+ subscribed_event=subscription.event_name,
258
+ scheduler=scheduler,
259
+ code=exc.code,
260
+ message=exc.summary or exc.message,
261
+ )
262
+ self._dispatch_results.append(dispatch)
263
+ return dispatch
264
+ except Exception as exc:
265
+ dispatch = EventDispatchResult(
266
+ event_name=dispatched_event,
267
+ owner=subscription.owner,
268
+ handler_name=subscription.handler_name,
269
+ ok=False,
270
+ subscribed_event=subscription.event_name,
271
+ scheduler=scheduler,
272
+ code="PZ_EVENT_HANDLER_FAILED",
273
+ message=str(exc) or exc.__class__.__name__,
274
+ )
275
+ self._dispatch_results.append(dispatch)
276
+ return dispatch
277
+ dispatch = EventDispatchResult(
278
+ event_name=dispatched_event,
279
+ owner=subscription.owner,
280
+ handler_name=subscription.handler_name,
281
+ ok=True,
282
+ subscribed_event=subscription.event_name,
283
+ scheduler=scheduler,
284
+ )
285
+ self._dispatch_results.append(dispatch)
286
+ return dispatch
287
+
288
+ async def publish(
289
+ self,
290
+ event_name: str,
291
+ payload: dict[str, Any] | None = None,
292
+ *,
293
+ owner: str,
294
+ ) -> tuple[EventDispatchResult, ...]:
295
+ normalized_event = _read_required_event_name(
296
+ event_name,
297
+ code="PZ_EVENT_NAME_INVALID",
298
+ action="publish",
299
+ owner="runtime.events",
300
+ )
301
+ normalized_owner = _read_required_owner(
302
+ owner,
303
+ code="PZ_EVENT_PUBLISH_OWNER_INVALID",
304
+ message="event publisher must declare owner as a non-empty string.",
305
+ action="publish",
306
+ owner=normalized_event,
307
+ )
308
+ self._ensure_declared(normalized_event)
309
+ envelope = {
310
+ "event": normalized_event,
311
+ "owner": normalized_owner,
312
+ "payload": dict(payload or {}),
313
+ }
314
+ subscriptions = self.subscriptions_for(normalized_event)
315
+ results: list[EventDispatchResult] = []
316
+ for subscription in subscriptions:
317
+ _, handler = self._subscriptions[subscription.subscription_id]
318
+ results.append(
319
+ await self._invoke_handler(
320
+ subscription,
321
+ handler,
322
+ envelope,
323
+ scheduler="await",
324
+ )
325
+ )
326
+ return tuple(results)
327
+
328
+ def publish_nowait(
329
+ self,
330
+ event_name: str,
331
+ payload: dict[str, Any] | None = None,
332
+ *,
333
+ owner: str,
334
+ ) -> EventDispatchTicket:
335
+ normalized_event = _read_required_event_name(
336
+ event_name,
337
+ code="PZ_EVENT_NAME_INVALID",
338
+ action="publish_nowait",
339
+ owner="runtime.events",
340
+ )
341
+ normalized_owner = _read_required_owner(
342
+ owner,
343
+ code="PZ_EVENT_PUBLISH_OWNER_INVALID",
344
+ message="event publisher must declare owner as a non-empty string.",
345
+ action="publish_nowait",
346
+ owner=normalized_event,
347
+ )
348
+ self._ensure_declared(normalized_event)
349
+ try:
350
+ loop = asyncio.get_running_loop()
351
+ except RuntimeError as exc:
352
+ raise PzError(
353
+ code="PZ_EVENT_LOOP_REQUIRED",
354
+ message="publish_nowait() requires an active asyncio loop.",
355
+ stage="events",
356
+ owner=normalized_event,
357
+ action="publish_nowait",
358
+ ) from exc
359
+ subscriptions = self.subscriptions_for(normalized_event)
360
+ envelope = {
361
+ "event": normalized_event,
362
+ "owner": normalized_owner,
363
+ "payload": dict(payload or {}),
364
+ }
365
+ for subscription in subscriptions:
366
+ _, handler = self._subscriptions[subscription.subscription_id]
367
+ loop.create_task(
368
+ self._invoke_handler(
369
+ subscription,
370
+ handler,
371
+ envelope,
372
+ scheduler="asyncio.create_task",
373
+ )
374
+ )
375
+ return EventDispatchTicket(
376
+ event_name=normalized_event,
377
+ owner=normalized_owner,
378
+ scheduled_handlers=len(subscriptions),
379
+ scheduler="asyncio.create_task",
380
+ )
@@ -0,0 +1,101 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import replace
4
+ import time
5
+ from typing import TYPE_CHECKING
6
+
7
+ from pz_core.runtime.facades._capabilities import merged_capability_status
8
+ from pz_core.runtime.health import HealthSnapshot, build_health_snapshot
9
+ from pz_core.runtime.http_timing import log_http_timing
10
+ from pz_core.runtime.lifecycle import LifecycleState
11
+ from pz_core.runtime.plan import RuntimePlan
12
+ from pz_core.runtime.registries import CapabilityStatus
13
+ from pz_core.runtime.errors import PzError
14
+
15
+ if TYPE_CHECKING:
16
+ from pz_core.runtime.facades.providers import ProviderFacade
17
+
18
+
19
+ class HealthFacade:
20
+ def __init__(
21
+ self,
22
+ plan: RuntimePlan,
23
+ state: LifecycleState | None = None,
24
+ *,
25
+ providers: "ProviderFacade | None" = None,
26
+ ) -> None:
27
+ if not isinstance(plan, RuntimePlan):
28
+ raise PzError(
29
+ code="PZ_HEALTH_PLAN_INVALID",
30
+ message="HealthFacade requires a RuntimePlan instance.",
31
+ stage="health",
32
+ owner="runtime.health",
33
+ action="bind_health_facade",
34
+ )
35
+ if state is not None and not isinstance(state, LifecycleState):
36
+ raise PzError(
37
+ code="PZ_HEALTH_STATE_INVALID",
38
+ message="HealthFacade state must be a LifecycleState when configured.",
39
+ stage="health",
40
+ owner="runtime.health",
41
+ action="bind_health_facade",
42
+ )
43
+ if providers is not None and not hasattr(providers, "declared"):
44
+ raise PzError(
45
+ code="PZ_HEALTH_PROVIDERS_INVALID",
46
+ message="HealthFacade providers must expose declared() when configured.",
47
+ stage="health",
48
+ owner="runtime.health",
49
+ action="bind_health_facade",
50
+ )
51
+ self._plan = plan
52
+ self._state = state
53
+ self._providers = providers
54
+
55
+ def _provider_capability_statuses(self) -> dict[str, CapabilityStatus]:
56
+ if self._providers is None:
57
+ return {}
58
+ statuses: dict[str, CapabilityStatus] = {}
59
+ for declaration in self._providers.declared():
60
+ capability = declaration.capability
61
+ base = self._plan.capability_registry.get(capability)
62
+ if base is not None and base.status == "disabled":
63
+ statuses[capability] = "disabled"
64
+ continue
65
+ statuses[capability] = merged_capability_status(
66
+ self._plan,
67
+ self._providers,
68
+ capability,
69
+ )
70
+ return statuses
71
+
72
+ def liveness(self) -> HealthSnapshot:
73
+ started_at = time.perf_counter()
74
+ provider_started_at = started_at
75
+ extra_capability_statuses = self._provider_capability_statuses()
76
+ providers_ms = (time.perf_counter() - provider_started_at) * 1000.0
77
+ snapshot_started_at = time.perf_counter()
78
+ snapshot = build_health_snapshot(
79
+ self._plan,
80
+ self._state,
81
+ extra_capability_statuses=extra_capability_statuses,
82
+ )
83
+ snapshot_ms = (time.perf_counter() - snapshot_started_at) * 1000.0
84
+ total_ms = (time.perf_counter() - started_at) * 1000.0
85
+ log_http_timing("health.providers", total_ms=providers_ms)
86
+ log_http_timing("health.snapshot", total_ms=snapshot_ms)
87
+ log_http_timing("health.liveness", total_ms=total_ms)
88
+ return snapshot
89
+
90
+ def readiness(self) -> HealthSnapshot:
91
+ started_at = time.perf_counter()
92
+ snapshot = self.liveness()
93
+ if not bool(self._plan.infra.health.get("readiness_enabled", True)):
94
+ readiness_snapshot = replace(snapshot, status="disabled")
95
+ log_http_timing("health.readiness", total_ms=(time.perf_counter() - started_at) * 1000.0)
96
+ return readiness_snapshot
97
+ log_http_timing("health.readiness", total_ms=(time.perf_counter() - started_at) * 1000.0)
98
+ return snapshot
99
+
100
+ def capabilities(self) -> dict[str, CapabilityStatus]:
101
+ return dict(self.liveness().capability_statuses)
@@ -0,0 +1,121 @@
1
+ from __future__ import annotations
2
+
3
+ from pz_core.runtime.errors import PzError
4
+ from pz_core.runtime.facades._capabilities import merged_capability_status
5
+ from pz_core.runtime.facades.providers import ProviderFacade
6
+ from pz_core.runtime.plan import RuntimePlan
7
+ from pz_core.runtime.registries import CapabilityStatus
8
+
9
+
10
+ def _require_mail_template(template: str, *, action: str) -> str:
11
+ if not isinstance(template, str) or not template.strip():
12
+ raise PzError(
13
+ code="PZ_MAIL_TEMPLATE_INVALID",
14
+ message="Mail template must be a non-empty string.",
15
+ stage="mail",
16
+ owner="runtime.mail",
17
+ action=action,
18
+ )
19
+ return template
20
+
21
+
22
+ def _require_mail_context(context: dict[str, object], *, action: str) -> dict[str, object]:
23
+ if not isinstance(context, dict):
24
+ raise PzError(
25
+ code="PZ_MAIL_CONTEXT_INVALID",
26
+ message="Mail context must be a mapping.",
27
+ stage="mail",
28
+ owner="runtime.mail",
29
+ action=action,
30
+ )
31
+ return context
32
+
33
+
34
+ def _read_optional_mail_locale(locale: str | None, *, action: str) -> str | None:
35
+ if locale is None:
36
+ return None
37
+ if not isinstance(locale, str) or not locale.strip():
38
+ raise PzError(
39
+ code="PZ_MAIL_LOCALE_INVALID",
40
+ message="Mail locale must be a non-empty string when configured.",
41
+ stage="mail",
42
+ owner="runtime.mail",
43
+ action=action,
44
+ )
45
+ return locale
46
+
47
+
48
+ class MailFacade:
49
+ def __init__(self, plan: RuntimePlan, providers: ProviderFacade) -> None:
50
+ if not isinstance(plan, RuntimePlan):
51
+ raise PzError(
52
+ code="PZ_MAIL_PLAN_INVALID",
53
+ message="MailFacade requires a RuntimePlan instance.",
54
+ stage="mail",
55
+ owner="runtime.mail",
56
+ action="bind_mail_facade",
57
+ )
58
+ if not hasattr(providers, "require"):
59
+ raise PzError(
60
+ code="PZ_MAIL_PROVIDERS_INVALID",
61
+ message="MailFacade providers must expose require().",
62
+ stage="mail",
63
+ owner="runtime.mail",
64
+ action="bind_mail_facade",
65
+ )
66
+ self._plan = plan
67
+ self._providers = providers
68
+
69
+ def status(self) -> CapabilityStatus:
70
+ return merged_capability_status(self._plan, self._providers, "mail")
71
+
72
+ def _provider(self) -> object:
73
+ return self._providers.require("mail")
74
+
75
+ def render(
76
+ self,
77
+ template: str,
78
+ context: dict[str, object],
79
+ *,
80
+ locale: str | None = None,
81
+ ) -> object:
82
+ template = _require_mail_template(template, action="render")
83
+ context = _require_mail_context(context, action="render")
84
+ locale = _read_optional_mail_locale(locale, action="render")
85
+ provider = self._provider()
86
+ renderer = getattr(provider, "render", None)
87
+ if not callable(renderer):
88
+ raise PzError(
89
+ code="PZ_MAIL_RENDER_UNSUPPORTED",
90
+ message="Registered mail provider does not expose render().",
91
+ stage="mail",
92
+ owner="infra.mail",
93
+ action="render",
94
+ )
95
+ return renderer(template, context, locale=locale)
96
+
97
+ def send(self, message: object) -> object:
98
+ provider = self._provider()
99
+ sender = getattr(provider, "send", None)
100
+ if not callable(sender):
101
+ raise PzError(
102
+ code="PZ_MAIL_SEND_UNSUPPORTED",
103
+ message="Registered mail provider does not expose send().",
104
+ stage="mail",
105
+ owner="infra.mail",
106
+ action="send",
107
+ )
108
+ return sender(message)
109
+
110
+ def enqueue(self, message: object) -> object:
111
+ provider = self._provider()
112
+ enqueue = getattr(provider, "enqueue", None)
113
+ if not callable(enqueue):
114
+ raise PzError(
115
+ code="PZ_MAIL_ENQUEUE_UNSUPPORTED",
116
+ message="Registered mail provider does not expose enqueue().",
117
+ stage="mail",
118
+ owner="infra.mail",
119
+ action="enqueue",
120
+ )
121
+ return enqueue(message)
@@ -0,0 +1,102 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+
5
+ from pz_core.runtime.errors import PzError
6
+ from pz_core.infra.observability import NoopObservability, ObservabilityPort
7
+ from pz_core.runtime.facades._capabilities import merged_capability_status
8
+ from pz_core.runtime.facades.providers import ProviderFacade
9
+ from pz_core.runtime.plan import RuntimePlan
10
+ from pz_core.runtime.registries import CapabilityStatus
11
+
12
+
13
+ def _require_observability_name(name: str, *, action: str) -> str:
14
+ if not isinstance(name, str) or not name.strip():
15
+ raise PzError(
16
+ code="PZ_OBSERVABILITY_NAME_INVALID",
17
+ message="Observability name must be a non-empty string.",
18
+ stage="observability",
19
+ owner="runtime.observability",
20
+ action=action,
21
+ )
22
+ return name
23
+
24
+
25
+ class ObservabilityFacade:
26
+ def __init__(self, plan: RuntimePlan, providers: ProviderFacade) -> None:
27
+ if not isinstance(plan, RuntimePlan):
28
+ raise PzError(
29
+ code="PZ_OBSERVABILITY_PLAN_INVALID",
30
+ message="ObservabilityFacade requires a RuntimePlan instance.",
31
+ stage="observability",
32
+ owner="runtime.observability",
33
+ action="bind_observability_facade",
34
+ )
35
+ if not hasattr(providers, "require") or not hasattr(providers, "declared_for"):
36
+ raise PzError(
37
+ code="PZ_OBSERVABILITY_PROVIDERS_INVALID",
38
+ message="ObservabilityFacade providers must expose require() and declared_for().",
39
+ stage="observability",
40
+ owner="runtime.observability",
41
+ action="bind_observability_facade",
42
+ )
43
+ self._plan = plan
44
+ self._providers = providers
45
+ self._noop = NoopObservability()
46
+
47
+ def status(self) -> CapabilityStatus:
48
+ metrics = merged_capability_status(self._plan, self._providers, "metrics")
49
+ tracing = merged_capability_status(self._plan, self._providers, "tracing")
50
+ statuses = (metrics, tracing)
51
+ if any(status == "unavailable" for status in statuses):
52
+ return "unavailable"
53
+ if any(status == "ready" for status in statuses):
54
+ return "ready"
55
+ if any(status == "enabled" for status in statuses):
56
+ return "enabled"
57
+ return "disabled"
58
+
59
+ def _port(self) -> ObservabilityPort:
60
+ for capability in ("metrics", "tracing"):
61
+ if not self._providers.declared_for(capability):
62
+ continue
63
+ try:
64
+ provider = self._providers.require(capability)
65
+ except PzError:
66
+ continue
67
+ if isinstance(provider, ObservabilityPort):
68
+ return provider
69
+ return self._noop
70
+
71
+ def logger(self, name: str):
72
+ name = _require_observability_name(name, action="logger")
73
+ return self._port().logger(name)
74
+
75
+ def counter(
76
+ self,
77
+ name: str,
78
+ *,
79
+ unit: str | None = None,
80
+ description: str | None = None,
81
+ ):
82
+ name = _require_observability_name(name, action="counter")
83
+ return self._port().counter(name, unit=unit, description=description)
84
+
85
+ def histogram(
86
+ self,
87
+ name: str,
88
+ *,
89
+ unit: str | None = None,
90
+ description: str | None = None,
91
+ ):
92
+ name = _require_observability_name(name, action="histogram")
93
+ return self._port().histogram(name, unit=unit, description=description)
94
+
95
+ def span(
96
+ self,
97
+ name: str,
98
+ *,
99
+ attributes: Mapping[str, object] | None = None,
100
+ ):
101
+ name = _require_observability_name(name, action="span")
102
+ return self._port().span(name, attributes=attributes)