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,664 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import importlib.util
5
+ import json
6
+ import os
7
+ from pathlib import Path
8
+ import re
9
+ import shutil
10
+ import subprocess
11
+ import sys
12
+ from typing import Any
13
+
14
+ from pz_foundation.db.config import build_foundation_tortoise_config
15
+
16
+
17
+ def _require_aerich() -> None:
18
+ if importlib.util.find_spec("aerich") is None:
19
+ raise RuntimeError("Aerich is not installed. Install the project dependencies that provide the migration executor.")
20
+
21
+
22
+ def _runnable_sources(migration_sources: tuple[Any, ...], *, action: str) -> tuple[Any, ...]:
23
+ runnable: list[Any] = []
24
+ for source in migration_sources:
25
+ if _is_shared_source_package(source):
26
+ continue
27
+ if not str(getattr(source, "models_module", "") or "").strip():
28
+ continue
29
+ files = tuple(str(item) for item in (getattr(source, "files", ()) or ()))
30
+ has_real_migration = any(
31
+ Path(item).suffix == ".py" and Path(item).name != "__init__.py"
32
+ for item in files
33
+ )
34
+ if has_real_migration or action == "makemigrations":
35
+ runnable.append(source)
36
+ return tuple(runnable)
37
+
38
+
39
+ def _shared_init_sources(migration_sources: tuple[Any, ...]) -> tuple[Any, ...]:
40
+ return tuple(
41
+ source
42
+ for source in migration_sources
43
+ if _is_shared_source_package(source)
44
+ )
45
+
46
+
47
+ def _is_shared_source_package(source: Any) -> bool:
48
+ raw_package_root = str(getattr(source, "package_root", "") or "").strip()
49
+ if not raw_package_root:
50
+ return False
51
+ try:
52
+ package_root = Path(raw_package_root).resolve()
53
+ except Exception:
54
+ return False
55
+ return ".sources" in package_root.parts
56
+
57
+
58
+ def _ensure_workspace_packages(*, workspace_dir: Path, migration_sources: tuple[Any, ...]) -> Path:
59
+ owners_dir = workspace_dir / "owners"
60
+ owners_dir.mkdir(parents=True, exist_ok=True)
61
+ (owners_dir / "__init__.py").touch(exist_ok=True)
62
+ for source in migration_sources:
63
+ owner_dir = owners_dir / str(getattr(source, "owner"))
64
+ owner_dir.mkdir(parents=True, exist_ok=True)
65
+ (owner_dir / "__init__.py").touch(exist_ok=True)
66
+ return owners_dir
67
+
68
+
69
+ def _build_tortoise_config(*, plan: Any, migration_sources: tuple[Any, ...]) -> dict[str, Any]:
70
+ installed_apps = [
71
+ str(getattr(source, "module_path", "")).split(":", 1)[0].strip()
72
+ for source in migration_sources
73
+ if str(getattr(source, "module_path", "")).strip()
74
+ ]
75
+ return build_foundation_tortoise_config(
76
+ project_dir=str(plan.project.project_dir),
77
+ installed_apps=installed_apps,
78
+ )
79
+
80
+
81
+ def _ensure_aerich_history_table(*, tortoise_config: dict[str, Any]) -> None:
82
+ if "aerich" not in (tortoise_config.get("apps", {}) or {}):
83
+ return
84
+ try:
85
+ from tortoise import Tortoise
86
+ except Exception:
87
+ return
88
+
89
+ async def _bootstrap() -> None:
90
+ try:
91
+ await Tortoise.init(
92
+ config={
93
+ "connections": tortoise_config.get("connections", {}),
94
+ "apps": {"aerich": tortoise_config["apps"]["aerich"]},
95
+ }
96
+ )
97
+ await Tortoise.generate_schemas(safe=True)
98
+ finally:
99
+ try:
100
+ await Tortoise.close_connections()
101
+ except Exception:
102
+ pass
103
+
104
+ asyncio.run(_bootstrap())
105
+
106
+
107
+ def _migration_python_files(owner_dir: Path) -> tuple[Path, ...]:
108
+ if not owner_dir.is_dir():
109
+ return ()
110
+ return tuple(
111
+ path
112
+ for path in sorted(owner_dir.glob("*.py"), key=_migration_sort_key)
113
+ if path.name != "__init__.py"
114
+ )
115
+
116
+
117
+ def _migration_sort_key(path: Path) -> tuple[int, str]:
118
+ match = re.match(r"^(?P<prefix>\d+)_", path.name)
119
+ if match is None:
120
+ return (sys.maxsize, path.name)
121
+ return (int(match.group("prefix")), path.name)
122
+
123
+
124
+ def _migration_file_has_aerich_state(path: Path) -> bool:
125
+ try:
126
+ text = path.read_text(encoding="utf-8")
127
+ except Exception:
128
+ return False
129
+ return "MODELS_STATE" in text
130
+
131
+
132
+ def _owner_uses_legacy_sql_migrations(owner_dir: Path) -> bool:
133
+ return bool(_legacy_sql_migration_files(owner_dir))
134
+
135
+
136
+ def _legacy_sql_migration_files(owner_dir: Path) -> tuple[Path, ...]:
137
+ files = _migration_python_files(owner_dir)
138
+ if any(not _migration_file_has_aerich_state(path) for path in files):
139
+ return files
140
+ return tuple(path for path in files if not _migration_file_has_aerich_state(path))
141
+
142
+
143
+ def _coerce_query_row(row: Any) -> Any:
144
+ if isinstance(row, dict):
145
+ return row
146
+ keys = getattr(row, "keys", None)
147
+ if not callable(keys):
148
+ return row
149
+ try:
150
+ return {key: row[key] for key in keys()}
151
+ except Exception:
152
+ return row
153
+
154
+
155
+ def _coerce_execute_query_result(result: Any) -> Any:
156
+ if not isinstance(result, tuple) or len(result) < 2:
157
+ return result
158
+ rows = result[1]
159
+ if not isinstance(rows, list):
160
+ return result
161
+ coerced = [_coerce_query_row(row) for row in rows]
162
+ return (result[0], coerced, *result[2:])
163
+
164
+
165
+ class _LegacySqlMigrationConnection:
166
+ def __init__(self, connection: Any) -> None:
167
+ self._connection = connection
168
+
169
+ def __getattr__(self, name: str) -> Any:
170
+ return getattr(self._connection, name)
171
+
172
+ async def execute_query(self, *args: Any, **kwargs: Any) -> Any:
173
+ result = await self._connection.execute_query(*args, **kwargs)
174
+ return _coerce_execute_query_result(result)
175
+
176
+
177
+ def _connection_dialect(connection: Any) -> str:
178
+ try:
179
+ dialect = str(getattr(getattr(connection, "capabilities", None), "dialect", "") or "").lower()
180
+ if dialect:
181
+ return dialect
182
+ except Exception:
183
+ pass
184
+ try:
185
+ module_name = str(type(connection).__module__ or "").lower()
186
+ except Exception:
187
+ module_name = ""
188
+ if "sqlite" in module_name:
189
+ return "sqlite"
190
+ if "asyncpg" in module_name or "postgres" in module_name or "psycopg" in module_name:
191
+ return "postgres"
192
+ if "mysql" in module_name or "aiomysql" in module_name or "mariadb" in module_name:
193
+ return "mysql"
194
+ return ""
195
+
196
+
197
+ def _split_sql_statements(script: str) -> list[str]:
198
+ statements: list[str] = []
199
+ current: list[str] = []
200
+ in_single_quote = False
201
+ in_double_quote = False
202
+ idx = 0
203
+ while idx < len(script):
204
+ char = script[idx]
205
+ current.append(char)
206
+ if char == "'" and not in_double_quote:
207
+ next_char = script[idx + 1] if idx + 1 < len(script) else ""
208
+ if in_single_quote and next_char == "'":
209
+ current.append(next_char)
210
+ idx += 1
211
+ else:
212
+ in_single_quote = not in_single_quote
213
+ elif char == '"' and not in_single_quote:
214
+ in_double_quote = not in_double_quote
215
+ elif char == ";" and not in_single_quote and not in_double_quote:
216
+ statement = "".join(current).strip()
217
+ if statement:
218
+ statements.append(statement)
219
+ current = []
220
+ idx += 1
221
+ tail = "".join(current).strip()
222
+ if tail:
223
+ statements.append(tail)
224
+ return statements
225
+
226
+
227
+ def _strip_sqlite_unsupported_comment_statements(script: str) -> str:
228
+ statements = [
229
+ statement
230
+ for statement in _split_sql_statements(script)
231
+ if not statement.lstrip().upper().startswith("COMMENT ON ")
232
+ ]
233
+ if not statements:
234
+ return ""
235
+ return "\n".join(statements)
236
+
237
+
238
+ def _strip_sqlite_postgres_do_blocks(script: str) -> str:
239
+ return re.sub(
240
+ r"\bDO\s+\$\$.*?\$\$\s*;?",
241
+ "",
242
+ script,
243
+ flags=re.IGNORECASE | re.DOTALL,
244
+ )
245
+
246
+
247
+ def _strip_sqlite_postgres_json_casts(script: str) -> str:
248
+ result: list[str] = []
249
+ in_single_quote = False
250
+ in_double_quote = False
251
+ idx = 0
252
+ while idx < len(script):
253
+ char = script[idx]
254
+ if char == "'" and not in_double_quote:
255
+ result.append(char)
256
+ next_char = script[idx + 1] if idx + 1 < len(script) else ""
257
+ if in_single_quote and next_char == "'":
258
+ result.append(next_char)
259
+ idx += 2
260
+ continue
261
+ in_single_quote = not in_single_quote
262
+ idx += 1
263
+ continue
264
+ if char == '"' and not in_single_quote:
265
+ in_double_quote = not in_double_quote
266
+ result.append(char)
267
+ idx += 1
268
+ continue
269
+ if not in_single_quote and not in_double_quote:
270
+ lower_tail = script[idx:].lower()
271
+ if lower_tail.startswith("::jsonb"):
272
+ idx += len("::jsonb")
273
+ continue
274
+ if lower_tail.startswith("::json"):
275
+ idx += len("::json")
276
+ continue
277
+ result.append(char)
278
+ idx += 1
279
+ return "".join(result)
280
+
281
+
282
+ def _replace_sqlite_postgres_default_functions(script: str) -> str:
283
+ return re.sub(
284
+ r"\bDEFAULT\s+NOW\s*\(\s*\)",
285
+ "DEFAULT CURRENT_TIMESTAMP",
286
+ script,
287
+ flags=re.IGNORECASE,
288
+ )
289
+
290
+
291
+ def _prepare_legacy_sql_script(script: str, *, connection: Any) -> str:
292
+ if _connection_dialect(connection) != "sqlite":
293
+ return script
294
+ script = _strip_sqlite_postgres_do_blocks(script)
295
+ script = _strip_sqlite_unsupported_comment_statements(script)
296
+ script = _strip_sqlite_postgres_json_casts(script)
297
+ return _replace_sqlite_postgres_default_functions(script)
298
+
299
+
300
+ def _query_result_rows(result: Any) -> list[Any]:
301
+ if not isinstance(result, tuple) or len(result) < 2:
302
+ return []
303
+ rows = result[1]
304
+ if not isinstance(rows, list):
305
+ return []
306
+ return rows
307
+
308
+
309
+ def _row_value(row: Any, key: str) -> Any:
310
+ get = getattr(row, "get", None)
311
+ if callable(get):
312
+ return get(key)
313
+ try:
314
+ return row[key]
315
+ except Exception:
316
+ return None
317
+
318
+
319
+ async def _sqlite_column_exists(connection: Any, *, table_name: str, column_name: str) -> bool:
320
+ result = await connection.execute_query(f'PRAGMA table_info("{table_name}")')
321
+ return any(
322
+ str(_row_value(_coerce_query_row(row), "name") or "") == column_name
323
+ for row in _query_result_rows(result)
324
+ )
325
+
326
+
327
+ _SQLITE_ADD_COLUMN_IF_NOT_EXISTS_RE = re.compile(
328
+ r'^\s*ALTER\s+TABLE\s+"?(?P<table>[^"\s]+)"?\s+ADD\s+COLUMN\s+IF\s+NOT\s+EXISTS\s+"?(?P<column>[^"\s]+)"?\s+',
329
+ flags=re.IGNORECASE | re.DOTALL,
330
+ )
331
+ _SQLITE_ALTER_COLUMN_RE = re.compile(
332
+ r'^\s*ALTER\s+TABLE\s+"?[^"\s]+"?\s+ALTER\s+COLUMN\s+',
333
+ flags=re.IGNORECASE | re.DOTALL,
334
+ )
335
+ _SQLITE_ALTER_CONSTRAINT_RE = re.compile(
336
+ r'^\s*ALTER\s+TABLE\s+"?[^"\s]+"?\s+(?:DROP|ADD)\s+CONSTRAINT\s+',
337
+ flags=re.IGNORECASE | re.DOTALL,
338
+ )
339
+ _SQLITE_UPDATE_TABLE_RE = re.compile(
340
+ r'^\s*UPDATE\s+"?(?P<table>[^"\s]+)"?\s+',
341
+ flags=re.IGNORECASE | re.DOTALL,
342
+ )
343
+
344
+
345
+ async def _sqlite_table_has_rows(connection: Any, *, table_name: str) -> bool:
346
+ result = await connection.execute_query(f'SELECT 1 FROM "{table_name}" LIMIT 1')
347
+ return bool(_query_result_rows(result))
348
+
349
+
350
+ async def _execute_sqlite_legacy_statement(connection: Any, statement: str) -> None:
351
+ normalized = statement.strip().rstrip(";").strip()
352
+ if not normalized:
353
+ return
354
+ add_column_match = _SQLITE_ADD_COLUMN_IF_NOT_EXISTS_RE.match(normalized)
355
+ if add_column_match is not None:
356
+ table_name = add_column_match.group("table")
357
+ column_name = add_column_match.group("column")
358
+ if await _sqlite_column_exists(connection, table_name=table_name, column_name=column_name):
359
+ return
360
+ normalized = re.sub(
361
+ r"\bADD\s+COLUMN\s+IF\s+NOT\s+EXISTS\b",
362
+ "ADD COLUMN",
363
+ normalized,
364
+ count=1,
365
+ flags=re.IGNORECASE,
366
+ )
367
+ if _SQLITE_ALTER_COLUMN_RE.match(normalized) or _SQLITE_ALTER_CONSTRAINT_RE.match(normalized):
368
+ return
369
+ update_match = _SQLITE_UPDATE_TABLE_RE.match(normalized)
370
+ if update_match is not None and not await _sqlite_table_has_rows(connection, table_name=update_match.group("table")):
371
+ return
372
+ await connection.execute_script(normalized)
373
+
374
+
375
+ async def _execute_legacy_sql_script(connection: Any, script: str) -> None:
376
+ if _connection_dialect(connection) != "sqlite":
377
+ await connection.execute_script(script)
378
+ return
379
+ for statement in _split_sql_statements(script):
380
+ await _execute_sqlite_legacy_statement(connection, statement)
381
+
382
+
383
+ def _apply_legacy_sql_migrations(
384
+ *,
385
+ owner: str,
386
+ owner_dir: Path,
387
+ tortoise_config: dict[str, Any],
388
+ ) -> dict[str, Any]:
389
+ files = _legacy_sql_migration_files(owner_dir)
390
+ if not files:
391
+ return {"owner": owner, "command": "legacy-upgrade", "files": [], "returncode": 0}
392
+ try:
393
+ from tortoise import Tortoise
394
+ except Exception as exc:
395
+ raise RuntimeError(f"Tortoise is required to apply legacy SQL migrations for app '{owner}': {exc}") from exc
396
+
397
+ async def _upgrade() -> None:
398
+ try:
399
+ await Tortoise.init(config=tortoise_config)
400
+ connection = Tortoise.get_connection("default")
401
+ migration_connection = _LegacySqlMigrationConnection(connection)
402
+ for path in files:
403
+ spec = importlib.util.spec_from_file_location(f"_pz_legacy_migration_{owner}_{path.stem}", path)
404
+ if spec is None or spec.loader is None:
405
+ raise RuntimeError(f"Cannot load legacy migration file: {path}")
406
+ module = importlib.util.module_from_spec(spec)
407
+ spec.loader.exec_module(module)
408
+ upgrade = getattr(module, "upgrade", None)
409
+ if upgrade is None:
410
+ continue
411
+ sql = await upgrade(migration_connection)
412
+ sql_text = str(sql or "").strip()
413
+ sql_text = _prepare_legacy_sql_script(sql_text, connection=connection).strip()
414
+ if sql_text:
415
+ try:
416
+ await _execute_legacy_sql_script(connection, sql_text)
417
+ except Exception as exc:
418
+ raise RuntimeError(
419
+ f"Legacy SQL migration failed for app '{owner}' file '{path.name}': {exc}"
420
+ ) from exc
421
+ finally:
422
+ try:
423
+ await Tortoise.close_connections()
424
+ except Exception:
425
+ pass
426
+
427
+ asyncio.run(_upgrade())
428
+ return {
429
+ "owner": owner,
430
+ "command": "legacy-upgrade",
431
+ "files": [path.name for path in files],
432
+ "returncode": 0,
433
+ }
434
+
435
+
436
+ def _init_shared_source_schemas(
437
+ *,
438
+ plan: Any,
439
+ migration_sources: tuple[Any, ...],
440
+ owners_dir: Path | None = None,
441
+ ) -> tuple[str, ...]:
442
+ sources = _shared_init_sources(migration_sources)
443
+ if not sources:
444
+ return ()
445
+ installed_apps = [
446
+ str(getattr(source, "module_path", "")).split(":", 1)[0].strip()
447
+ for source in sources
448
+ if str(getattr(source, "module_path", "")).strip()
449
+ ]
450
+ if not installed_apps:
451
+ return ()
452
+ tortoise_config = build_foundation_tortoise_config(
453
+ project_dir=str(plan.project.project_dir),
454
+ installed_apps=installed_apps,
455
+ )
456
+ try:
457
+ from tortoise import Tortoise
458
+ except Exception as exc:
459
+ raise RuntimeError(f"Tortoise is required to initialize shared source database schemas: {exc}") from exc
460
+
461
+ async def _bootstrap() -> None:
462
+ try:
463
+ await Tortoise.init(config=tortoise_config)
464
+ await Tortoise.generate_schemas(safe=True)
465
+ finally:
466
+ try:
467
+ await Tortoise.close_connections()
468
+ except Exception:
469
+ pass
470
+
471
+ asyncio.run(_bootstrap())
472
+ if owners_dir is not None:
473
+ for source in sources:
474
+ owner = str(getattr(source, "owner"))
475
+ owner_dir = owners_dir / owner
476
+ if _owner_uses_legacy_sql_migrations(owner_dir):
477
+ _apply_legacy_sql_migrations(
478
+ owner=owner,
479
+ owner_dir=owner_dir,
480
+ tortoise_config=tortoise_config,
481
+ )
482
+ return tuple(str(getattr(source, "owner")) for source in sources)
483
+
484
+
485
+ def _write_workspace_config(*, workspace_dir: Path, tortoise_config: dict[str, Any]) -> tuple[Path, Path]:
486
+ module_path = workspace_dir / "_pz_migration_config.py"
487
+ module_path.write_text(
488
+ "\n".join(
489
+ [
490
+ "from __future__ import annotations",
491
+ "",
492
+ f"TORTOISE_ORM = {json.dumps(tortoise_config, ensure_ascii=True, indent=2, sort_keys=True)}",
493
+ "TORTOISE_CONFIG = TORTOISE_ORM",
494
+ "",
495
+ ]
496
+ ),
497
+ encoding="utf-8",
498
+ )
499
+ config_path = workspace_dir / "aerich.ini"
500
+ config_path.write_text(
501
+ "\n".join(
502
+ [
503
+ "[tool.aerich]",
504
+ 'tortoise_orm = "_pz_migration_config.TORTOISE_CONFIG"',
505
+ 'location = "./owners"',
506
+ f'src_folder = "{workspace_dir}"',
507
+ "",
508
+ ]
509
+ ),
510
+ encoding="utf-8",
511
+ )
512
+ return module_path, config_path
513
+
514
+
515
+ def _aerich_command() -> list[str]:
516
+ if importlib.util.find_spec("aerich.__main__") is not None:
517
+ return [sys.executable, "-m", "aerich"]
518
+ candidate = Path(sys.executable).with_name("aerich")
519
+ if candidate.exists():
520
+ return [str(candidate)]
521
+ found = shutil.which("aerich")
522
+ if found:
523
+ return [found]
524
+ return [sys.executable, "-m", "aerich"]
525
+
526
+
527
+ def _runtime_import_roots(*, plan: Any) -> tuple[Path, ...]:
528
+ settings = getattr(plan, "settings", None)
529
+ values = tuple(getattr(settings, "import_roots", ()) or ())
530
+ roots: list[Path] = []
531
+ seen: set[Path] = set()
532
+ for item in values:
533
+ try:
534
+ root = Path(item).resolve()
535
+ except Exception:
536
+ continue
537
+ if root in seen:
538
+ continue
539
+ seen.add(root)
540
+ roots.append(root)
541
+ return tuple(roots)
542
+
543
+
544
+ def _run_aerich(
545
+ *,
546
+ workspace_dir: Path,
547
+ owners_dir: Path,
548
+ config_path: Path,
549
+ owner: str,
550
+ command: str,
551
+ tortoise_config: dict[str, Any],
552
+ plan: Any,
553
+ ) -> dict[str, Any]:
554
+ env = dict(os.environ)
555
+ pythonpath = env.get("PYTHONPATH", "")
556
+ import_entries = [str(workspace_dir), str(Path(getattr(getattr(plan, "project", None), "project_dir", workspace_dir)).resolve())]
557
+ import_entries.extend(str(path) for path in _runtime_import_roots(plan=plan))
558
+ if pythonpath:
559
+ import_entries.extend(item for item in pythonpath.split(os.pathsep) if item)
560
+ deduped_entries: list[str] = []
561
+ seen_entries: set[str] = set()
562
+ for entry in import_entries:
563
+ value = str(entry or "").strip()
564
+ if not value or value in seen_entries:
565
+ continue
566
+ seen_entries.add(value)
567
+ deduped_entries.append(value)
568
+ env["PYTHONPATH"] = os.pathsep.join(deduped_entries)
569
+ base_cmd = _aerich_command() + ["-c", str(config_path), "--app", owner]
570
+ owner_dir = owners_dir / owner
571
+ if command == "upgrade" and _owner_uses_legacy_sql_migrations(owner_dir):
572
+ return _apply_legacy_sql_migrations(owner=owner, owner_dir=owner_dir, tortoise_config=tortoise_config)
573
+
574
+ def _invoke(*args: str) -> subprocess.CompletedProcess[str]:
575
+ return subprocess.run(
576
+ base_cmd + list(args),
577
+ cwd=str(workspace_dir),
578
+ env=env,
579
+ capture_output=True,
580
+ text=True,
581
+ check=False,
582
+ )
583
+
584
+ proc = _invoke(command)
585
+ stderr_text = proc.stderr or ""
586
+ stdout_text = proc.stdout or ""
587
+ if proc.returncode != 0 and "Old format of migration file detected" in (stderr_text or stdout_text):
588
+ fix_proc = _invoke("fix-migrations")
589
+ if fix_proc.returncode == 0:
590
+ proc = _invoke(command)
591
+ stderr_text = proc.stderr or ""
592
+ stdout_text = proc.stdout or ""
593
+ if proc.returncode != 0:
594
+ raise RuntimeError(
595
+ f"Aerich {command} failed for app '{owner}' with exit code {proc.returncode}: "
596
+ f"{(stderr_text or stdout_text).strip()}"
597
+ )
598
+ return {
599
+ "owner": owner,
600
+ "command": command,
601
+ "stdout": stdout_text,
602
+ "stderr": stderr_text,
603
+ "returncode": proc.returncode,
604
+ }
605
+
606
+
607
+ def execute(
608
+ *,
609
+ action: str,
610
+ plan: Any,
611
+ workspace_dir: Path,
612
+ manifest_path: Path,
613
+ orm_config_path: Path,
614
+ lock_path: Path,
615
+ migration_sources: tuple[Any, ...],
616
+ ) -> dict[str, Any]:
617
+ del manifest_path, orm_config_path, lock_path
618
+ _require_aerich()
619
+ runnable = _runnable_sources(migration_sources, action=action)
620
+ owners_dir = _ensure_workspace_packages(workspace_dir=workspace_dir, migration_sources=migration_sources)
621
+ tortoise_config = _build_tortoise_config(plan=plan, migration_sources=migration_sources)
622
+ _ensure_aerich_history_table(tortoise_config=tortoise_config)
623
+ initialized_shared_sources = ()
624
+ if action in {"migrate", "upgrade"}:
625
+ initialized_shared_sources = _init_shared_source_schemas(
626
+ plan=plan,
627
+ migration_sources=migration_sources,
628
+ owners_dir=owners_dir,
629
+ )
630
+ _, config_path = _write_workspace_config(
631
+ workspace_dir=workspace_dir,
632
+ tortoise_config=tortoise_config,
633
+ )
634
+ command_by_action = {
635
+ "makemigrations": "migrate",
636
+ "migrate": "upgrade",
637
+ "upgrade": "upgrade",
638
+ }
639
+ command = command_by_action.get(action)
640
+ if command is None:
641
+ raise RuntimeError(f"Unsupported Aerich executor action: {action}")
642
+ command_results = [
643
+ _run_aerich(
644
+ workspace_dir=workspace_dir,
645
+ owners_dir=owners_dir,
646
+ config_path=config_path,
647
+ owner=str(getattr(source, "owner")),
648
+ command=command,
649
+ tortoise_config=tortoise_config,
650
+ plan=plan,
651
+ )
652
+ for source in runnable
653
+ ]
654
+ return {
655
+ "action": action,
656
+ "owners": [str(getattr(source, "owner")) for source in runnable],
657
+ "initialized_shared_source_owners": list(initialized_shared_sources),
658
+ "results": command_results,
659
+ }
660
+
661
+
662
+ __all__ = [
663
+ "execute",
664
+ ]