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,1061 @@
1
+ from __future__ import annotations
2
+
3
+ from contextlib import contextmanager
4
+ from dataclasses import dataclass
5
+ import hashlib
6
+ import json
7
+ import io
8
+ import os
9
+ import re
10
+ from pathlib import Path, PurePosixPath
11
+ import shutil
12
+ import stat
13
+ import subprocess
14
+ import tarfile
15
+ import tempfile
16
+ import tomllib
17
+ import uuid
18
+ from urllib.parse import urlsplit, urlunsplit
19
+
20
+ try:
21
+ import fcntl as _fcntl
22
+ except ImportError: # pragma: no cover - Windows
23
+ _fcntl = None
24
+
25
+ try:
26
+ import msvcrt as _msvcrt
27
+ except ImportError: # pragma: no cover - POSIX
28
+ _msvcrt = None
29
+
30
+ import tomlkit # type: ignore[import-not-found]
31
+
32
+ from pz_core.runtime.errors import PzError
33
+
34
+ from .official_dev_sources import resolve_source_locator
35
+ from .project_state import ensure_project_state, load_project_payload, save_project_payload
36
+ from .workspace import iter_workspace_packages_in_roots
37
+
38
+
39
+ _OWNER = "pz_core.authoring.dev_import"
40
+ _REPO_KEY_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_-]*$")
41
+ _FULL_COMMIT_RE = re.compile(r"^[0-9a-f]{40}$")
42
+ _CACHE_OWNER_FILE = ".pz-cache-owner.json"
43
+
44
+
45
+ @dataclass(frozen=True, slots=True)
46
+ class CachedGitSource:
47
+ repo_key: str
48
+ source: str
49
+ branch: str
50
+ checkout: Path
51
+ commit: str
52
+ resolution_ref: str
53
+
54
+
55
+ @dataclass(frozen=True, slots=True)
56
+ class ImportedApp:
57
+ app_id: str
58
+ destination: Path
59
+ subpath: PurePosixPath
60
+ commit: str
61
+ created: bool
62
+
63
+
64
+ def _error(code: str, message: str, *, action: str, detail: str | None = None) -> PzError:
65
+ return PzError(
66
+ code=code,
67
+ message=message,
68
+ stage="dev",
69
+ owner=_OWNER,
70
+ action=action,
71
+ detail=detail,
72
+ )
73
+
74
+
75
+ def _run_git(
76
+ *args: str,
77
+ cwd: Path | None,
78
+ action: str,
79
+ code: str = "PZ_DEV_CACHE_REFRESH_FAILED",
80
+ ) -> str:
81
+ completed = subprocess.run(
82
+ ("git", *args),
83
+ cwd=cwd,
84
+ check=False,
85
+ capture_output=True,
86
+ text=True,
87
+ stdin=subprocess.DEVNULL,
88
+ env={**os.environ, "GIT_TERMINAL_PROMPT": "0"},
89
+ )
90
+ if completed.returncode != 0:
91
+ detail = (completed.stderr or completed.stdout or "git command failed").strip()
92
+ detail = re.sub(
93
+ r"([A-Za-z][A-Za-z0-9+.-]*://)[^/@\s]+@",
94
+ r"\1<redacted>@",
95
+ detail,
96
+ )
97
+ raise _error(
98
+ code,
99
+ "无法准备开发源码 Git cache。",
100
+ action=action,
101
+ detail=detail[:500],
102
+ )
103
+ return completed.stdout.strip()
104
+
105
+
106
+ def _canonical_remote(value: str) -> str:
107
+ raw = value.strip().rstrip("/")
108
+ if "://" not in raw and ":" in raw and not raw.startswith(("/", ".")):
109
+ user_host, path = raw.split(":", 1)
110
+ host = user_host.rsplit("@", 1)[-1].lower()
111
+ return f"ssh://{host}/{path.removesuffix('.git')}"
112
+ parsed = urlsplit(raw)
113
+ if parsed.scheme:
114
+ host = (parsed.hostname or "").lower()
115
+ if parsed.port is not None:
116
+ host = f"{host}:{parsed.port}"
117
+ return urlunsplit(
118
+ (parsed.scheme.lower(), host, parsed.path.rstrip("/").removesuffix(".git"), "", "")
119
+ )
120
+ return str(Path(raw).expanduser().resolve()).removesuffix(".git")
121
+
122
+
123
+ def _cache_paths(repo_key: str) -> tuple[Path, Path]:
124
+ if not _REPO_KEY_RE.fullmatch(repo_key):
125
+ raise _error(
126
+ "PZ_DEV_CACHE_INVALID",
127
+ "Git cache repo_key 只能包含字母、数字、下划线和连字符。",
128
+ action="dev_source_cache",
129
+ detail=repo_key,
130
+ )
131
+ user_key = f"uid-{os.getuid()}" if hasattr(os, "getuid") else "current-user"
132
+ root = Path(tempfile.gettempdir()) / "pz" / "users" / user_key / "git"
133
+ return root / repo_key, root / f".{repo_key}.lock"
134
+
135
+
136
+ def _ensure_cache_root(root: Path, *, action: str) -> None:
137
+ root.mkdir(parents=True, mode=0o700, exist_ok=True)
138
+ if root.is_symlink() or not root.is_dir():
139
+ raise _error(
140
+ "PZ_DEV_CACHE_INVALID",
141
+ "当前用户的 Git cache 根目录不可信。",
142
+ action=action,
143
+ detail=str(root),
144
+ )
145
+ metadata = root.stat()
146
+ if hasattr(os, "getuid") and metadata.st_uid != os.getuid():
147
+ raise _error(
148
+ "PZ_DEV_CACHE_INVALID",
149
+ "当前用户不是 Git cache 根目录的所有者。",
150
+ action=action,
151
+ detail=str(root),
152
+ )
153
+ if stat.S_IMODE(metadata.st_mode) & 0o077:
154
+ root.chmod(0o700)
155
+
156
+
157
+ @contextmanager
158
+ def _repo_lock(path: Path, *, action: str):
159
+ _ensure_cache_root(path.parent, action=action)
160
+ if path.is_symlink():
161
+ raise _error(
162
+ "PZ_DEV_CACHE_INVALID",
163
+ "Git cache lock 不能是符号链接。",
164
+ action=action,
165
+ detail=str(path),
166
+ )
167
+ flags = os.O_CREAT | os.O_RDWR
168
+ if hasattr(os, "O_NOFOLLOW"):
169
+ flags |= os.O_NOFOLLOW
170
+ try:
171
+ descriptor = os.open(path, flags, 0o600)
172
+ except OSError as exc:
173
+ raise _error(
174
+ "PZ_DEV_CACHE_INVALID",
175
+ "无法安全打开 Git cache lock。",
176
+ action=action,
177
+ detail=str(path),
178
+ ) from exc
179
+ with os.fdopen(descriptor, "a+b") as lock:
180
+ metadata = os.fstat(lock.fileno())
181
+ if hasattr(os, "getuid") and metadata.st_uid != os.getuid():
182
+ raise _error(
183
+ "PZ_DEV_CACHE_INVALID",
184
+ "当前用户不是 Git cache lock 的所有者。",
185
+ action=action,
186
+ detail=str(path),
187
+ )
188
+ os.fchmod(lock.fileno(), 0o600)
189
+ if _fcntl is not None:
190
+ _fcntl.flock(lock.fileno(), _fcntl.LOCK_EX)
191
+ elif _msvcrt is not None: # pragma: no cover - Windows
192
+ lock.seek(0)
193
+ if lock.read(1) == b"":
194
+ lock.write(b"\0")
195
+ lock.flush()
196
+ lock.seek(0)
197
+ _msvcrt.locking(lock.fileno(), _msvcrt.LK_LOCK, 1)
198
+ else: # pragma: no cover - unsupported runtime
199
+ raise _error(
200
+ "PZ_DEV_CACHE_INVALID",
201
+ "当前平台不支持可靠的 Git cache 文件锁。",
202
+ action="dev_source_cache",
203
+ )
204
+ try:
205
+ yield
206
+ finally:
207
+ if _fcntl is not None:
208
+ _fcntl.flock(lock.fileno(), _fcntl.LOCK_UN)
209
+ elif _msvcrt is not None: # pragma: no cover - Windows
210
+ lock.seek(0)
211
+ _msvcrt.locking(lock.fileno(), _msvcrt.LK_UNLCK, 1)
212
+
213
+
214
+ def _resolution_ref(commit: str) -> str:
215
+ return f"refs/pz/resolutions/{commit}"
216
+
217
+
218
+ def _owner_payload(*, repo_key: str, source: str) -> dict[str, object]:
219
+ return {
220
+ "schema_version": 1,
221
+ "uid": os.getuid() if hasattr(os, "getuid") else None,
222
+ "repo_key": repo_key,
223
+ "source": _canonical_remote(source),
224
+ }
225
+
226
+
227
+ def _write_cache_owner(checkout: Path, *, repo_key: str, source: str) -> None:
228
+ marker = checkout / _CACHE_OWNER_FILE
229
+ marker.write_text(
230
+ json.dumps(_owner_payload(repo_key=repo_key, source=source), sort_keys=True) + "\n",
231
+ encoding="utf-8",
232
+ )
233
+ marker.chmod(0o600)
234
+
235
+
236
+ def _validate_cache(
237
+ checkout: Path,
238
+ *,
239
+ repo_key: str,
240
+ source: str,
241
+ action: str,
242
+ ) -> None:
243
+ if checkout.is_symlink() or not checkout.is_dir() or not (checkout / ".git").is_dir():
244
+ raise _error(
245
+ "PZ_DEV_CACHE_INVALID",
246
+ "系统临时目录中的源码 cache 不是可信 Git checkout。",
247
+ action=action,
248
+ detail=str(checkout),
249
+ )
250
+ metadata = checkout.stat()
251
+ if hasattr(os, "getuid") and metadata.st_uid != os.getuid():
252
+ raise _error(
253
+ "PZ_DEV_CACHE_INVALID",
254
+ "当前用户不是 Git cache checkout 的所有者。",
255
+ action=action,
256
+ detail=str(checkout),
257
+ )
258
+ marker = checkout / _CACHE_OWNER_FILE
259
+ if marker.is_symlink() or not marker.is_file():
260
+ raise _error(
261
+ "PZ_DEV_CACHE_INVALID",
262
+ "Git cache 缺少可信 owner marker。",
263
+ action=action,
264
+ detail=str(checkout),
265
+ )
266
+ marker_metadata = marker.stat()
267
+ if (
268
+ hasattr(os, "getuid")
269
+ and marker_metadata.st_uid != os.getuid()
270
+ or stat.S_IMODE(marker_metadata.st_mode) & 0o077
271
+ ):
272
+ raise _error(
273
+ "PZ_DEV_CACHE_INVALID",
274
+ "Git cache owner marker 的所有者或权限无效。",
275
+ action=action,
276
+ detail=str(marker),
277
+ )
278
+ try:
279
+ owner = json.loads(marker.read_text(encoding="utf-8"))
280
+ except (OSError, json.JSONDecodeError) as exc:
281
+ raise _error(
282
+ "PZ_DEV_CACHE_INVALID",
283
+ "Git cache owner marker 无效。",
284
+ action=action,
285
+ detail=str(marker),
286
+ ) from exc
287
+ if owner != _owner_payload(repo_key=repo_key, source=source):
288
+ raise _error(
289
+ "PZ_DEV_CACHE_INVALID",
290
+ "Git cache owner marker 与请求身份不一致。",
291
+ action=action,
292
+ detail=str(checkout),
293
+ )
294
+ remote = _run_git("remote", "get-url", "origin", cwd=checkout, action=action)
295
+ if _canonical_remote(remote) != _canonical_remote(source):
296
+ raise _error(
297
+ "PZ_DEV_CACHE_REMOTE_MISMATCH",
298
+ "系统临时目录中的源码 cache remote 不匹配。",
299
+ action=action,
300
+ detail=f"expected={_canonical_remote(source)}; actual={_canonical_remote(remote)}",
301
+ )
302
+
303
+
304
+ def cache_git_source(
305
+ *,
306
+ repo_key: str,
307
+ source: str,
308
+ branch: str = "develop",
309
+ revision: str | None = None,
310
+ action: str = "dev_source_cache",
311
+ ) -> CachedGitSource:
312
+ checkout, lock_path = _cache_paths(repo_key)
313
+ cache_root = checkout.parent
314
+ _ensure_cache_root(cache_root, action=action)
315
+ if revision is not None and not _FULL_COMMIT_RE.fullmatch(revision):
316
+ raise _error(
317
+ "PZ_DEV_CACHE_RESOLUTION_MISSING",
318
+ "Git revision 必须是完整的 40 位 commit hash。",
319
+ action=action,
320
+ detail=revision,
321
+ )
322
+ with _repo_lock(lock_path, action=action):
323
+ created = False
324
+ if checkout.exists() or checkout.is_symlink():
325
+ _validate_cache(
326
+ checkout,
327
+ repo_key=repo_key,
328
+ source=source,
329
+ action=action,
330
+ )
331
+ if not checkout.exists() and not checkout.is_symlink():
332
+ if revision is not None:
333
+ raise _error(
334
+ "PZ_DEV_CACHE_RESOLUTION_MISSING",
335
+ "系统临时 Git cache 已丢失,不能凭任意对象重建既有 revision handoff。",
336
+ action=action,
337
+ detail=revision,
338
+ )
339
+ staged = cache_root / f".{repo_key}.{uuid.uuid4().hex}.tmp"
340
+ try:
341
+ _run_git(
342
+ "clone",
343
+ "--branch",
344
+ branch,
345
+ "--single-branch",
346
+ source,
347
+ str(staged),
348
+ cwd=None,
349
+ action=action,
350
+ )
351
+ _write_cache_owner(staged, repo_key=repo_key, source=source)
352
+ staged.chmod(0o700)
353
+ os.replace(staged, checkout)
354
+ created = True
355
+ finally:
356
+ shutil.rmtree(staged, ignore_errors=True)
357
+ else:
358
+ _run_git("fetch", "origin", branch, cwd=checkout, action=action)
359
+ if revision is not None:
360
+ reference = _resolution_ref(revision)
361
+ try:
362
+ commit = _run_git(
363
+ "rev-parse", f"{reference}^{{commit}}", cwd=checkout, action=action
364
+ )
365
+ except PzError as exc:
366
+ raise _error(
367
+ "PZ_DEV_CACHE_RESOLUTION_MISSING",
368
+ "Git cache 中不存在先前固定的 revision handoff。",
369
+ action=action,
370
+ detail=revision,
371
+ ) from exc
372
+ if commit != revision:
373
+ raise _error(
374
+ "PZ_DEV_CACHE_RESOLUTION_MISSING",
375
+ "Git cache resolution ref 与请求 revision 不一致。",
376
+ action=action,
377
+ detail=revision,
378
+ )
379
+ else:
380
+ selected = "HEAD" if created else "FETCH_HEAD"
381
+ commit = _run_git(
382
+ "rev-parse", f"{selected}^{{commit}}", cwd=checkout, action=action
383
+ )
384
+ reference = _resolution_ref(commit)
385
+ _run_git("update-ref", reference, commit, cwd=checkout, action=action)
386
+ return CachedGitSource(
387
+ repo_key,
388
+ _canonical_remote(source),
389
+ branch,
390
+ checkout,
391
+ commit,
392
+ reference,
393
+ )
394
+
395
+
396
+ def load_cached_git_source(
397
+ *,
398
+ repo_key: str,
399
+ source: str,
400
+ revision: str,
401
+ branch: str = "develop",
402
+ action: str = "dev_source_import",
403
+ ) -> CachedGitSource:
404
+ if not _FULL_COMMIT_RE.fullmatch(revision):
405
+ raise _error(
406
+ "PZ_DEV_CACHE_RESOLUTION_MISSING",
407
+ "Git revision 必须是完整的 40 位 commit hash。",
408
+ action=action,
409
+ detail=revision,
410
+ )
411
+ checkout, lock_path = _cache_paths(repo_key)
412
+ if not lock_path.parent.is_dir():
413
+ raise _error(
414
+ "PZ_DEV_CACHE_RESOLUTION_MISSING",
415
+ "系统临时 Git cache 不存在。",
416
+ action=action,
417
+ )
418
+ with _repo_lock(lock_path, action=action):
419
+ _validate_cache(
420
+ checkout,
421
+ repo_key=repo_key,
422
+ source=source,
423
+ action=action,
424
+ )
425
+ reference = _resolution_ref(revision)
426
+ try:
427
+ commit = _run_git(
428
+ "rev-parse", f"{reference}^{{commit}}", cwd=checkout, action=action
429
+ )
430
+ except PzError as exc:
431
+ raise _error(
432
+ "PZ_DEV_CACHE_RESOLUTION_MISSING",
433
+ "Git cache 中不存在请求的不可变 revision。",
434
+ action=action,
435
+ detail=revision,
436
+ ) from exc
437
+ if commit != revision:
438
+ raise _error(
439
+ "PZ_DEV_CACHE_RESOLUTION_MISSING",
440
+ "Git cache resolution ref 与请求 revision 不一致。",
441
+ action=action,
442
+ detail=f"expected={revision}; actual={commit}",
443
+ )
444
+ return CachedGitSource(
445
+ repo_key,
446
+ _canonical_remote(source),
447
+ branch,
448
+ checkout,
449
+ commit,
450
+ reference,
451
+ )
452
+
453
+
454
+ def _archive_bytes(cache: CachedGitSource, subpath: PurePosixPath, *, action: str) -> bytes:
455
+ completed = subprocess.run(
456
+ (
457
+ "git",
458
+ "-C",
459
+ str(cache.checkout),
460
+ "archive",
461
+ "--format=tar",
462
+ cache.commit,
463
+ subpath.as_posix(),
464
+ ),
465
+ check=False,
466
+ capture_output=True,
467
+ stdin=subprocess.DEVNULL,
468
+ )
469
+ if completed.returncode != 0:
470
+ raise _error(
471
+ "PZ_DEV_SOURCE_IMPORT_FAILED",
472
+ "无法从固定 Git revision 导出子包源码。",
473
+ action=action,
474
+ detail=completed.stderr.decode("utf-8", errors="replace")[:500],
475
+ )
476
+ return completed.stdout
477
+
478
+
479
+ def _extract_archive(payload: bytes, destination: Path, subpath: PurePosixPath, *, action: str) -> None:
480
+ prefix_parts = subpath.parts
481
+ with tarfile.open(fileobj=io.BytesIO(payload), mode="r:") as archive:
482
+ for member in archive.getmembers():
483
+ parts = PurePosixPath(member.name).parts
484
+ if parts[: len(prefix_parts)] != prefix_parts:
485
+ raise _error(
486
+ "PZ_DEV_SOURCE_IMPORT_FAILED",
487
+ "Git archive 包含越界路径。",
488
+ action=action,
489
+ detail=member.name,
490
+ )
491
+ relative = PurePosixPath(*parts[len(prefix_parts) :])
492
+ if not relative.parts:
493
+ continue
494
+ if relative.is_absolute() or ".." in relative.parts:
495
+ raise _error(
496
+ "PZ_DEV_SOURCE_IMPORT_FAILED",
497
+ "Git archive 包含不安全路径。",
498
+ action=action,
499
+ detail=member.name,
500
+ )
501
+ target = destination / Path(*relative.parts)
502
+ if member.isdir():
503
+ target.mkdir(parents=True, exist_ok=True)
504
+ continue
505
+ target.parent.mkdir(parents=True, exist_ok=True)
506
+ if member.isfile():
507
+ source = archive.extractfile(member)
508
+ if source is None:
509
+ raise _error("PZ_DEV_SOURCE_IMPORT_FAILED", "无法读取 Git archive 文件。", action=action)
510
+ target.write_bytes(source.read())
511
+ target.chmod(stat.S_IMODE(member.mode))
512
+ continue
513
+ if member.issym():
514
+ link = PurePosixPath(member.linkname)
515
+ if link.is_absolute() or ".." in link.parts:
516
+ raise _error(
517
+ "PZ_DEV_SOURCE_IMPORT_FAILED",
518
+ "Git archive 包含不安全符号链接。",
519
+ action=action,
520
+ detail=member.name,
521
+ )
522
+ target.symlink_to(member.linkname)
523
+ continue
524
+ raise _error(
525
+ "PZ_DEV_SOURCE_IMPORT_FAILED",
526
+ "Git archive 包含不支持的文件类型。",
527
+ action=action,
528
+ detail=member.name,
529
+ )
530
+
531
+
532
+ def _validate_app(path: Path, app_id: str, *, action: str) -> None:
533
+ pyproject = path / "pyproject.toml"
534
+ try:
535
+ payload = tomllib.loads(pyproject.read_text(encoding="utf-8"))
536
+ distribution = payload["project"]["name"]
537
+ except Exception as exc:
538
+ raise _error(
539
+ "PZ_DEV_SOURCE_LAYOUT_INVALID",
540
+ "导入目录不是正式最小子包。",
541
+ action=action,
542
+ detail=f"{path}: {exc}",
543
+ ) from exc
544
+ if not isinstance(distribution, str) or distribution.replace("-", "_") != app_id:
545
+ raise _error(
546
+ "PZ_DEV_SOURCE_LAYOUT_INVALID",
547
+ "导入子包的 distribution 与 app_id 不一致。",
548
+ action=action,
549
+ detail=str(path),
550
+ )
551
+
552
+
553
+ def _write_provenance(path: Path, cache: CachedGitSource, subpath: PurePosixPath) -> None:
554
+ pyproject = path / "pyproject.toml"
555
+ document = tomlkit.parse(pyproject.read_text(encoding="utf-8"))
556
+ tool = document.setdefault("tool", tomlkit.table())
557
+ pz = tool.setdefault("pz", tomlkit.table())
558
+ provenance = tomlkit.table()
559
+ provenance["kind"] = "git"
560
+ provenance["source"] = _canonical_remote(cache.source)
561
+ provenance["revision"] = cache.commit
562
+ provenance["subpath"] = subpath.as_posix()
563
+ pz["imported_from"] = provenance
564
+ pyproject.write_text(tomlkit.dumps(document), encoding="utf-8")
565
+
566
+
567
+ def _write_local_provenance(
568
+ path: Path,
569
+ *,
570
+ source: str,
571
+ subpath: PurePosixPath,
572
+ ) -> None:
573
+ pyproject = path / "pyproject.toml"
574
+ document = tomlkit.parse(pyproject.read_text(encoding="utf-8"))
575
+ tool = document.setdefault("tool", tomlkit.table())
576
+ pz = tool.setdefault("pz", tomlkit.table())
577
+ provenance = tomlkit.table()
578
+ provenance["kind"] = "path"
579
+ provenance["source"] = "local"
580
+ provenance["identity"] = hashlib.sha256(source.encode("utf-8")).hexdigest()
581
+ provenance["subpath"] = subpath.as_posix()
582
+ pz["imported_from"] = provenance
583
+ pyproject.write_text(tomlkit.dumps(document), encoding="utf-8")
584
+
585
+
586
+ def _declare_local_source_mode(project_dir: Path, app_id: str, *, action: str) -> None:
587
+ ensure_project_state(project_dir, action=action)
588
+ payload = load_project_payload(project_dir, action=action)
589
+ apps = payload.setdefault("apps", {})
590
+ if not isinstance(apps, dict):
591
+ raise _error(
592
+ "PZ_SCAFFOLD_PROJECT_CONFIG_INVALID",
593
+ "项目 apps 配置必须是对象。",
594
+ action=action,
595
+ )
596
+ app = apps.setdefault(app_id, {})
597
+ if not isinstance(app, dict):
598
+ raise _error(
599
+ "PZ_SCAFFOLD_PROJECT_CONFIG_INVALID",
600
+ f"项目 app 配置必须是对象:{app_id}",
601
+ action=action,
602
+ )
603
+ sources = app.setdefault("sources", {})
604
+ if not isinstance(sources, dict):
605
+ raise _error(
606
+ "PZ_SCAFFOLD_PROJECT_CONFIG_INVALID",
607
+ f"项目 app sources 配置必须是对象:{app_id}",
608
+ action=action,
609
+ )
610
+ if sources.get("source_mode") == "local":
611
+ return
612
+ sources["source_mode"] = "local"
613
+ save_project_payload(project_dir, payload, action=action)
614
+
615
+
616
+ def _existing_import_matches(
617
+ path: Path,
618
+ *,
619
+ app_id: str,
620
+ cache: CachedGitSource,
621
+ subpath: PurePosixPath,
622
+ action: str,
623
+ ) -> bool:
624
+ _validate_app(path, app_id, action=action)
625
+ try:
626
+ payload = tomllib.loads((path / "pyproject.toml").read_text(encoding="utf-8"))
627
+ imported = payload["tool"]["pz"]["imported_from"]
628
+ except Exception:
629
+ return False
630
+ return (
631
+ isinstance(imported, dict)
632
+ and imported.get("kind") == "git"
633
+ and _canonical_remote(str(imported.get("source") or ""))
634
+ == _canonical_remote(cache.source)
635
+ and imported.get("revision") == cache.commit
636
+ and imported.get("subpath") == subpath.as_posix()
637
+ )
638
+
639
+
640
+ def _existing_local_import_matches(
641
+ path: Path,
642
+ *,
643
+ app_id: str,
644
+ source: str,
645
+ subpath: PurePosixPath,
646
+ action: str,
647
+ ) -> bool:
648
+ _validate_app(path, app_id, action=action)
649
+ try:
650
+ payload = tomllib.loads((path / "pyproject.toml").read_text(encoding="utf-8"))
651
+ imported = payload["tool"]["pz"]["imported_from"]
652
+ except Exception:
653
+ return False
654
+ return (
655
+ isinstance(imported, dict)
656
+ and imported.get("kind") == "path"
657
+ and imported.get("source") == "local"
658
+ and imported.get("identity")
659
+ == hashlib.sha256(source.encode("utf-8")).hexdigest()
660
+ and imported.get("revision") is None
661
+ and imported.get("subpath") == subpath.as_posix()
662
+ )
663
+
664
+
665
+ def find_existing_local_import(
666
+ locator: str,
667
+ *,
668
+ project_dir: Path,
669
+ action: str = "dev_source_import",
670
+ ) -> ImportedApp | None:
671
+ resolved = resolve_source_locator(locator, project_dir=project_dir, action=action)
672
+ if resolved.source_kind != "link" or resolved.expected_app_id is None:
673
+ return None
674
+ root = Path(resolved.clone_source).expanduser().resolve()
675
+ destination = project_dir / "apps" / resolved.expected_app_id
676
+ if not destination.is_dir() or destination.is_symlink():
677
+ return None
678
+ if not _existing_local_import_matches(
679
+ destination,
680
+ app_id=resolved.expected_app_id,
681
+ source=str(root),
682
+ subpath=resolved.subpath,
683
+ action=action,
684
+ ):
685
+ return None
686
+ return ImportedApp(
687
+ resolved.expected_app_id,
688
+ destination,
689
+ resolved.subpath,
690
+ "local",
691
+ False,
692
+ )
693
+
694
+
695
+ def _import_cached_apps_unlocked(
696
+ cache: CachedGitSource,
697
+ *,
698
+ project_dir: Path,
699
+ apps: tuple[tuple[str, PurePosixPath], ...],
700
+ action: str = "dev_source_import",
701
+ ) -> tuple[ImportedApp, ...]:
702
+ apps_root = project_dir / "apps"
703
+ apps_root.mkdir(parents=True, exist_ok=True)
704
+ config_path = project_dir / "pz.jsonc"
705
+ config_before = config_path.read_bytes() if config_path.is_file() else None
706
+ metadata_path = project_dir / "pyproject.toml"
707
+ metadata_before = metadata_path.read_bytes() if metadata_path.is_file() else None
708
+ existing: list[ImportedApp] = []
709
+ pending: list[tuple[str, PurePosixPath, Path, Path]] = []
710
+ for app_id, subpath in apps:
711
+ destination = apps_root / app_id
712
+ if destination.exists() or destination.is_symlink():
713
+ if (
714
+ destination.is_dir()
715
+ and not destination.is_symlink()
716
+ and _existing_import_matches(
717
+ destination,
718
+ app_id=app_id,
719
+ cache=cache,
720
+ subpath=subpath,
721
+ action=action,
722
+ )
723
+ ):
724
+ existing.append(ImportedApp(app_id, destination, subpath, cache.commit, False))
725
+ continue
726
+ raise _error(
727
+ "PZ_DEV_SOURCE_CONFLICT",
728
+ f"目标 app 路径已被占用:{app_id}",
729
+ action=action,
730
+ detail=str(destination),
731
+ )
732
+ staged = apps_root / f".{app_id}.{uuid.uuid4().hex}.tmp"
733
+ pending.append((app_id, subpath, destination, staged))
734
+ created: list[ImportedApp] = []
735
+ try:
736
+ for app_id, subpath, destination, staged in pending:
737
+ staged.mkdir()
738
+ _extract_archive(_archive_bytes(cache, subpath, action=action), staged, subpath, action=action)
739
+ _validate_app(staged, app_id, action=action)
740
+ _write_provenance(staged, cache, subpath)
741
+ for app_id, subpath, destination, staged in pending:
742
+ os.replace(staged, destination)
743
+ created.append(ImportedApp(app_id, destination, subpath, cache.commit, True))
744
+ _declare_local_source_mode(project_dir, app_id, action=action)
745
+ imported = tuple(existing + created)
746
+ declare_imported_apps(project_dir, imported, action=action)
747
+ except Exception:
748
+ for item in created:
749
+ shutil.rmtree(item.destination, ignore_errors=True)
750
+ if config_before is None:
751
+ config_path.unlink(missing_ok=True)
752
+ else:
753
+ config_path.write_bytes(config_before)
754
+ if metadata_before is None:
755
+ metadata_path.unlink(missing_ok=True)
756
+ else:
757
+ metadata_path.write_bytes(metadata_before)
758
+ raise
759
+ finally:
760
+ for _, _, _, staged in pending:
761
+ shutil.rmtree(staged, ignore_errors=True)
762
+ return tuple(existing + created)
763
+
764
+
765
+ def import_cached_apps(
766
+ cache: CachedGitSource,
767
+ *,
768
+ project_dir: Path,
769
+ apps: tuple[tuple[str, PurePosixPath], ...],
770
+ action: str = "dev_source_import",
771
+ ) -> tuple[ImportedApp, ...]:
772
+ checkout, lock_path = _cache_paths(cache.repo_key)
773
+ if checkout != cache.checkout:
774
+ raise _error(
775
+ "PZ_DEV_CACHE_RESOLUTION_MISSING",
776
+ "cache checkout 路径与规范 repo key 不匹配。",
777
+ action=action,
778
+ )
779
+ with _repo_lock(lock_path, action=action):
780
+ _validate_cache(
781
+ checkout,
782
+ repo_key=cache.repo_key,
783
+ source=cache.source,
784
+ action=action,
785
+ )
786
+ actual = _run_git(
787
+ "rev-parse",
788
+ f"{cache.resolution_ref}^{{commit}}",
789
+ cwd=checkout,
790
+ action=action,
791
+ )
792
+ if actual != cache.commit:
793
+ raise _error(
794
+ "PZ_DEV_CACHE_RESOLUTION_MISSING",
795
+ "导入前 cache 的固定 revision 已发生变化。",
796
+ action=action,
797
+ detail=f"expected={cache.commit}; actual={actual}",
798
+ )
799
+ return _import_cached_apps_unlocked(
800
+ cache,
801
+ project_dir=project_dir,
802
+ apps=apps,
803
+ action=action,
804
+ )
805
+
806
+
807
+ def _copy_local_app(
808
+ source: Path,
809
+ *,
810
+ project_dir: Path,
811
+ app_id: str,
812
+ source_identity: str,
813
+ subpath: PurePosixPath,
814
+ action: str,
815
+ ) -> ImportedApp:
816
+ destination = project_dir / "apps" / app_id
817
+ if destination.exists() or destination.is_symlink():
818
+ raise _error(
819
+ "PZ_DEV_SOURCE_CONFLICT",
820
+ f"目标 app 路径已被占用:{app_id}",
821
+ action=action,
822
+ detail=str(destination),
823
+ )
824
+ destination.parent.mkdir(parents=True, exist_ok=True)
825
+ staged = destination.parent / f".{app_id}.{uuid.uuid4().hex}.tmp"
826
+ excluded = {".git", ".venv", "__pycache__", ".pytest_cache", "dist", "build"}
827
+ committed = False
828
+ config_path = project_dir / "pz.jsonc"
829
+ config_before = config_path.read_bytes() if config_path.is_file() else None
830
+ metadata_path = project_dir / "pyproject.toml"
831
+ metadata_before = metadata_path.read_bytes() if metadata_path.is_file() else None
832
+ try:
833
+ source_root = source.resolve(strict=True)
834
+ for candidate in source.rglob("*"):
835
+ if not candidate.is_symlink():
836
+ continue
837
+ try:
838
+ target = candidate.resolve(strict=True)
839
+ except (OSError, RuntimeError) as exc:
840
+ raise _error(
841
+ "PZ_DEV_SOURCE_IMPORT_FAILED",
842
+ "本地来源包含无效符号链接。",
843
+ action=action,
844
+ detail=str(candidate),
845
+ ) from exc
846
+ if not target.is_relative_to(source_root):
847
+ raise _error(
848
+ "PZ_DEV_SOURCE_IMPORT_FAILED",
849
+ "本地来源包含指向目录外的符号链接。",
850
+ action=action,
851
+ detail=str(candidate),
852
+ )
853
+ shutil.copytree(
854
+ source,
855
+ staged,
856
+ symlinks=True,
857
+ ignore=lambda _root, names: [name for name in names if name in excluded or name.endswith(".egg-info")],
858
+ )
859
+ _validate_app(staged, app_id, action=action)
860
+ _write_local_provenance(
861
+ staged,
862
+ source=source_identity,
863
+ subpath=subpath,
864
+ )
865
+ os.replace(staged, destination)
866
+ committed = True
867
+ _declare_local_source_mode(project_dir, app_id, action=action)
868
+ result = ImportedApp(app_id, destination, subpath, "local", True)
869
+ declare_imported_apps(project_dir, (result,), action=action)
870
+ except Exception:
871
+ if committed:
872
+ shutil.rmtree(destination, ignore_errors=True)
873
+ if config_before is None:
874
+ config_path.unlink(missing_ok=True)
875
+ else:
876
+ config_path.write_bytes(config_before)
877
+ if metadata_before is None:
878
+ metadata_path.unlink(missing_ok=True)
879
+ else:
880
+ metadata_path.write_bytes(metadata_before)
881
+ raise
882
+ finally:
883
+ shutil.rmtree(staged, ignore_errors=True)
884
+ return result
885
+
886
+
887
+ def import_dev_source(
888
+ locator: str,
889
+ *,
890
+ project_dir: Path,
891
+ branch: str = "develop",
892
+ action: str = "dev_source_import",
893
+ ) -> ImportedApp:
894
+ resolved = resolve_source_locator(locator, project_dir=project_dir, action=action)
895
+ if resolved.source_kind == "link":
896
+ root = Path(resolved.clone_source).expanduser().resolve()
897
+ source = root if resolved.subpath == PurePosixPath(".") else root / Path(*resolved.subpath.parts)
898
+ app_id = resolved.expected_app_id
899
+ if app_id is None:
900
+ raise _error(
901
+ "PZ_DEV_SOURCE_IMPORT_AMBIGUOUS",
902
+ "本地目录包含多个子包,请指定一个最小子包目录。",
903
+ action=action,
904
+ detail=locator,
905
+ )
906
+ return _copy_local_app(
907
+ source,
908
+ project_dir=project_dir,
909
+ app_id=app_id,
910
+ source_identity=str(root),
911
+ subpath=resolved.subpath,
912
+ action=action,
913
+ )
914
+
915
+ assert resolved.repo_key is not None
916
+ cache = cache_git_source(
917
+ repo_key=resolved.repo_key,
918
+ source=resolved.clone_source,
919
+ branch=branch,
920
+ action=action,
921
+ )
922
+ return import_git_app(
923
+ locator,
924
+ cache=cache,
925
+ project_dir=project_dir,
926
+ action=action,
927
+ )
928
+
929
+
930
+ def import_local_dev_source(
931
+ locator: str,
932
+ *,
933
+ project_dir: Path,
934
+ action: str = "dev_source_import",
935
+ ) -> ImportedApp:
936
+ resolved = resolve_source_locator(locator, project_dir=project_dir, action=action)
937
+ if resolved.source_kind != "link":
938
+ raise _error(
939
+ "PZ_DEV_SOURCE_NOT_LOCAL",
940
+ "source import local mode requires a non-Git local directory.",
941
+ action=action,
942
+ detail=locator,
943
+ )
944
+ root = Path(resolved.clone_source).expanduser().resolve()
945
+ source = root if resolved.subpath == PurePosixPath(".") else root / Path(*resolved.subpath.parts)
946
+ if resolved.expected_app_id is None:
947
+ raise _error(
948
+ "PZ_DEV_SOURCE_IMPORT_AMBIGUOUS",
949
+ "本地目录包含多个子包,请指定一个最小子包目录。",
950
+ action=action,
951
+ detail=locator,
952
+ )
953
+ return _copy_local_app(
954
+ source,
955
+ project_dir=project_dir,
956
+ app_id=resolved.expected_app_id,
957
+ source_identity=str(root),
958
+ subpath=resolved.subpath,
959
+ action=action,
960
+ )
961
+
962
+
963
+ def import_git_app(
964
+ locator: str,
965
+ *,
966
+ cache: CachedGitSource,
967
+ project_dir: Path,
968
+ action: str = "dev_source_import",
969
+ ) -> ImportedApp:
970
+ resolved = resolve_source_locator(locator, project_dir=project_dir, action=action)
971
+ if resolved.source_kind != "git" or resolved.repo_key != cache.repo_key:
972
+ raise _error(
973
+ "PZ_DEV_CACHE_RESOLUTION_MISSING",
974
+ "源码 locator 与固定 Git cache identity 不一致。",
975
+ action=action,
976
+ detail=locator,
977
+ )
978
+ if resolved.expected_app_id is not None:
979
+ app_id = resolved.expected_app_id
980
+ subpath = resolved.subpath
981
+ else:
982
+ packages = tuple(
983
+ package
984
+ for package in iter_workspace_packages_in_roots((cache.checkout,))
985
+ if package.repo.resolve() != cache.checkout.resolve()
986
+ )
987
+ if len(packages) != 1:
988
+ raise _error(
989
+ "PZ_DEV_SOURCE_IMPORT_AMBIGUOUS",
990
+ "Git 来源必须唯一解析到一个正式最小子包。",
991
+ action=action,
992
+ detail=f"matches={len(packages)}",
993
+ )
994
+ package = packages[0]
995
+ app_id = package.app_id
996
+ subpath = PurePosixPath(package.repo.relative_to(cache.checkout).as_posix())
997
+ return import_cached_apps(
998
+ cache,
999
+ project_dir=project_dir,
1000
+ apps=((app_id, subpath),),
1001
+ action=action,
1002
+ )[0]
1003
+
1004
+
1005
+ def declare_imported_apps(
1006
+ project_dir: Path,
1007
+ imported: tuple[ImportedApp, ...],
1008
+ *,
1009
+ action: str = "declare_imported_apps",
1010
+ ) -> None:
1011
+ path = project_dir / "pyproject.toml"
1012
+ try:
1013
+ document = tomlkit.parse(path.read_text(encoding="utf-8"))
1014
+ project = document["project"]
1015
+ except Exception as exc:
1016
+ raise _error(
1017
+ "PZ_DEV_PROJECT_METADATA_INVALID",
1018
+ "无法更新项目依赖声明。",
1019
+ action=action,
1020
+ detail=str(exc),
1021
+ ) from exc
1022
+ dependencies = project.setdefault("dependencies", tomlkit.array())
1023
+ existing = {str(item).split("[", 1)[0].strip().lower() for item in dependencies}
1024
+ tool = document.setdefault("tool", tomlkit.table())
1025
+ uv = tool.setdefault("uv", tomlkit.table())
1026
+ sources = uv.setdefault("sources", tomlkit.table())
1027
+ for item in imported:
1028
+ try:
1029
+ payload = tomllib.loads(
1030
+ (item.destination / "pyproject.toml").read_text(encoding="utf-8")
1031
+ )
1032
+ except Exception:
1033
+ raise
1034
+ distribution = str(payload["project"]["name"]).strip()
1035
+ if distribution.lower() not in existing:
1036
+ dependencies.append(distribution)
1037
+ existing.add(distribution.lower())
1038
+ source = tomlkit.inline_table()
1039
+ source["path"] = item.destination.relative_to(project_dir).as_posix()
1040
+ source["editable"] = True
1041
+ sources[distribution] = source
1042
+ staged = path.with_name(f".{path.name}.{uuid.uuid4().hex}.tmp")
1043
+ try:
1044
+ staged.write_text(tomlkit.dumps(document), encoding="utf-8")
1045
+ os.replace(staged, path)
1046
+ finally:
1047
+ staged.unlink(missing_ok=True)
1048
+
1049
+
1050
+ __all__ = [
1051
+ "CachedGitSource",
1052
+ "ImportedApp",
1053
+ "cache_git_source",
1054
+ "declare_imported_apps",
1055
+ "find_existing_local_import",
1056
+ "import_cached_apps",
1057
+ "import_dev_source",
1058
+ "import_git_app",
1059
+ "import_local_dev_source",
1060
+ "load_cached_git_source",
1061
+ ]