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,724 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import importlib
5
+ import traceback
6
+ from dataclasses import dataclass
7
+ from types import SimpleNamespace
8
+ from typing import Any, Callable, Optional
9
+
10
+ from ..observability import get_logger
11
+
12
+ try:
13
+ from tortoise import Tortoise, connections
14
+ except Exception: # pragma: no cover
15
+ Tortoise = None # type: ignore[assignment]
16
+ connections = None # type: ignore[assignment]
17
+
18
+
19
+ logger = get_logger(__name__)
20
+
21
+ ConfigBuilder = Callable[[], dict[str, Any]]
22
+ ClusterFactory = Callable[[], Any]
23
+
24
+
25
+ @dataclass(frozen=True, slots=True)
26
+ class _RuntimeStateInspection:
27
+ connections_compatible: bool
28
+ apps_compatible: bool
29
+ reasons: tuple[str, ...]
30
+
31
+ @property
32
+ def compatible(self) -> bool:
33
+ return self.connections_compatible and self.apps_compatible
34
+
35
+ @property
36
+ def can_rebuild_aliases(self) -> bool:
37
+ return (not self.connections_compatible) and self.apps_compatible
38
+
39
+
40
+ class _FallbackConnection:
41
+ def __init__(self, alias: str, payload: Any) -> None:
42
+ self.alias = alias
43
+ self.payload = payload
44
+
45
+ async def execute_query(self, query: str, params: list[Any] | None = None):
46
+ return {"alias": self.alias, "query": query, "params": list(params or []), "payload": self.payload}
47
+
48
+ def in_transaction(self):
49
+ class _Tx:
50
+ async def __aenter__(self_inner):
51
+ return self_inner
52
+
53
+ async def __aexit__(self_inner, exc_type, exc, tb):
54
+ return False
55
+
56
+ return _Tx()
57
+
58
+ async def close(self) -> None:
59
+ return None
60
+
61
+
62
+ class _TortoiseManager:
63
+ def __init__(self) -> None:
64
+ self._init_lock = asyncio.Lock()
65
+ self._initialized: bool = False
66
+ self._last_config: Optional[dict[str, Any]] = None
67
+ self._cluster = None
68
+ self._ctx = None
69
+ self._pool_close_patched = False
70
+ self._fallback_connections: dict[str, _FallbackConnection] = {}
71
+ self._last_reconnect_at: dict[str, float] = {}
72
+ self._reconnect_min_interval = 0.1
73
+ self._loop_id: int | None = None
74
+
75
+ async def _close_tortoise_connections_quietly(self, *, reason: str) -> None:
76
+ if Tortoise is None:
77
+ return
78
+ try:
79
+ await Tortoise.close_connections()
80
+ except asyncio.CancelledError:
81
+ # Safe to ignore here because this path only runs while tearing down or
82
+ # replacing an old Tortoise connection set. Treating it as cleanup noise
83
+ # avoids surfacing a transient shutdown cancellation as a startup failure.
84
+ try:
85
+ logger.debug("Ignored CancelledError from Tortoise.close_connections during %s", reason)
86
+ except Exception:
87
+ pass
88
+ except Exception:
89
+ pass
90
+
91
+ def _install_pool_close_probe(self) -> None:
92
+ if self._pool_close_patched:
93
+ return
94
+ try:
95
+ import asyncpg.pool as _ap # type: ignore
96
+ except Exception:
97
+ return
98
+ original_close = _ap.Pool.close
99
+
100
+ async def _logged_close(pool, *args, **kwargs):
101
+ try:
102
+ logger.error("asyncpg pool.close called\n%s", "".join(traceback.format_stack(limit=12)))
103
+ except Exception:
104
+ pass
105
+ return await original_close(pool, *args, **kwargs)
106
+
107
+ _ap.Pool.close = _logged_close
108
+ self._pool_close_patched = True
109
+
110
+ def _configured_aliases(self) -> tuple[str, ...]:
111
+ return tuple((self._last_config or {}).get("connections", {}).keys())
112
+
113
+ @staticmethod
114
+ def _current_loop_id() -> int | None:
115
+ try:
116
+ return id(asyncio.get_running_loop())
117
+ except RuntimeError:
118
+ return None
119
+
120
+ @staticmethod
121
+ def _get_active_context() -> Any | None:
122
+ try:
123
+ if connections is None:
124
+ return None
125
+ if not bool(getattr(connections, "_db_config", None)) and not connections._get_storage():
126
+ return None
127
+ return SimpleNamespace(
128
+ connections=connections,
129
+ inited=bool(getattr(connections, "_db_config", None)),
130
+ )
131
+ except Exception:
132
+ return None
133
+
134
+ def _adopt_active_context(self, cfg: dict[str, Any], *, current_loop_id: int | None = None) -> bool:
135
+ ctx = self._get_active_context()
136
+ if ctx is None or not bool(getattr(ctx, "inited", False)):
137
+ return False
138
+ try:
139
+ available_aliases = set(getattr(ctx.connections, "_db_config", {}) or {})
140
+ except Exception:
141
+ return False
142
+ expected_aliases = set((cfg or {}).get("connections", {}).keys())
143
+ if not available_aliases:
144
+ return False
145
+ if expected_aliases and not expected_aliases.intersection(available_aliases) and "default" not in available_aliases:
146
+ return False
147
+ inspection = self._inspect_runtime_state(cfg, current_loop_id=current_loop_id)
148
+ if not inspection.compatible:
149
+ return False
150
+ self._ctx = ctx
151
+ self._initialized = True
152
+ self._install_pool_close_probe()
153
+ return True
154
+
155
+ @staticmethod
156
+ def _configs_equivalent(left: dict[str, Any] | None, right: dict[str, Any] | None) -> bool:
157
+ return dict(left or {}) == dict(right or {})
158
+
159
+ def _expected_model_names(self, cfg: dict[str, Any]) -> dict[str, set[str]]:
160
+ if Tortoise is None:
161
+ return {}
162
+ expected: dict[str, set[str]] = {}
163
+ for app_label, app_cfg in (cfg or {}).get("apps", {}).items():
164
+ discovered: set[str] = set()
165
+ for module_path in app_cfg.get("models", []):
166
+ try:
167
+ models = Tortoise._discover_models(module_path, app_label)
168
+ except Exception:
169
+ return {}
170
+ for model in models:
171
+ discovered.add(f"{model.__module__}.{model.__name__}")
172
+ expected[app_label] = discovered
173
+ return expected
174
+
175
+ @staticmethod
176
+ def _expected_aliases(cfg: dict[str, Any]) -> tuple[str, ...]:
177
+ return tuple(dict((cfg or {}).get("connections", {}) or {}).keys())
178
+
179
+ def _inspect_runtime_state(
180
+ self,
181
+ cfg: dict[str, Any],
182
+ *,
183
+ current_loop_id: int | None = None,
184
+ ) -> _RuntimeStateInspection:
185
+ connection_reasons = self._inspect_connection_state(cfg, current_loop_id=current_loop_id)
186
+ app_reasons = self._inspect_app_state(cfg)
187
+ return _RuntimeStateInspection(
188
+ connections_compatible=not connection_reasons,
189
+ apps_compatible=not app_reasons,
190
+ reasons=tuple((*connection_reasons, *app_reasons)),
191
+ )
192
+
193
+ def _inspect_connection_state(
194
+ self,
195
+ cfg: dict[str, Any],
196
+ *,
197
+ current_loop_id: int | None = None,
198
+ ) -> tuple[str, ...]:
199
+ expected_aliases = set(self._expected_aliases(cfg))
200
+ if Tortoise is None or connections is None:
201
+ if expected_aliases.issubset(set(self._fallback_connections.keys())):
202
+ return ()
203
+ missing = sorted(expected_aliases.difference(self._fallback_connections.keys()))
204
+ return tuple(f"missing_fallback_alias:{alias}" for alias in missing)
205
+
206
+ reasons: list[str] = []
207
+ try:
208
+ current_aliases = set(getattr(connections, "db_config", {}).keys())
209
+ except Exception:
210
+ return ("db_config_unavailable",)
211
+ missing_config_aliases = sorted(expected_aliases.difference(current_aliases))
212
+ reasons.extend(f"missing_db_config_alias:{alias}" for alias in missing_config_aliases)
213
+
214
+ try:
215
+ storage = connections._get_storage()
216
+ except Exception:
217
+ return (*reasons, "connection_storage_unavailable")
218
+
219
+ live_aliases = set(storage.keys())
220
+ missing_live_aliases = sorted(expected_aliases.difference(live_aliases))
221
+ reasons.extend(f"missing_live_alias:{alias}" for alias in missing_live_aliases)
222
+
223
+ for alias in expected_aliases.intersection(live_aliases):
224
+ conn = storage.get(alias)
225
+ pool = getattr(conn, "_pool", None)
226
+ if pool is not None and bool(getattr(pool, "closed", False) or getattr(pool, "_closed", False)):
227
+ reasons.append(f"closed_pool:{alias}")
228
+ continue
229
+ if not self._connection_pools_match_loop(
230
+ storage,
231
+ {alias},
232
+ current_loop_id=current_loop_id,
233
+ ):
234
+ reasons.append(f"loop_mismatch:{alias}")
235
+ return tuple(reasons)
236
+
237
+ def _inspect_app_state(self, cfg: dict[str, Any]) -> tuple[str, ...]:
238
+ if Tortoise is None:
239
+ return ()
240
+
241
+ reasons: list[str] = []
242
+ try:
243
+ expected_apps = set((cfg or {}).get("apps", {}).keys())
244
+ current_apps = getattr(Tortoise, "apps", {}) or {}
245
+ missing_apps = sorted(expected_apps.difference(set(current_apps.keys())))
246
+ reasons.extend(f"missing_app_registry:{app_label}" for app_label in missing_apps)
247
+
248
+ expected_model_names = self._expected_model_names(cfg)
249
+ if expected_apps and not expected_model_names:
250
+ reasons.append("expected_models_unavailable")
251
+ return tuple(reasons)
252
+
253
+ for app_label in sorted(expected_apps):
254
+ app_models = current_apps.get(app_label)
255
+ if not app_models:
256
+ if f"missing_app_registry:{app_label}" not in reasons:
257
+ reasons.append(f"missing_models:{app_label}")
258
+ continue
259
+ current_model_names = {f"{model.__module__}.{model.__name__}" for model in app_models.values()}
260
+ expected_names = expected_model_names.get(app_label, set())
261
+ if current_model_names != expected_names:
262
+ reasons.append(f"model_registry_mismatch:{app_label}")
263
+ for model in app_models.values():
264
+ meta = getattr(model, "_meta", None)
265
+ if meta is None:
266
+ continue
267
+ if getattr(meta, "default_connection", None) is None:
268
+ reasons.append(f"missing_default_connection:{model.__module__}.{model.__name__}")
269
+ basequery = getattr(meta, "basequery", None)
270
+ if basequery is None or not hasattr(basequery, "get_parameterized_sql"):
271
+ reasons.append(f"missing_basequery:{model.__module__}.{model.__name__}")
272
+ except Exception:
273
+ return ("app_registry_inspection_failed",)
274
+ return tuple(reasons)
275
+
276
+ def _is_current_state_compatible(self, cfg: dict[str, Any], *, current_loop_id: int | None = None) -> bool:
277
+ inspection = self._inspect_runtime_state(cfg, current_loop_id=current_loop_id)
278
+ return inspection.compatible
279
+
280
+ @staticmethod
281
+ def _pool_matches_loop(pool: Any, *, current_loop_id: int | None) -> bool:
282
+ if current_loop_id is None or pool is None:
283
+ return True
284
+ pool_loop = getattr(pool, "_loop", None)
285
+ if pool_loop is not None and id(pool_loop) != current_loop_id:
286
+ return False
287
+ return True
288
+
289
+ @staticmethod
290
+ def _connection_pools_match_loop(
291
+ storage: dict[str, Any],
292
+ expected_aliases: set[str],
293
+ *,
294
+ current_loop_id: int | None,
295
+ ) -> bool:
296
+ if current_loop_id is None:
297
+ return True
298
+ for alias in expected_aliases:
299
+ conn = storage.get(alias)
300
+ pool = getattr(conn, "_pool", None)
301
+ if pool is not None and not _TortoiseManager._pool_matches_loop(pool, current_loop_id=current_loop_id):
302
+ return False
303
+ if pool is None:
304
+ continue
305
+ workers = tuple(getattr(pool, "_workers", ()) or ())
306
+ scheduler = getattr(pool, "_sched_runner", None)
307
+ maintenance_tasks = tuple(task for task in (*workers, scheduler) if task is not None)
308
+ if not maintenance_tasks and hasattr(pool, "_tasks"):
309
+ return False
310
+ for task in maintenance_tasks:
311
+ done = getattr(task, "done", None)
312
+ if callable(done):
313
+ try:
314
+ if bool(done()):
315
+ return False
316
+ except Exception:
317
+ return False
318
+ get_loop = getattr(task, "get_loop", None)
319
+ if not callable(get_loop):
320
+ continue
321
+ try:
322
+ task_loop = get_loop()
323
+ except Exception:
324
+ return False
325
+ if id(task_loop) != current_loop_id:
326
+ return False
327
+ return True
328
+
329
+ def _connection_pool_ready(self, alias: str) -> bool:
330
+ if connections is None:
331
+ return alias in self._fallback_connections
332
+ try:
333
+ storage = connections._get_storage()
334
+ conn = storage.get(alias)
335
+ except Exception:
336
+ return False
337
+ if conn is None:
338
+ return False
339
+ pool = getattr(conn, "_pool", None)
340
+ if pool is not None and bool(getattr(pool, "closed", False) or getattr(pool, "_closed", False)):
341
+ return False
342
+ return self._connection_pools_match_loop(
343
+ storage,
344
+ {alias},
345
+ current_loop_id=self._current_loop_id(),
346
+ )
347
+
348
+ @staticmethod
349
+ def _has_real_tortoise_config(cfg: dict[str, Any]) -> bool:
350
+ connections_cfg = dict((cfg or {}).get("connections", {}))
351
+ if not connections_cfg:
352
+ return False
353
+ for payload in connections_cfg.values():
354
+ if not isinstance(payload, dict):
355
+ return False
356
+ if payload.get("db_url"):
357
+ continue
358
+ engine = payload.get("engine")
359
+ if isinstance(engine, str) and engine.strip():
360
+ try:
361
+ importlib.import_module(engine.strip())
362
+ except Exception:
363
+ return False
364
+ continue
365
+ return False
366
+ return True
367
+
368
+ @staticmethod
369
+ def _bind_configured_models(cfg: dict[str, Any]) -> None:
370
+ if Tortoise is None:
371
+ return
372
+ current_apps = getattr(Tortoise, "apps", None)
373
+ if not isinstance(current_apps, dict):
374
+ return
375
+ for app_label, app_cfg in dict((cfg or {}).get("apps", {}) or {}).items():
376
+ modules = tuple(app_cfg.get("models", ()) or ())
377
+ if not modules:
378
+ continue
379
+ default_connection = str(app_cfg.get("default_connection") or "default").strip() or "default"
380
+ registry = current_apps.setdefault(app_label, {})
381
+ for module_path in modules:
382
+ try:
383
+ models = Tortoise._discover_models(module_path, app_label)
384
+ except Exception:
385
+ continue
386
+ for model in models:
387
+ meta = getattr(model, "_meta", None)
388
+ if meta is None:
389
+ continue
390
+ if getattr(meta, "default_connection", None) is None:
391
+ meta.default_connection = default_connection
392
+ registry.setdefault(getattr(model, "__name__", str(id(model))), model)
393
+
394
+ @staticmethod
395
+ def _clear_global_context_fallback() -> None:
396
+ return None
397
+
398
+ async def _init_with_config(self, cfg: dict[str, Any]) -> None:
399
+ if Tortoise is None or not self._has_real_tortoise_config(cfg):
400
+ if Tortoise is not None:
401
+ await self._close_tortoise_connections_quietly(reason="fallback init")
402
+ self._clear_global_context_fallback()
403
+ try:
404
+ await Tortoise._reset_apps()
405
+ except Exception:
406
+ pass
407
+ self._ctx = None
408
+ self._fallback_connections = {
409
+ alias: _FallbackConnection(alias, payload)
410
+ for alias, payload in dict((cfg or {}).get("connections", {})).items()
411
+ }
412
+ return
413
+
414
+ self._fallback_connections.clear()
415
+ await self._close_tortoise_connections_quietly(reason="real init")
416
+ self._clear_global_context_fallback()
417
+ try:
418
+ await Tortoise._reset_apps()
419
+ except Exception:
420
+ pass
421
+
422
+ try:
423
+ self._ctx = await Tortoise.init(config=cfg, _enable_global_fallback=True) # type: ignore[call-arg]
424
+ except TypeError:
425
+ self._ctx = await Tortoise.init(config=cfg)
426
+ except Exception as exc:
427
+ if "Global context fallback is already enabled" in str(exc):
428
+ self._ctx = await Tortoise.init(config=cfg, _enable_global_fallback=False) # type: ignore[call-arg]
429
+ else:
430
+ raise
431
+
432
+ async def _close_connection(self, conn: Any) -> None:
433
+ if conn is None:
434
+ return
435
+ close = getattr(conn, "close", None)
436
+ if close is None:
437
+ return
438
+ try:
439
+ result = close()
440
+ if asyncio.iscoroutine(result):
441
+ await result
442
+ except asyncio.CancelledError:
443
+ # Alias rebuild publishes the replacement connection before retiring the
444
+ # previous pool. Cross-loop or already-tearing-down pools can surface a
445
+ # cancellation during close; once the new alias is live this is cleanup
446
+ # noise, not a fatal runtime initialization failure.
447
+ try:
448
+ logger.debug("Ignored CancelledError while retiring rebuilt DB connection")
449
+ except Exception:
450
+ pass
451
+ except Exception:
452
+ pass
453
+
454
+ async def _close_cluster(self, cluster: Any) -> None:
455
+ if cluster is None:
456
+ return
457
+ for attr_name in ("aclose", "close", "shutdown", "disconnect"):
458
+ closer = getattr(cluster, attr_name, None)
459
+ if closer is None:
460
+ continue
461
+ try:
462
+ result = closer()
463
+ if asyncio.iscoroutine(result):
464
+ await result
465
+ except Exception:
466
+ pass
467
+ return
468
+
469
+ def _rebuild_connection(self, alias: str):
470
+ if connections is None:
471
+ old_conn = self._fallback_connections.get(alias)
472
+ payload = ((self._last_config or {}).get("connections", {}) or {}).get(alias)
473
+ if payload is not None:
474
+ self._fallback_connections[alias] = _FallbackConnection(alias, payload)
475
+ return old_conn, self._fallback_connections.get(alias)
476
+
477
+ old_conn = connections.discard(alias)
478
+ new_conn = connections._create_connection(alias)
479
+ connections._get_storage()[alias] = new_conn
480
+ return old_conn, new_conn
481
+
482
+ def _rebuild_aliases(self, aliases: tuple[str, ...]) -> list[Any]:
483
+ rebuilt_old_connections: list[Any] = []
484
+ for name in aliases:
485
+ old_conn, _new_conn = self._rebuild_connection(name)
486
+ rebuilt_old_connections.append(old_conn)
487
+ self._install_pool_close_probe()
488
+ scope_key = ",".join(aliases) if aliases else "*"
489
+ self._last_reconnect_at[scope_key] = asyncio.get_running_loop().time()
490
+ return rebuilt_old_connections
491
+
492
+ def has_connection(self, alias: str) -> bool:
493
+ if self._fallback_connections:
494
+ return alias in self._fallback_connections
495
+ if connections is None:
496
+ return False
497
+ try:
498
+ return alias in connections._get_storage()
499
+ except Exception:
500
+ return False
501
+
502
+ def get_connection(self, alias: str):
503
+ if self._fallback_connections:
504
+ if alias not in self._fallback_connections:
505
+ raise KeyError(alias)
506
+ return self._fallback_connections[alias]
507
+ if Tortoise is None:
508
+ raise KeyError(alias)
509
+ return Tortoise.get_connection(alias)
510
+
511
+ async def generate_schemas(self, *, safe: bool = True) -> None:
512
+ if self._fallback_connections:
513
+ raise RuntimeError("schema generation is unavailable for fallback-only connections")
514
+ if Tortoise is None:
515
+ raise RuntimeError("tortoise is unavailable")
516
+ await Tortoise.generate_schemas(safe=safe)
517
+
518
+ async def ensure_initialized(
519
+ self,
520
+ config_builder: Optional[ConfigBuilder] = None,
521
+ cluster_factory: Optional[ClusterFactory] = None,
522
+ ) -> None:
523
+ cfg_builder = config_builder or self._default_config_builder()
524
+ cfg = cfg_builder() if cfg_builder is not None else {"connections": {"default": {}}, "apps": {}}
525
+ wants_fallback_only = not self._has_real_tortoise_config(cfg)
526
+ current_loop_id = self._current_loop_id()
527
+ loop_changed = (
528
+ current_loop_id is not None
529
+ and self._loop_id is not None
530
+ and self._loop_id != current_loop_id
531
+ )
532
+
533
+ if wants_fallback_only:
534
+ if self._initialized and self._configs_equivalent(self._last_config, cfg) and (
535
+ self._fallback_connections or self._is_current_state_compatible(cfg)
536
+ ):
537
+ self._last_config = cfg
538
+ self._loop_id = current_loop_id
539
+ return
540
+ async with self._init_lock:
541
+ if self._initialized and self._configs_equivalent(self._last_config, cfg) and (
542
+ self._fallback_connections or self._is_current_state_compatible(cfg)
543
+ ):
544
+ self._last_config = cfg
545
+ self._loop_id = current_loop_id
546
+ return
547
+
548
+ old_cluster = self._cluster
549
+ self._last_config = cfg
550
+ await self._init_with_config(cfg)
551
+ new_cluster = None
552
+ try:
553
+ if cluster_factory is not None:
554
+ new_cluster = cluster_factory()
555
+ except Exception:
556
+ new_cluster = None
557
+ self._cluster = new_cluster
558
+ if old_cluster is not None and old_cluster is not new_cluster:
559
+ await self._close_cluster(old_cluster)
560
+ self._initialized = True
561
+ self._loop_id = current_loop_id
562
+ return
563
+
564
+ if self._adopt_active_context(cfg, current_loop_id=current_loop_id):
565
+ self._last_config = cfg
566
+ self._loop_id = current_loop_id
567
+ return
568
+
569
+ if Tortoise is not None and bool(getattr(Tortoise, "_inited", False)):
570
+ if (
571
+ not loop_changed
572
+ and self._is_current_state_compatible(cfg, current_loop_id=current_loop_id)
573
+ and self._configs_equivalent(self._last_config, cfg)
574
+ ):
575
+ try:
576
+ self._ctx = self._ctx or self._get_active_context()
577
+ except Exception:
578
+ pass
579
+ self._initialized = True
580
+ self._last_config = cfg
581
+ self._loop_id = current_loop_id
582
+ self._install_pool_close_probe()
583
+ return
584
+
585
+ if (
586
+ not loop_changed
587
+ and self._initialized
588
+ and self._is_current_state_compatible(cfg, current_loop_id=current_loop_id)
589
+ and self._configs_equivalent(self._last_config, cfg)
590
+ ):
591
+ self._last_config = cfg
592
+ self._loop_id = current_loop_id
593
+ return
594
+
595
+ rebuilt_old_connections: list[Any] = []
596
+ should_close_rebuilt_connections = False
597
+ handled_rebuild = False
598
+ async with self._init_lock:
599
+ inspection = self._inspect_runtime_state(cfg, current_loop_id=current_loop_id)
600
+ if (
601
+ self._initialized
602
+ and inspection.compatible
603
+ and self._configs_equivalent(self._last_config, cfg)
604
+ ):
605
+ if loop_changed and not wants_fallback_only:
606
+ aliases = self._configured_aliases()
607
+ rebuilt_old_connections = self._rebuild_aliases(aliases)
608
+ should_close_rebuilt_connections = True
609
+ self._last_config = cfg
610
+ self._loop_id = current_loop_id
611
+ handled_rebuild = True
612
+ if not should_close_rebuilt_connections:
613
+ return
614
+
615
+ elif (
616
+ self._initialized
617
+ and not wants_fallback_only
618
+ and inspection.can_rebuild_aliases
619
+ and self._configs_equivalent(self._last_config, cfg)
620
+ ):
621
+ aliases = self._expected_aliases(cfg)
622
+ logger.warning(
623
+ "Tortoise runtime degraded; rebuilding configured aliases without full re-init: %s",
624
+ ", ".join(inspection.reasons) or "unknown",
625
+ )
626
+ rebuilt_old_connections = self._rebuild_aliases(aliases)
627
+ should_close_rebuilt_connections = True
628
+ self._last_config = cfg
629
+ self._loop_id = current_loop_id
630
+ handled_rebuild = True
631
+ if not should_close_rebuilt_connections:
632
+ return
633
+
634
+ else:
635
+ if self._initialized and not inspection.compatible:
636
+ logger.warning(
637
+ "Tortoise runtime requires full re-init: %s",
638
+ ", ".join(inspection.reasons) or "unknown",
639
+ )
640
+ old_cluster = self._cluster
641
+ self._last_config = cfg
642
+ await self._init_with_config(cfg)
643
+ self._bind_configured_models(cfg)
644
+ self._install_pool_close_probe()
645
+ new_cluster = None
646
+ try:
647
+ if cluster_factory is not None:
648
+ new_cluster = cluster_factory()
649
+ except Exception:
650
+ new_cluster = None
651
+ self._cluster = new_cluster
652
+ if old_cluster is not None and old_cluster is not new_cluster:
653
+ await self._close_cluster(old_cluster)
654
+ self._initialized = True
655
+ self._loop_id = current_loop_id
656
+
657
+ if handled_rebuild and should_close_rebuilt_connections:
658
+ for old_conn in rebuilt_old_connections:
659
+ await self._close_connection(old_conn)
660
+ return
661
+
662
+ @staticmethod
663
+ def _default_config_builder() -> Optional[ConfigBuilder]:
664
+ try:
665
+ from pz_foundation.host.db.config import get_tortoise_config
666
+
667
+ return get_tortoise_config
668
+ except Exception:
669
+ return None
670
+
671
+ async def reconnect(self, alias: str | None = None) -> None:
672
+ if self._last_config is None:
673
+ await self.ensure_initialized()
674
+ return
675
+
676
+ async with self._init_lock:
677
+ aliases = self._configured_aliases()
678
+ if alias is not None:
679
+ aliases = tuple(name for name in aliases if name == alias)
680
+ if not aliases:
681
+ raise KeyError(alias)
682
+ scope_key = ",".join(aliases) if aliases else "*"
683
+ now = asyncio.get_running_loop().time()
684
+ last_reconnect_at = self._last_reconnect_at.get(scope_key, 0.0)
685
+ if last_reconnect_at and now - last_reconnect_at < self._reconnect_min_interval:
686
+ return
687
+ rebuilt_old_connections = self._rebuild_aliases(aliases)
688
+
689
+ for old_conn in rebuilt_old_connections:
690
+ await self._close_connection(old_conn)
691
+
692
+ async def close_connections(self) -> None:
693
+ old_cluster = self._cluster
694
+ if self._fallback_connections:
695
+ self._fallback_connections.clear()
696
+ if Tortoise is not None:
697
+ await self._close_tortoise_connections_quietly(reason="manager close")
698
+ self._clear_global_context_fallback()
699
+ self._last_config = None
700
+ self._cluster = None
701
+ self._ctx = None
702
+ self._initialized = False
703
+ self._last_reconnect_at.clear()
704
+ self._loop_id = None
705
+ await self._close_cluster(old_cluster)
706
+
707
+ async def get_connection_status(self) -> dict[str, Any]:
708
+ from .lifecycle_status import connection_lifecycle_status
709
+
710
+ lifecycle = connection_lifecycle_status(self)
711
+ return {
712
+ "default": lifecycle.alias_status("default"),
713
+ "read_only": lifecycle.alias_status("read_only"),
714
+ }
715
+
716
+
717
+ tortoise_manager = _TortoiseManager()
718
+
719
+
720
+ async def get_connection_status() -> dict[str, Any]:
721
+ return await tortoise_manager.get_connection_status()
722
+
723
+
724
+ __all__ = ["ConfigBuilder", "ClusterFactory", "_TortoiseManager", "get_connection_status", "tortoise_manager"]