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,843 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable, Mapping
4
+ from dataclasses import dataclass
5
+ from itertools import product
6
+ import inspect
7
+ from typing import Any, Callable, get_type_hints
8
+
9
+ from fastapi import APIRouter
10
+
11
+ from .exports import IncludedUrls, create_lazy_mount_router
12
+ from .loaders import DeclaredUrlSpec
13
+ from .utils import _join_path_prefix
14
+
15
+
16
+ RoutePathProjectionProvider = Callable[[Any], Iterable[Mapping[str, Any]]]
17
+
18
+
19
+ @dataclass(frozen=True, slots=True)
20
+ class RuntimeRouteBinding:
21
+ path: str | Callable[[Any], str]
22
+ handler_attr: str
23
+ methods: tuple[str, ...] = ("GET",)
24
+ response_class: object | None = None
25
+ name: str | None = None
26
+ include_in_schema: bool = True
27
+ enabled_when: str | None = None
28
+ enumerate_params: tuple[tuple[str, Any], ...] | dict[str, Any] = ()
29
+ path_projections: RoutePathProjectionProvider | None = None
30
+
31
+
32
+ @dataclass(frozen=True, slots=True)
33
+ class DeclaredEndpointBinding:
34
+ path: str
35
+ endpoint: Callable[..., Any]
36
+ methods: tuple[str, ...] = ("GET",)
37
+ response_class: object | None = None
38
+ name: str | None = None
39
+ include_in_schema: bool = True
40
+
41
+
42
+ @dataclass(frozen=True, slots=True)
43
+ class LazyMountUrlBinding:
44
+ namespace: str
45
+ path: str
46
+ app_factory: Callable[[], Any]
47
+ name: str | None = None
48
+ dependencies: tuple[object, ...] = ()
49
+ replace_target: str = ""
50
+ route_hints: tuple[str, ...] = ()
51
+ conflict_policy: str = "fail"
52
+
53
+
54
+ @dataclass(frozen=True, slots=True)
55
+ class UrlPathPattern:
56
+ path: str
57
+ view: Callable[..., Any]
58
+ methods: tuple[str, ...] = ("GET",)
59
+ response_class: object | None = None
60
+ name: str | None = None
61
+ include_in_schema: bool = True
62
+ enabled_when: str | Callable[..., object] | None = None
63
+ dependencies: tuple[object, ...] = ()
64
+
65
+
66
+ @dataclass(frozen=True, slots=True)
67
+ class MountedUrlPattern:
68
+ path: str
69
+ app: Callable[[], Any]
70
+ namespace: str
71
+ name: str | None = None
72
+ dependencies: tuple[object, ...] = ()
73
+ replace_target: str = ""
74
+ route_hints: tuple[str, ...] = ()
75
+ conflict_policy: str = "fail"
76
+
77
+
78
+ def path(
79
+ path_value: str,
80
+ view: Callable[..., Any] | IncludedUrls,
81
+ *,
82
+ methods: tuple[str, ...] = ("GET",),
83
+ response_class: object | None = None,
84
+ name: str | None = None,
85
+ include_in_schema: bool = True,
86
+ enabled_when: str | Callable[..., object] | None = None,
87
+ dependencies: tuple[object, ...] = (),
88
+ ) -> UrlPathPattern | IncludedUrls:
89
+ if isinstance(view, IncludedUrls):
90
+ normalized_path_value = str(path_value or "").strip()
91
+ if normalized_path_value == "/":
92
+ normalized_path_value = ""
93
+ if normalized_path_value:
94
+ return IncludedUrls(
95
+ module_name=view.module_name,
96
+ path_prefix=normalized_path_value,
97
+ namespace=view.namespace,
98
+ )
99
+ return view
100
+ if not callable(view):
101
+ raise RuntimeError("path(...) requires a callable view function; string handler names are no longer supported.")
102
+ return UrlPathPattern(
103
+ path=str(path_value),
104
+ view=view,
105
+ methods=tuple(methods or ("GET",)),
106
+ response_class=response_class,
107
+ name=name,
108
+ include_in_schema=bool(include_in_schema),
109
+ enabled_when=enabled_when if callable(enabled_when) else (str(enabled_when or "").strip() or None),
110
+ dependencies=tuple(dependencies or ()),
111
+ )
112
+
113
+
114
+ def mount(
115
+ path_value: str,
116
+ *,
117
+ app: Callable[[], Any],
118
+ namespace: str,
119
+ name: str | None = None,
120
+ dependencies: tuple[object, ...] = (),
121
+ replace_target: str = "",
122
+ route_hints: tuple[str, ...] = (),
123
+ conflict_policy: str = "fail",
124
+ ) -> MountedUrlPattern:
125
+ return MountedUrlPattern(
126
+ path=str(path_value),
127
+ app=app,
128
+ namespace=str(namespace),
129
+ name=name,
130
+ dependencies=tuple(dependencies or ()),
131
+ replace_target=str(replace_target or ""),
132
+ route_hints=tuple(route_hints or ()),
133
+ conflict_policy=str(conflict_policy or "fail"),
134
+ )
135
+
136
+
137
+ def create_runtime_route_bindings(
138
+ definitions: tuple[RuntimeRouteBinding | dict[str, Any], ...] | list[RuntimeRouteBinding | dict[str, Any]],
139
+ ) -> tuple[RuntimeRouteBinding, ...]:
140
+ bindings: list[RuntimeRouteBinding] = []
141
+ for item in definitions:
142
+ if isinstance(item, RuntimeRouteBinding):
143
+ bindings.append(item)
144
+ continue
145
+ bindings.append(RuntimeRouteBinding(**dict(item)))
146
+ return tuple(bindings)
147
+
148
+
149
+ def create_lazy_mount_url_bindings(
150
+ definitions: tuple[LazyMountUrlBinding | dict[str, Any], ...] | list[LazyMountUrlBinding | dict[str, Any]],
151
+ ) -> tuple[LazyMountUrlBinding, ...]:
152
+ bindings: list[LazyMountUrlBinding] = []
153
+ for item in definitions:
154
+ if isinstance(item, LazyMountUrlBinding):
155
+ bindings.append(item)
156
+ continue
157
+ bindings.append(LazyMountUrlBinding(**dict(item)))
158
+ return tuple(bindings)
159
+
160
+
161
+ def _resolve_binding_path(binding: RuntimeRouteBinding, runtime: Any) -> str:
162
+ if callable(binding.path):
163
+ path = binding.path(runtime)
164
+ else:
165
+ path = binding.path
166
+ return str(path)
167
+
168
+
169
+ def _resolve_binding_endpoint(binding: RuntimeRouteBinding, runtime: Any) -> Any:
170
+ handlers = getattr(runtime, "handlers", None)
171
+ if handlers is None or not hasattr(handlers, binding.handler_attr):
172
+ raise RuntimeError(f"runtime.handlers must expose {binding.handler_attr!r}.")
173
+ return getattr(handlers, binding.handler_attr)
174
+
175
+
176
+ def _parse_template_path_param(segment: str) -> tuple[str, str] | None:
177
+ if not segment.startswith("{") or not segment.endswith("}"):
178
+ return None
179
+ body = segment[1:-1].strip()
180
+ if not body:
181
+ return None
182
+ if ":" in body:
183
+ name, convertor = body.split(":", 1)
184
+ else:
185
+ name, convertor = body, ""
186
+ name = name.strip()
187
+ if not name:
188
+ return None
189
+ return name, convertor.strip()
190
+
191
+
192
+ def _path_param_names(path_value: str) -> tuple[str, ...]:
193
+ names: list[str] = []
194
+ for segment in (item for item in str(path_value or "").strip("/").split("/") if item):
195
+ parsed = _parse_template_path_param(segment)
196
+ if parsed is not None:
197
+ names.append(parsed[0])
198
+ return tuple(names)
199
+
200
+
201
+ def _iter_enumerate_param_specs(binding: RuntimeRouteBinding) -> tuple[tuple[str, Any], ...]:
202
+ raw = binding.enumerate_params
203
+ if isinstance(raw, dict):
204
+ return tuple((str(name), values) for name, values in raw.items())
205
+ return tuple((str(name), values) for name, values in tuple(raw or ()))
206
+
207
+
208
+ def _resolve_enumerated_param_values(name: str, values: Any, runtime: Any) -> tuple[str, ...]:
209
+ raw_values = values(runtime) if callable(values) else values
210
+ if isinstance(raw_values, str):
211
+ raw_values = (raw_values,)
212
+ normalized: list[str] = []
213
+ seen: set[str] = set()
214
+ for item in tuple(raw_values or ()):
215
+ value = str(item or "").strip()
216
+ if not value or value in seen:
217
+ continue
218
+ if "/" in value or "{" in value or "}" in value:
219
+ raise RuntimeError(f"enumerated route param {name!r} contains an invalid path segment {value!r}.")
220
+ seen.add(value)
221
+ normalized.append(value)
222
+ if not normalized:
223
+ raise RuntimeError(f"enumerated route param {name!r} requires at least one value.")
224
+ return tuple(normalized)
225
+
226
+
227
+ def _normalize_path_projection_path(path_value: Any, *, index: int) -> str:
228
+ path_text = str(path_value or "").strip()
229
+ if not path_text:
230
+ raise RuntimeError(f"runtime route path projection #{index} requires a non-empty path.")
231
+ if not path_text.startswith("/"):
232
+ raise RuntimeError(f"runtime route path projection #{index} path must start with '/': {path_text!r}.")
233
+ stripped = path_text.strip("/")
234
+ if not stripped:
235
+ return path_text
236
+ for segment in stripped.split("/"):
237
+ if not segment:
238
+ raise RuntimeError(
239
+ f"runtime route path projection #{index} contains an empty path segment: {path_text!r}."
240
+ )
241
+ parsed = _parse_template_path_param(segment)
242
+ if ("{" in segment or "}" in segment) and parsed is None:
243
+ raise RuntimeError(
244
+ f"runtime route path projection #{index} contains an invalid path parameter segment {segment!r}."
245
+ )
246
+ return path_text
247
+
248
+
249
+ def _normalize_path_projection_constants(constants_value: Any, *, index: int) -> dict[str, Any]:
250
+ if not isinstance(constants_value, Mapping):
251
+ raise RuntimeError(f"runtime route path projection #{index} constants must be a mapping.")
252
+ constants: dict[str, Any] = {}
253
+ for raw_name, value in constants_value.items():
254
+ name = str(raw_name or "").strip()
255
+ if not name:
256
+ raise RuntimeError(f"runtime route path projection #{index} constants must declare non-empty names.")
257
+ if name in constants:
258
+ raise RuntimeError(f"runtime route path projection #{index} declares duplicate constant {name!r}.")
259
+ constants[name] = value
260
+ return constants
261
+
262
+
263
+ def _iter_path_projection_specs(
264
+ *,
265
+ binding: RuntimeRouteBinding,
266
+ runtime: Any,
267
+ path_value: str,
268
+ ) -> tuple[tuple[str, dict[str, Any]], ...]:
269
+ provider = binding.path_projections
270
+ if provider is None:
271
+ return ((str(path_value), {}),)
272
+ if not callable(provider):
273
+ raise RuntimeError("RuntimeRouteBinding.path_projections must be callable.")
274
+
275
+ raw_projections = provider(runtime)
276
+ if raw_projections is None:
277
+ raw_items: tuple[Any, ...] = ()
278
+ elif isinstance(raw_projections, Mapping):
279
+ raise RuntimeError("RuntimeRouteBinding.path_projections must return an iterable of projection mappings.")
280
+ else:
281
+ try:
282
+ raw_items = tuple(raw_projections)
283
+ except TypeError as exc:
284
+ raise RuntimeError(
285
+ "RuntimeRouteBinding.path_projections must return an iterable of projection mappings."
286
+ ) from exc
287
+
288
+ projections: list[tuple[str, dict[str, Any]]] = []
289
+ for index, projection in enumerate(raw_items, start=1):
290
+ if not isinstance(projection, Mapping):
291
+ raise RuntimeError(f"runtime route path projection #{index} must be a mapping.")
292
+ if "path" not in projection:
293
+ raise RuntimeError(f"runtime route path projection #{index} must declare a path.")
294
+ if "constants" not in projection:
295
+ raise RuntimeError(f"runtime route path projection #{index} must declare constants.")
296
+
297
+ projected_path = _normalize_path_projection_path(projection["path"], index=index)
298
+ constants = _normalize_path_projection_constants(projection["constants"], index=index)
299
+ path_constant_names = set(_path_param_names(projected_path)).intersection(constants)
300
+ if path_constant_names:
301
+ formatted_names = ", ".join(sorted(path_constant_names))
302
+ raise RuntimeError(
303
+ "runtime route path projection constants must not overlap dynamic path params: "
304
+ f"{formatted_names}."
305
+ )
306
+ projections.append((projected_path, constants))
307
+
308
+ if not projections:
309
+ raise RuntimeError("RuntimeRouteBinding.path_projections requires at least one projection.")
310
+ return tuple(projections)
311
+
312
+
313
+ def _materialize_enumerated_path(path_value: str, constants: dict[str, str]) -> str:
314
+ path_text = str(path_value or "")
315
+ if path_text == "/":
316
+ return "/"
317
+ trailing_slash = path_text.endswith("/")
318
+ segments: list[str] = []
319
+ for segment in (item for item in path_text.strip("/").split("/") if item):
320
+ parsed = _parse_template_path_param(segment)
321
+ if parsed is not None and parsed[0] in constants:
322
+ segments.append(constants[parsed[0]])
323
+ continue
324
+ segments.append(segment)
325
+ materialized = "/" + "/".join(segments)
326
+ if trailing_slash and materialized != "/":
327
+ materialized += "/"
328
+ return materialized
329
+
330
+
331
+ def _bind_enumerated_endpoint(endpoint: Any, constants: Mapping[str, Any]) -> Any:
332
+ if not constants:
333
+ return endpoint
334
+
335
+ async def _endpoint(**kwargs):
336
+ call_kwargs = dict(kwargs)
337
+ call_kwargs.update(constants)
338
+ result = endpoint(**call_kwargs)
339
+ if inspect.isawaitable(result):
340
+ return await result
341
+ return result
342
+
343
+ _endpoint.__name__ = str(getattr(endpoint, "__name__", "_runtime_route_endpoint") or "_runtime_route_endpoint")
344
+ _endpoint.__module__ = str(getattr(endpoint, "__module__", __name__) or __name__)
345
+ try:
346
+ signature = inspect.signature(endpoint)
347
+ try:
348
+ type_hints = get_type_hints(endpoint)
349
+ except Exception:
350
+ type_hints = {}
351
+ parameters = [
352
+ parameter.replace(annotation=type_hints.get(parameter.name, parameter.annotation))
353
+ for parameter in signature.parameters.values()
354
+ if parameter.name not in constants
355
+ ]
356
+ _endpoint.__signature__ = signature.replace(
357
+ parameters=parameters,
358
+ return_annotation=type_hints.get("return", signature.return_annotation),
359
+ )
360
+ except (TypeError, ValueError):
361
+ pass
362
+ return _endpoint
363
+
364
+
365
+ def _iter_materialized_projected_runtime_bindings(
366
+ *,
367
+ path_value: str,
368
+ endpoint: Any,
369
+ binding: RuntimeRouteBinding,
370
+ runtime: Any,
371
+ projection_constants: Mapping[str, Any],
372
+ ) -> tuple[tuple[str, Any], ...]:
373
+ enum_specs = _iter_enumerate_param_specs(binding)
374
+ if not enum_specs:
375
+ return ((str(path_value), _bind_enumerated_endpoint(endpoint, projection_constants)),)
376
+
377
+ path_param_names = set(_path_param_names(path_value))
378
+ resolved_specs: list[tuple[str, tuple[str, ...]]] = []
379
+ for name, values in enum_specs:
380
+ if not name:
381
+ raise RuntimeError("enumerated route params must declare non-empty names.")
382
+ if name not in path_param_names:
383
+ raise RuntimeError(f"enumerated route param {name!r} is not present in route path {path_value!r}.")
384
+ resolved_specs.append((name, _resolve_enumerated_param_values(name, values, runtime)))
385
+
386
+ materialized: list[tuple[str, Any]] = []
387
+ names = tuple(name for name, _values in resolved_specs)
388
+ value_sets = tuple(values for _name, values in resolved_specs)
389
+ for values in product(*value_sets):
390
+ enumerated_constants = dict(zip(names, values, strict=True))
391
+ duplicated_names = set(projection_constants).intersection(enumerated_constants)
392
+ if duplicated_names:
393
+ formatted_names = ", ".join(sorted(duplicated_names))
394
+ raise RuntimeError(f"runtime route constants are declared more than once: {formatted_names}.")
395
+ constants = dict(projection_constants)
396
+ constants.update(enumerated_constants)
397
+ materialized.append(
398
+ (
399
+ _materialize_enumerated_path(path_value, enumerated_constants),
400
+ _bind_enumerated_endpoint(endpoint, constants),
401
+ )
402
+ )
403
+ return tuple(materialized)
404
+
405
+
406
+ def _iter_materialized_runtime_bindings(
407
+ *,
408
+ path_value: str,
409
+ endpoint: Any,
410
+ binding: RuntimeRouteBinding,
411
+ runtime: Any,
412
+ ) -> tuple[tuple[str, Any], ...]:
413
+ materialized: list[tuple[str, Any]] = []
414
+ seen_paths: set[str] = set()
415
+ for projected_path, projection_constants in _iter_path_projection_specs(
416
+ binding=binding,
417
+ runtime=runtime,
418
+ path_value=path_value,
419
+ ):
420
+ for materialized_path, materialized_endpoint in _iter_materialized_projected_runtime_bindings(
421
+ path_value=projected_path,
422
+ endpoint=endpoint,
423
+ binding=binding,
424
+ runtime=runtime,
425
+ projection_constants=projection_constants,
426
+ ):
427
+ if materialized_path in seen_paths:
428
+ raise RuntimeError(f"runtime route path projection produced duplicate path {materialized_path!r}.")
429
+ seen_paths.add(materialized_path)
430
+ materialized.append((materialized_path, materialized_endpoint))
431
+ return tuple(materialized)
432
+
433
+
434
+ def _resolve_pattern_path(pattern: UrlPathPattern, runtime: Any | None) -> str:
435
+ return str(pattern.path)
436
+
437
+
438
+ def _resolve_pattern_endpoint(pattern: UrlPathPattern, runtime: Any | None) -> Any:
439
+ if runtime is not None:
440
+ handlers = getattr(runtime, "handlers", None)
441
+ handler_name = str(getattr(pattern.view, "__name__", "") or "").strip()
442
+ if handlers is not None and handler_name and hasattr(handlers, handler_name):
443
+ return getattr(handlers, handler_name)
444
+ return pattern.view
445
+
446
+
447
+ def _resolve_enabled_when(enabled_when: str | Callable[..., object] | None, runtime: Any | None) -> bool:
448
+ if enabled_when is None:
449
+ return True
450
+ if callable(enabled_when):
451
+ try:
452
+ return bool(enabled_when(runtime))
453
+ except TypeError:
454
+ return bool(enabled_when())
455
+ return bool(getattr(runtime, enabled_when, False))
456
+
457
+
458
+ def _binding_route_kwargs(
459
+ *,
460
+ methods: tuple[str, ...],
461
+ response_class: object | None,
462
+ name: str | None,
463
+ include_in_schema: bool,
464
+ dependencies: tuple[object, ...] = (),
465
+ ) -> dict[str, Any]:
466
+ kwargs: dict[str, Any] = {
467
+ "methods": list(methods),
468
+ "name": name,
469
+ "include_in_schema": include_in_schema,
470
+ }
471
+ if dependencies:
472
+ kwargs["dependencies"] = list(dependencies)
473
+ if response_class is not None:
474
+ kwargs["response_class"] = response_class
475
+ return kwargs
476
+
477
+
478
+ def build_runtime_router(
479
+ *,
480
+ runtime_factory: Callable[[], Any | None],
481
+ bindings: tuple[RuntimeRouteBinding, ...],
482
+ ) -> APIRouter:
483
+ router = APIRouter()
484
+ runtime = runtime_factory()
485
+ if runtime is None:
486
+ return router
487
+ for binding in bindings:
488
+ if binding.enabled_when and not bool(getattr(runtime, binding.enabled_when, False)):
489
+ continue
490
+ path_value = _resolve_binding_path(binding, runtime)
491
+ endpoint = _resolve_binding_endpoint(binding, runtime)
492
+ for materialized_path, materialized_endpoint in _iter_materialized_runtime_bindings(
493
+ path_value=path_value,
494
+ endpoint=endpoint,
495
+ binding=binding,
496
+ runtime=runtime,
497
+ ):
498
+ router.add_api_route(
499
+ materialized_path,
500
+ materialized_endpoint,
501
+ **_binding_route_kwargs(
502
+ methods=binding.methods,
503
+ response_class=binding.response_class,
504
+ name=binding.name,
505
+ include_in_schema=binding.include_in_schema,
506
+ ),
507
+ )
508
+ return router
509
+
510
+
511
+ def build_router_from_urlpatterns(
512
+ *,
513
+ urlpatterns: tuple[UrlPathPattern | IncludedUrls | MountedUrlPattern, ...],
514
+ runtime_factory: Callable[[], Any | None] | None = None,
515
+ ) -> APIRouter:
516
+ router = APIRouter()
517
+ runtime_required = any(
518
+ isinstance(item, UrlPathPattern) and isinstance(item.enabled_when, str)
519
+ for item in urlpatterns
520
+ )
521
+ runtime = None
522
+ if runtime_factory is not None:
523
+ runtime = runtime_factory()
524
+ if runtime is None:
525
+ return router
526
+ elif runtime_required:
527
+ raise RuntimeError("urlpatterns with conditional flags must declare runtime_factory.")
528
+ for item in urlpatterns:
529
+ if not isinstance(item, UrlPathPattern):
530
+ continue
531
+ if not _resolve_enabled_when(item.enabled_when, runtime):
532
+ continue
533
+ router.add_api_route(
534
+ _resolve_pattern_path(item, runtime),
535
+ _resolve_pattern_endpoint(item, runtime),
536
+ **_binding_route_kwargs(
537
+ methods=item.methods,
538
+ response_class=item.response_class,
539
+ name=item.name,
540
+ include_in_schema=item.include_in_schema,
541
+ dependencies=item.dependencies,
542
+ ),
543
+ )
544
+ return router
545
+
546
+
547
+ def build_declared_router(
548
+ *,
549
+ bindings: tuple[DeclaredEndpointBinding, ...],
550
+ ) -> APIRouter:
551
+ router = APIRouter()
552
+ for binding in bindings:
553
+ router.add_api_route(
554
+ str(binding.path),
555
+ binding.endpoint,
556
+ **_binding_route_kwargs(
557
+ methods=binding.methods,
558
+ response_class=binding.response_class,
559
+ name=binding.name,
560
+ include_in_schema=binding.include_in_schema,
561
+ ),
562
+ )
563
+ return router
564
+
565
+
566
+ def create_declared_router_url_spec(
567
+ *,
568
+ namespace: str,
569
+ bindings: tuple[DeclaredEndpointBinding, ...],
570
+ path_prefix: str = "",
571
+ dependencies: tuple[object, ...] = (),
572
+ replace_target: str = "",
573
+ route_hints: tuple[str, ...] = (),
574
+ conflict_policy: str = "fail",
575
+ ) -> DeclaredUrlSpec:
576
+ return DeclaredUrlSpec(
577
+ router=build_declared_router(bindings=bindings),
578
+ path_prefix=path_prefix,
579
+ namespace=namespace,
580
+ dependencies=dependencies,
581
+ replace_target=replace_target,
582
+ route_hints=route_hints,
583
+ conflict_policy=conflict_policy,
584
+ )
585
+
586
+
587
+ def create_runtime_router_url_spec(
588
+ *,
589
+ namespace: str,
590
+ runtime_factory: Callable[[], Any | None],
591
+ bindings: tuple[RuntimeRouteBinding, ...],
592
+ path_prefix: str = "",
593
+ dependencies: tuple[object, ...] = (),
594
+ replace_target: str = "",
595
+ route_hints: tuple[str, ...] = (),
596
+ conflict_policy: str = "fail",
597
+ ) -> DeclaredUrlSpec:
598
+ return DeclaredUrlSpec(
599
+ router=build_runtime_router(
600
+ runtime_factory=runtime_factory,
601
+ bindings=bindings,
602
+ ),
603
+ path_prefix=path_prefix,
604
+ namespace=namespace,
605
+ dependencies=dependencies,
606
+ replace_target=replace_target,
607
+ route_hints=route_hints,
608
+ conflict_policy=conflict_policy,
609
+ )
610
+
611
+
612
+ def build_runtime_router_from_spec(
613
+ *,
614
+ runtime_factory: Callable[[], Any | None],
615
+ bindings: tuple[RuntimeRouteBinding, ...],
616
+ ) -> APIRouter:
617
+ return build_runtime_router(
618
+ runtime_factory=runtime_factory,
619
+ bindings=bindings,
620
+ )
621
+
622
+
623
+ def create_lazy_mount_url_spec(
624
+ *,
625
+ namespace: str,
626
+ path: str,
627
+ app_factory: Callable[[], Any],
628
+ name: str | None = None,
629
+ dependencies: tuple[object, ...] = (),
630
+ replace_target: str = "",
631
+ route_hints: tuple[str, ...] = (),
632
+ conflict_policy: str = "fail",
633
+ ) -> DeclaredUrlSpec:
634
+ return DeclaredUrlSpec(
635
+ router=create_lazy_mount_router(
636
+ path_prefix=path,
637
+ app_factory=app_factory,
638
+ name=name,
639
+ ),
640
+ namespace=namespace,
641
+ dependencies=dependencies,
642
+ replace_target=replace_target,
643
+ route_hints=route_hints,
644
+ conflict_policy=conflict_policy,
645
+ )
646
+
647
+
648
+ def create_lazy_mount_url_specs(
649
+ bindings: tuple[LazyMountUrlBinding, ...] | list[LazyMountUrlBinding],
650
+ ) -> tuple[DeclaredUrlSpec, ...]:
651
+ return tuple(
652
+ create_lazy_mount_url_spec(
653
+ namespace=binding.namespace,
654
+ path=binding.path,
655
+ app_factory=binding.app_factory,
656
+ name=binding.name,
657
+ dependencies=tuple(binding.dependencies),
658
+ replace_target=binding.replace_target,
659
+ route_hints=tuple(binding.route_hints),
660
+ conflict_policy=binding.conflict_policy,
661
+ )
662
+ for binding in bindings
663
+ )
664
+
665
+
666
+ def _clone_declared_url_spec_with_prefix(
667
+ spec: DeclaredUrlSpec,
668
+ *,
669
+ path_prefix: str = "",
670
+ namespace_prefix: str = "",
671
+ ) -> DeclaredUrlSpec:
672
+ return DeclaredUrlSpec(
673
+ router=spec.router,
674
+ path_prefix=_join_path_prefix(path_prefix, spec.path_prefix),
675
+ namespace=f"{namespace_prefix}.{spec.namespace}" if namespace_prefix else spec.namespace,
676
+ dependencies=tuple(spec.dependencies),
677
+ replace_target=spec.replace_target,
678
+ route_hints=tuple(spec.route_hints),
679
+ conflict_policy=spec.conflict_policy,
680
+ )
681
+
682
+
683
+ def _coerce_declared_url_spec(item: object) -> DeclaredUrlSpec | None:
684
+ if isinstance(item, DeclaredUrlSpec):
685
+ return item
686
+ router = getattr(item, "router", None)
687
+ if not isinstance(router, APIRouter):
688
+ return None
689
+ if not hasattr(item, "namespace"):
690
+ return None
691
+ return DeclaredUrlSpec(
692
+ router=router,
693
+ path_prefix=getattr(item, "path_prefix", ""),
694
+ namespace=getattr(item, "namespace", ""),
695
+ dependencies=tuple(getattr(item, "dependencies", ()) or ()),
696
+ replace_target=getattr(item, "replace_target", ""),
697
+ route_hints=tuple(getattr(item, "route_hints", ()) or ()),
698
+ conflict_policy=getattr(item, "conflict_policy", "fail"),
699
+ )
700
+
701
+
702
+ def _coerce_url_path_pattern(item: object) -> UrlPathPattern | None:
703
+ if isinstance(item, UrlPathPattern):
704
+ return item
705
+ if not hasattr(item, "view") or not hasattr(item, "path"):
706
+ return None
707
+ return UrlPathPattern(
708
+ path=getattr(item, "path"),
709
+ view=getattr(item, "view"),
710
+ methods=tuple(getattr(item, "methods", ("GET",)) or ("GET",)),
711
+ response_class=getattr(item, "response_class", None),
712
+ name=getattr(item, "name", None),
713
+ include_in_schema=bool(getattr(item, "include_in_schema", True)),
714
+ enabled_when=getattr(item, "enabled_when", None),
715
+ dependencies=tuple(getattr(item, "dependencies", ()) or ()),
716
+ )
717
+
718
+
719
+ def _coerce_mounted_url_pattern(item: object) -> MountedUrlPattern | None:
720
+ if isinstance(item, MountedUrlPattern):
721
+ return item
722
+ if not hasattr(item, "app") or not hasattr(item, "path") or not hasattr(item, "namespace"):
723
+ return None
724
+ return MountedUrlPattern(
725
+ path=getattr(item, "path"),
726
+ app=getattr(item, "app"),
727
+ namespace=getattr(item, "namespace"),
728
+ name=getattr(item, "name", None),
729
+ dependencies=tuple(getattr(item, "dependencies", ()) or ()),
730
+ replace_target=getattr(item, "replace_target", ""),
731
+ route_hints=tuple(getattr(item, "route_hints", ()) or ()),
732
+ conflict_policy=getattr(item, "conflict_policy", "fail"),
733
+ )
734
+
735
+
736
+ def _coerce_included_urls(item: object) -> IncludedUrls | None:
737
+ if isinstance(item, IncludedUrls):
738
+ return item
739
+ if not hasattr(item, "module_name"):
740
+ return None
741
+ return IncludedUrls(
742
+ module_name=getattr(item, "module_name"),
743
+ path_prefix=getattr(item, "path_prefix", ""),
744
+ namespace=getattr(item, "namespace", ""),
745
+ )
746
+
747
+
748
+ def compile_urlpatterns(
749
+ *,
750
+ urlpatterns: tuple[DeclaredUrlSpec | UrlPathPattern | MountedUrlPattern | IncludedUrls, ...]
751
+ | list[DeclaredUrlSpec | UrlPathPattern | MountedUrlPattern | IncludedUrls],
752
+ app_name: str = "",
753
+ runtime_factory: Callable[[], Any | None] | None = None,
754
+ route_hints: tuple[str, ...] = (),
755
+ ) -> tuple[DeclaredUrlSpec, ...]:
756
+ specs: list[DeclaredUrlSpec] = []
757
+ pending_path_patterns: list[UrlPathPattern] = []
758
+
759
+ def _flush_path_patterns() -> None:
760
+ nonlocal pending_path_patterns
761
+ if not pending_path_patterns:
762
+ return
763
+ if not str(app_name or "").strip():
764
+ raise RuntimeError("urlpatterns with path(...) entries must declare app_name.")
765
+ specs.append(
766
+ DeclaredUrlSpec(
767
+ router=build_router_from_urlpatterns(
768
+ urlpatterns=tuple(pending_path_patterns),
769
+ runtime_factory=runtime_factory,
770
+ ),
771
+ namespace=str(app_name),
772
+ route_hints=tuple(route_hints or ()),
773
+ ),
774
+ )
775
+ pending_path_patterns = []
776
+
777
+ for item in urlpatterns:
778
+ declared_spec = _coerce_declared_url_spec(item)
779
+ if declared_spec is not None:
780
+ _flush_path_patterns()
781
+ specs.append(declared_spec)
782
+ continue
783
+ url_path_pattern = _coerce_url_path_pattern(item)
784
+ if url_path_pattern is not None:
785
+ pending_path_patterns.append(url_path_pattern)
786
+ continue
787
+ mounted_url_pattern = _coerce_mounted_url_pattern(item)
788
+ if mounted_url_pattern is not None:
789
+ _flush_path_patterns()
790
+ specs.append(
791
+ create_lazy_mount_url_spec(
792
+ namespace=mounted_url_pattern.namespace,
793
+ path=mounted_url_pattern.path,
794
+ app_factory=mounted_url_pattern.app,
795
+ name=mounted_url_pattern.name,
796
+ dependencies=tuple(mounted_url_pattern.dependencies),
797
+ replace_target=mounted_url_pattern.replace_target,
798
+ route_hints=tuple(mounted_url_pattern.route_hints),
799
+ conflict_policy=mounted_url_pattern.conflict_policy,
800
+ )
801
+ )
802
+ continue
803
+ included_urls = _coerce_included_urls(item)
804
+ if included_urls is not None:
805
+ _flush_path_patterns()
806
+ from .loaders import load_url_module_specs
807
+
808
+ for spec in load_url_module_specs(included_urls.module_name):
809
+ specs.append(
810
+ _clone_declared_url_spec_with_prefix(
811
+ spec,
812
+ path_prefix=included_urls.path_prefix,
813
+ namespace_prefix=included_urls.namespace,
814
+ )
815
+ )
816
+ continue
817
+ raise RuntimeError(
818
+ "urlpatterns must contain only path(...), mount(...), include(...), or DeclaredUrlSpec entries."
819
+ )
820
+ _flush_path_patterns()
821
+ return tuple(specs)
822
+
823
+
824
+ __all__ = [
825
+ "DeclaredEndpointBinding",
826
+ "MountedUrlPattern",
827
+ "LazyMountUrlBinding",
828
+ "build_declared_router",
829
+ "build_router_from_urlpatterns",
830
+ "create_lazy_mount_url_bindings",
831
+ "create_declared_router_url_spec",
832
+ "compile_urlpatterns",
833
+ "mount",
834
+ "path",
835
+ "RuntimeRouteBinding",
836
+ "build_runtime_router",
837
+ "build_runtime_router_from_spec",
838
+ "create_runtime_route_bindings",
839
+ "create_lazy_mount_url_spec",
840
+ "create_lazy_mount_url_specs",
841
+ "create_runtime_router_url_spec",
842
+ "UrlPathPattern",
843
+ ]