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.
- pz_cli-5.0.0.dist-info/METADATA +51 -0
- pz_cli-5.0.0.dist-info/RECORD +399 -0
- pz_cli-5.0.0.dist-info/WHEEL +4 -0
- pz_cli-5.0.0.dist-info/entry_points.txt +5 -0
- pz_core/__init__.py +64 -0
- pz_core/__main__.py +7 -0
- pz_core/app.py +45 -0
- pz_core/assembly/__init__.py +17 -0
- pz_core/assembly/app_discovery.py +1240 -0
- pz_core/assembly/apps/__init__.py +25 -0
- pz_core/assembly/apps/compiler.py +824 -0
- pz_core/assembly/apps/providers.py +188 -0
- pz_core/assembly/cli/__init__.py +9 -0
- pz_core/assembly/cli/compiler.py +166 -0
- pz_core/assembly/diagnostics/__init__.py +21 -0
- pz_core/assembly/diagnostics/checks.py +257 -0
- pz_core/assembly/diagnostics/dev_sources.py +569 -0
- pz_core/assembly/events/__init__.py +5 -0
- pz_core/assembly/events/compiler.py +219 -0
- pz_core/assembly/extensions/__init__.py +5 -0
- pz_core/assembly/extensions/compiler.py +696 -0
- pz_core/assembly/infra/__init__.py +6 -0
- pz_core/assembly/infra/compiler.py +272 -0
- pz_core/assembly/lifecycle/__init__.py +5 -0
- pz_core/assembly/lifecycle/compiler.py +95 -0
- pz_core/assembly/middlewares/__init__.py +19 -0
- pz_core/assembly/middlewares/compiler.py +508 -0
- pz_core/assembly/project/__init__.py +7 -0
- pz_core/assembly/project/compiler.py +9 -0
- pz_core/assembly/project/context.py +17 -0
- pz_core/assembly/project/discovery.py +227 -0
- pz_core/assembly/resources/__init__.py +19 -0
- pz_core/assembly/resources/compiler.py +310 -0
- pz_core/assembly/resources/settings.py +129 -0
- pz_core/assembly/routes/__init__.py +7 -0
- pz_core/assembly/routes/compiler.py +400 -0
- pz_core/assembly/runtime_manifests/__init__.py +13 -0
- pz_core/assembly/runtime_manifests/compiler.py +159 -0
- pz_core/assembly/settings/__init__.py +17 -0
- pz_core/assembly/settings/compiler.py +786 -0
- pz_core/assembly/settings/host_project_overrides.py +82 -0
- pz_core/assembly/settings/host_runtime_endpoint.py +207 -0
- pz_core/authoring/__init__.py +79 -0
- pz_core/authoring/app_init_service.py +946 -0
- pz_core/authoring/app_versions.py +814 -0
- pz_core/authoring/cli.py +192 -0
- pz_core/authoring/commands.py +1810 -0
- pz_core/authoring/conflicts.py +180 -0
- pz_core/authoring/dev_bindings.py +916 -0
- pz_core/authoring/dev_git.py +1087 -0
- pz_core/authoring/dev_import.py +1061 -0
- pz_core/authoring/dev_manifest.py +522 -0
- pz_core/authoring/dev_sources.py +1638 -0
- pz_core/authoring/env_cli.py +454 -0
- pz_core/authoring/files.py +109 -0
- pz_core/authoring/ide_projection.py +1129 -0
- pz_core/authoring/naming.py +58 -0
- pz_core/authoring/official_dev_sources.py +432 -0
- pz_core/authoring/official_sources.json +156 -0
- pz_core/authoring/orchestration_service.py +558 -0
- pz_core/authoring/project_default.jsonc +53 -0
- pz_core/authoring/project_defaults.py +88 -0
- pz_core/authoring/project_metadata_service.py +199 -0
- pz_core/authoring/project_settings_checks.py +51 -0
- pz_core/authoring/project_settings_cli.py +299 -0
- pz_core/authoring/project_settings_service.py +1700 -0
- pz_core/authoring/project_state.py +399 -0
- pz_core/authoring/publish_git.py +423 -0
- pz_core/authoring/publish_lock.py +188 -0
- pz_core/authoring/publish_source.py +195 -0
- pz_core/authoring/release.py +1382 -0
- pz_core/authoring/repo_keys.py +29 -0
- pz_core/authoring/root_cli.py +28 -0
- pz_core/authoring/scaffold_service.py +325 -0
- pz_core/authoring/sync_service.py +787 -0
- pz_core/authoring/templates/app/__init__.py +1 -0
- pz_core/authoring/templates/app/api.py +25 -0
- pz_core/authoring/templates/app/apps.py +31 -0
- pz_core/authoring/templates/app/auth_package_init.py +14 -0
- pz_core/authoring/templates/app/auth_readme.py +29 -0
- pz_core/authoring/templates/app/configs_package_init.py +14 -0
- pz_core/authoring/templates/app/default_env.py +18 -0
- pz_core/authoring/templates/app/default_jsonc.py +25 -0
- pz_core/authoring/templates/app/locales_readme.py +25 -0
- pz_core/authoring/templates/app/migrations_readme.py +19 -0
- pz_core/authoring/templates/app/models.py +14 -0
- pz_core/authoring/templates/app/package_init.py +8 -0
- pz_core/authoring/templates/app/pages_base.py +161 -0
- pz_core/authoring/templates/app/pages_readme.py +25 -0
- pz_core/authoring/templates/app/public.py +31 -0
- pz_core/authoring/templates/app/pyproject.py +77 -0
- pz_core/authoring/templates/app/readme.py +74 -0
- pz_core/authoring/templates/app/settings.py +46 -0
- pz_core/authoring/templates/app/static_app_js.py +24 -0
- pz_core/authoring/templates/app/static_readme.py +31 -0
- pz_core/authoring/templates/app/template_config_page.py +23 -0
- pz_core/authoring/templates/app/template_index_html.py +28 -0
- pz_core/authoring/templates/app/templates_readme.py +32 -0
- pz_core/authoring/templates/app/urls.py +36 -0
- pz_core/authoring/templates/app/views.py +36 -0
- pz_core/authoring/templates/project/__init__.py +1 -0
- pz_core/authoring/templates/project/agents.py +51 -0
- pz_core/authoring/templates/project/apps_container_readme.py +37 -0
- pz_core/authoring/templates/project/main_py.py +59 -0
- pz_core/authoring/templates/project/project_env.py +19 -0
- pz_core/authoring/templates/project/pyproject.py +26 -0
- pz_core/authoring/templates/project/pz_env_env.py +65 -0
- pz_core/authoring/templates/project/pz_env_jsonc.py +69 -0
- pz_core/authoring/templates/project/pz_jsonc.py +12 -0
- pz_core/authoring/templates/project/readme.py +94 -0
- pz_core/authoring/templates/project/root_gitkeep.py +8 -0
- pz_core/authoring/templates/shared/__init__.py +5 -0
- pz_core/authoring/templates/shared/text.py +10 -0
- pz_core/authoring/venv_service.py +87 -0
- pz_core/authoring/workspace.py +167 -0
- pz_core/cli.py +291 -0
- pz_core/cli_options.py +74 -0
- pz_core/core/__init__.py +3 -0
- pz_core/core/errors/__init__.py +5 -0
- pz_core/core/errors/models.py +117 -0
- pz_core/core/events/__init__.py +5 -0
- pz_core/core/events/registry.py +54 -0
- pz_core/core/extensions/__init__.py +51 -0
- pz_core/core/extensions/registry.py +790 -0
- pz_core/core/health/__init__.py +6 -0
- pz_core/core/health/contracts.py +10 -0
- pz_core/core/middlewares/__init__.py +6 -0
- pz_core/core/middlewares/models.py +24 -0
- pz_core/core/routing/__init__.py +5 -0
- pz_core/core/routing/models.py +21 -0
- pz_core/core/settings_contracts.py +25 -0
- pz_core/docs/README.md +20 -0
- pz_core/docs/distribution-README.md +438 -0
- pz_core/docs/pz/345/221/275/344/273/244/346/200/273/350/247/210.md +1011 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- pz_core/docs/pz/350/247/204/350/214/203/344/270/216/344/275/277/347/224/250/README.md +98 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- pz_core/docs//345/217/221/347/211/210/345/211/215/346/270/205/345/215/225-4.0.11.md +84 -0
- pz_core/docs//346/226/260/346/211/213/346/214/207/345/215/227.md +422 -0
- pz_core/infra/__init__.py +17 -0
- pz_core/infra/db/__init__.py +23 -0
- pz_core/infra/db/contracts.py +73 -0
- pz_core/infra/health/__init__.py +5 -0
- pz_core/infra/health/contracts.py +8 -0
- pz_core/infra/kv/__init__.py +5 -0
- pz_core/infra/kv/contracts.py +17 -0
- pz_core/infra/observability/__init__.py +23 -0
- pz_core/infra/observability/contracts.py +142 -0
- pz_core/infra/resilience/__init__.py +5 -0
- pz_core/infra/resilience/contracts.py +17 -0
- pz_core/infra/resources/__init__.py +5 -0
- pz_core/infra/resources/contracts.py +16 -0
- pz_core/infra/settings/__init__.py +17 -0
- pz_core/infra/settings/models.py +81 -0
- pz_core/infra/storage/__init__.py +5 -0
- pz_core/infra/storage/ports.py +27 -0
- pz_core/project_root_commands.py +282 -0
- pz_core/project_runtime_cli.py +344 -0
- pz_core/project_sources.py +253 -0
- pz_core/pz_launcher.py +209 -0
- pz_core/root_cli.py +70 -0
- pz_core/runtime/__init__.py +229 -0
- pz_core/runtime/access.py +86 -0
- pz_core/runtime/adapters/__init__.py +5 -0
- pz_core/runtime/adapters/base.py +18 -0
- pz_core/runtime/asyncio_bridge.py +18 -0
- pz_core/runtime/bindings/__init__.py +5 -0
- pz_core/runtime/bindings/container.py +50 -0
- pz_core/runtime/cli/__init__.py +5 -0
- pz_core/runtime/cli/_args.py +67 -0
- pz_core/runtime/cli/app_commands.py +417 -0
- pz_core/runtime/cli/check.py +214 -0
- pz_core/runtime/cli/db.py +297 -0
- pz_core/runtime/cli/main.py +89 -0
- pz_core/runtime/cli/resources.py +280 -0
- pz_core/runtime/discovery.py +23 -0
- pz_core/runtime/environment_integrity.py +157 -0
- pz_core/runtime/errors.py +5 -0
- pz_core/runtime/events.py +87 -0
- pz_core/runtime/facades/__init__.py +38 -0
- pz_core/runtime/facades/_capabilities.py +46 -0
- pz_core/runtime/facades/cache.py +103 -0
- pz_core/runtime/facades/database.py +71 -0
- pz_core/runtime/facades/events.py +380 -0
- pz_core/runtime/facades/health.py +101 -0
- pz_core/runtime/facades/mail.py +121 -0
- pz_core/runtime/facades/observability.py +102 -0
- pz_core/runtime/facades/providers.py +75 -0
- pz_core/runtime/facades/resources.py +52 -0
- pz_core/runtime/facades/storage.py +296 -0
- pz_core/runtime/health/__init__.py +5 -0
- pz_core/runtime/health/service.py +102 -0
- pz_core/runtime/host.py +188 -0
- pz_core/runtime/host_runner.py +282 -0
- pz_core/runtime/http/__init__.py +20 -0
- pz_core/runtime/http/app.py +576 -0
- pz_core/runtime/http_timing.py +173 -0
- pz_core/runtime/invocation.py +29 -0
- pz_core/runtime/json_safe.py +47 -0
- pz_core/runtime/lifecycle/__init__.py +25 -0
- pz_core/runtime/lifecycle/manager.py +533 -0
- pz_core/runtime/plan.py +214 -0
- pz_core/runtime/plan_context.py +33 -0
- pz_core/runtime/plan_snapshot.py +177 -0
- pz_core/runtime/registries/__init__.py +27 -0
- pz_core/runtime/registries/capabilities.py +101 -0
- pz_core/runtime/registries/providers.py +396 -0
- pz_core/runtime/resource_assets.py +344 -0
- pz_core/runtime/run_cli.py +211 -0
- pz_core/runtime/settings.py +888 -0
- pz_core/runtime/worker/__init__.py +21 -0
- pz_core/runtime/worker/engine.py +921 -0
- pz_core/runtime/worker/runtime.py +25 -0
- pz_core/testing/__init__.py +15 -0
- pz_core/testing/doc_route_contracts.py +305 -0
- pz_core/utils/__init__.py +44 -0
- pz_core/utils/env.py +65 -0
- pz_core/utils/project_jsonc/__init__.py +29 -0
- pz_core/utils/project_jsonc/document.py +420 -0
- pz_core/utils/project_settings.py +99 -0
- pz_foundation/__init__.py +50 -0
- pz_foundation/_contracts.py +81 -0
- pz_foundation/_errors.py +34 -0
- pz_foundation/api.py +14 -0
- pz_foundation/apps.py +127 -0
- pz_foundation/auth/__init__.py +1 -0
- pz_foundation/cache_control.py +233 -0
- pz_foundation/cli/__init__.py +2 -0
- pz_foundation/cli/workers.py +565 -0
- pz_foundation/configs/__init__.py +0 -0
- pz_foundation/configs/default.env +15 -0
- pz_foundation/configs/default.jsonc +452 -0
- pz_foundation/db/__init__.py +94 -0
- pz_foundation/db/aerich_executor.py +664 -0
- pz_foundation/db/config.py +358 -0
- pz_foundation/db/connection_manager.py +689 -0
- pz_foundation/db/connection_probe.py +204 -0
- pz_foundation/db/database.py +1291 -0
- pz_foundation/db/deps.py +47 -0
- pz_foundation/db/lifecycle_status.py +85 -0
- pz_foundation/db/migrations.py +477 -0
- pz_foundation/db/mysql.py +57 -0
- pz_foundation/db/postgresql.py +409 -0
- pz_foundation/db/production_pool_fallback.py +143 -0
- pz_foundation/db/public.py +45 -0
- pz_foundation/db/session.py +369 -0
- pz_foundation/db/sqlite.py +15 -0
- pz_foundation/db/tortoise_manager.py +724 -0
- pz_foundation/db/tortoise_psycopg.py +148 -0
- pz_foundation/docs/README.md +18 -0
- pz_foundation/docs/distribution-README.md +319 -0
- 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
- 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
- pz_foundation/docs//350/256/276/350/256/241/346/226/207/346/241/243.md +288 -0
- 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
- pz_foundation/health.py +105 -0
- pz_foundation/host/__init__.py +11 -0
- pz_foundation/host/builder.py +34 -0
- pz_foundation/host/cache_runtime.py +44 -0
- pz_foundation/host/db/__init__.py +17 -0
- pz_foundation/host/db/aerich_config.py +36 -0
- pz_foundation/host/db/config.py +106 -0
- pz_foundation/host/db/connection_manager.py +48 -0
- pz_foundation/host/db/deps.py +5 -0
- pz_foundation/host/db/orm_retry_patch.py +566 -0
- pz_foundation/host/db/request_adapter.py +325 -0
- pz_foundation/host/db/router.py +28 -0
- pz_foundation/host/db/session.py +284 -0
- pz_foundation/host/db/tortoise_manager.py +10 -0
- pz_foundation/host/idempotency.py +132 -0
- pz_foundation/host/lifespan.py +247 -0
- pz_foundation/host/redis_runtime.py +100 -0
- pz_foundation/host/web.py +790 -0
- pz_foundation/infra_provider.py +236 -0
- pz_foundation/kv/__init__.py +25 -0
- pz_foundation/kv/cache.py +1166 -0
- pz_foundation/kv/deps.py +84 -0
- pz_foundation/kv/public.py +25 -0
- pz_foundation/kv/redis_manager.py +1531 -0
- pz_foundation/lazy_exports.py +43 -0
- pz_foundation/lazy_imports.py +124 -0
- pz_foundation/lifecycle.py +392 -0
- pz_foundation/managed_workers.py +1117 -0
- pz_foundation/middlewares/__init__.py +26 -0
- pz_foundation/middlewares/_settings.py +250 -0
- pz_foundation/middlewares/analytics.py +124 -0
- pz_foundation/middlewares/cache_control.py +156 -0
- pz_foundation/middlewares/compression.py +202 -0
- pz_foundation/middlewares/cors.py +62 -0
- pz_foundation/middlewares/geo_country.py +44 -0
- pz_foundation/middlewares/inner_router.py +50 -0
- pz_foundation/middlewares/rate_limited/__init__.py +3 -0
- pz_foundation/middlewares/rate_limited/key_limiter.py +115 -0
- pz_foundation/middlewares/request_logging.py +104 -0
- pz_foundation/middlewares/route_owner.py +309 -0
- pz_foundation/middlewares/security_csrf.py +44 -0
- pz_foundation/middlewares/security_headers/__init__.py +4 -0
- pz_foundation/middlewares/security_headers/conflicts.py +44 -0
- pz_foundation/middlewares/security_headers/csp_nonce.py +200 -0
- pz_foundation/middlewares/security_headers/security_headers.py +44 -0
- pz_foundation/migrations/__init__.py +1 -0
- pz_foundation/migrations/pz_foundation/__init__.py +1 -0
- pz_foundation/models.py +22 -0
- pz_foundation/notifications/__init__.py +23 -0
- pz_foundation/notifications/email.py +542 -0
- pz_foundation/notifications/public.py +31 -0
- pz_foundation/observability/__init__.py +71 -0
- pz_foundation/observability/http.py +34 -0
- pz_foundation/observability/logger/__init__.py +49 -0
- pz_foundation/observability/logger/_bot_settings.py +126 -0
- pz_foundation/observability/logger/bot_message.py +592 -0
- pz_foundation/observability/logger/core.py +385 -0
- pz_foundation/observability/logger/delivery_audit.py +84 -0
- pz_foundation/observability/logger/feishu_bot.py +199 -0
- pz_foundation/observability/logger/telegram_bot.py +194 -0
- pz_foundation/observability/metrics.py +365 -0
- pz_foundation/observability/runtime.py +68 -0
- pz_foundation/public.py +405 -0
- pz_foundation/resources/__init__.py +12 -0
- pz_foundation/resources/builder.py +900 -0
- pz_foundation/resources/images.py +363 -0
- pz_foundation/root_cli.py +20 -0
- pz_foundation/routing/__init__.py +203 -0
- pz_foundation/routing/canonical.py +486 -0
- pz_foundation/routing/convertors.py +82 -0
- pz_foundation/routing/declarations.py +843 -0
- pz_foundation/routing/exports.py +148 -0
- pz_foundation/routing/loaders.py +138 -0
- pz_foundation/routing/manifest.py +215 -0
- pz_foundation/routing/manifest_provider.py +47 -0
- pz_foundation/routing/mounts.py +101 -0
- pz_foundation/routing/ordering.py +172 -0
- pz_foundation/routing/registry.py +133 -0
- pz_foundation/routing/utils.py +28 -0
- pz_foundation/runtime.py +234 -0
- pz_foundation/settings.py +253 -0
- pz_foundation/settings_normalization.py +130 -0
- pz_foundation/storage/__init__.py +25 -0
- pz_foundation/storage/local.py +209 -0
- pz_foundation/storage/public.py +106 -0
- pz_foundation/storage/s3.py +116 -0
- pz_foundation/storage/service.py +234 -0
- pz_foundation/urls.py +19 -0
- pz_foundation/utils/__init__.py +6 -0
- pz_foundation/utils/merge.py +160 -0
- pz_foundation/worker_replicas.py +55 -0
- pz_settings_center/__init__.py +5 -0
- pz_settings_center/api/__init__.py +5 -0
- pz_settings_center/api/runtime_overrides.py +360 -0
- pz_settings_center/apps.py +28 -0
- pz_settings_center/configs/__init__.py +0 -0
- pz_settings_center/configs/default.env +2 -0
- pz_settings_center/configs/default.jsonc +1 -0
- pz_settings_center/docs/01-settings_center/350/203/275/345/212/233/350/276/271/347/225/214.md +124 -0
- pz_settings_center/docs/README.md +12 -0
- pz_settings_center/docs/distribution-README.md +150 -0
- pz_settings_center/docs//350/256/276/350/256/241/346/226/207/346/241/243.md +48 -0
- pz_settings_center/integration_payload.py +18 -0
- pz_settings_center/project_helpers.py +405 -0
- pz_settings_center/public.py +139 -0
- pz_settings_center/routing_helpers.py +73 -0
- pz_settings_center/runtime_override_provider.py +61 -0
- pz_settings_center/typed_settings.py +324 -0
- pz_settings_center/urls.py +9 -0
|
@@ -0,0 +1,787 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
import csv
|
|
5
|
+
from fnmatch import fnmatchcase
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import shutil
|
|
9
|
+
import subprocess
|
|
10
|
+
import tomllib
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from pz_core.project_sources import (
|
|
15
|
+
OFFICIAL_CONTRIB_SOURCE_ROOT,
|
|
16
|
+
OFFICIAL_TRUTH_SOURCE_REPO_NAMES,
|
|
17
|
+
resolve_contrib_source_root,
|
|
18
|
+
resolve_sources_root,
|
|
19
|
+
)
|
|
20
|
+
from pz_core.runtime.errors import PzError
|
|
21
|
+
from pz_core.utils.project_jsonc import PROJECT_JSONC_NAME, load_jsonc_file
|
|
22
|
+
|
|
23
|
+
from .project_state import load_dev_manifest
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_OWNER = "pz_core.authoring.env"
|
|
27
|
+
_EDITABLE_IMPORT_RE = re.compile(r"^\s*import\s+([A-Za-z0-9_]+)\s*;")
|
|
28
|
+
_QUOTED_ABS_PATH_RE = re.compile(r"""['"](/[^'"]+)['"]""")
|
|
29
|
+
_PROJECT_ENV_CONFIG_NAMES = (
|
|
30
|
+
PROJECT_JSONC_NAME,
|
|
31
|
+
"pz.develop.jsonc",
|
|
32
|
+
"pz.test.jsonc",
|
|
33
|
+
"pz.product.jsonc",
|
|
34
|
+
)
|
|
35
|
+
_GENERATED_SOURCE_ARTIFACT_DIR_PATTERNS = (
|
|
36
|
+
".venv",
|
|
37
|
+
".pytest_cache",
|
|
38
|
+
"__pycache__",
|
|
39
|
+
"*.egg-info",
|
|
40
|
+
"dist",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True, slots=True)
|
|
45
|
+
class SyncTarget:
|
|
46
|
+
name: str
|
|
47
|
+
kind: str
|
|
48
|
+
path: Path
|
|
49
|
+
virtualenv_path: Path
|
|
50
|
+
exists: bool
|
|
51
|
+
has_pyproject: bool
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def build_sync_target(*, name: str, kind: str, path: Path) -> SyncTarget:
|
|
55
|
+
raw_path = _absolute_without_symlink_resolution(path)
|
|
56
|
+
resolved = raw_path.resolve()
|
|
57
|
+
exists = resolved.exists() and resolved.is_dir()
|
|
58
|
+
return SyncTarget(
|
|
59
|
+
name=name,
|
|
60
|
+
kind=kind,
|
|
61
|
+
path=resolved,
|
|
62
|
+
virtualenv_path=raw_path,
|
|
63
|
+
exists=exists,
|
|
64
|
+
has_pyproject=exists and (resolved / "pyproject.toml").is_file(),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass(frozen=True, slots=True)
|
|
69
|
+
class _EditableSourcePath:
|
|
70
|
+
resolved: Path
|
|
71
|
+
preferred: Path
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _absolute_without_symlink_resolution(path: Path) -> Path:
|
|
75
|
+
expanded = path.expanduser()
|
|
76
|
+
if not expanded.is_absolute():
|
|
77
|
+
expanded = Path.cwd() / expanded
|
|
78
|
+
return expanded.absolute()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def collect_sync_targets(project_dir: Path) -> tuple[SyncTarget, ...]:
|
|
82
|
+
targets: list[SyncTarget] = []
|
|
83
|
+
seen_paths: set[str] = set()
|
|
84
|
+
|
|
85
|
+
def add_target(*, name: str, kind: str, path: Path) -> None:
|
|
86
|
+
target = build_sync_target(name=name, kind=kind, path=path)
|
|
87
|
+
key = str(target.path)
|
|
88
|
+
if key in seen_paths:
|
|
89
|
+
return
|
|
90
|
+
seen_paths.add(key)
|
|
91
|
+
targets.append(target)
|
|
92
|
+
|
|
93
|
+
add_target(name="project", kind="project", path=project_dir)
|
|
94
|
+
manifest = load_dev_manifest(project_dir)
|
|
95
|
+
if manifest is None:
|
|
96
|
+
return tuple(targets)
|
|
97
|
+
for repo_key, repo in sorted(manifest.repos.items()):
|
|
98
|
+
add_target(
|
|
99
|
+
name=repo_key,
|
|
100
|
+
kind="repo",
|
|
101
|
+
path=project_dir / Path(*repo.checkout.parts),
|
|
102
|
+
)
|
|
103
|
+
for binding_key, binding in sorted(manifest.bindings.items()):
|
|
104
|
+
if binding.source_kind != "link":
|
|
105
|
+
continue
|
|
106
|
+
add_target(
|
|
107
|
+
name=binding_key,
|
|
108
|
+
kind=binding.kind,
|
|
109
|
+
path=Path(binding.source).expanduser(),
|
|
110
|
+
)
|
|
111
|
+
return tuple(targets)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def sync_project_targets(
|
|
115
|
+
project_dir: Path,
|
|
116
|
+
*,
|
|
117
|
+
action: str,
|
|
118
|
+
groups: tuple[str, ...] = (),
|
|
119
|
+
extras: tuple[str, ...] = (),
|
|
120
|
+
reinstall_packages: tuple[str, ...] = (),
|
|
121
|
+
project_only: bool = False,
|
|
122
|
+
) -> tuple[SyncTarget, ...]:
|
|
123
|
+
uv_binary = _require_uv_binary(action=action)
|
|
124
|
+
sync_targets = collect_sync_targets(project_dir)
|
|
125
|
+
inferred_extras = _infer_required_extras_for_project(project_dir, action=action)
|
|
126
|
+
if project_only:
|
|
127
|
+
sync_targets = tuple(item for item in sync_targets if item.kind == "project")
|
|
128
|
+
ordered_targets = [
|
|
129
|
+
*[item for item in sync_targets if item.kind == "project" and item.exists and item.has_pyproject],
|
|
130
|
+
*[item for item in sync_targets if item.kind != "project" and item.exists and item.has_pyproject],
|
|
131
|
+
]
|
|
132
|
+
synced: list[SyncTarget] = []
|
|
133
|
+
for item in ordered_targets:
|
|
134
|
+
_sync_target(
|
|
135
|
+
item,
|
|
136
|
+
project_dir=project_dir,
|
|
137
|
+
uv_binary=uv_binary,
|
|
138
|
+
action=action,
|
|
139
|
+
groups=groups,
|
|
140
|
+
extras=extras,
|
|
141
|
+
inferred_extras=inferred_extras,
|
|
142
|
+
reinstall_packages=reinstall_packages,
|
|
143
|
+
)
|
|
144
|
+
synced.append(item)
|
|
145
|
+
return tuple(synced)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _require_uv_binary(*, action: str) -> str:
|
|
149
|
+
uv_binary = shutil.which("uv")
|
|
150
|
+
if uv_binary:
|
|
151
|
+
return uv_binary
|
|
152
|
+
raise PzError(
|
|
153
|
+
code="PZ_ENV_UV_MISSING",
|
|
154
|
+
message="required dependency backend was not found on PATH.",
|
|
155
|
+
stage="cli",
|
|
156
|
+
owner=_OWNER,
|
|
157
|
+
action=action,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _sync_target(
|
|
162
|
+
target: SyncTarget,
|
|
163
|
+
*,
|
|
164
|
+
project_dir: Path,
|
|
165
|
+
uv_binary: str,
|
|
166
|
+
action: str,
|
|
167
|
+
groups: tuple[str, ...] = (),
|
|
168
|
+
extras: tuple[str, ...] = (),
|
|
169
|
+
inferred_extras: tuple[str, ...] = (),
|
|
170
|
+
reinstall_packages: tuple[str, ...] = (),
|
|
171
|
+
) -> None:
|
|
172
|
+
venv_path = _target_virtualenv_path(target.virtualenv_path)
|
|
173
|
+
editable_source_paths = _editable_source_paths_for_target(target.path)
|
|
174
|
+
allowed_editable_paths = tuple(item.resolved for item in editable_source_paths)
|
|
175
|
+
_prune_stale_editable_artifacts(venv_path, allowed_editable_paths=allowed_editable_paths)
|
|
176
|
+
reinstall_queue: list[str] = []
|
|
177
|
+
for package_name in (*reinstall_packages, *_find_corrupt_installed_distributions(venv_path)):
|
|
178
|
+
normalized = str(package_name).strip()
|
|
179
|
+
if normalized and normalized not in reinstall_queue:
|
|
180
|
+
reinstall_queue.append(normalized)
|
|
181
|
+
command = [uv_binary, "sync"]
|
|
182
|
+
for group in groups:
|
|
183
|
+
command.extend(["--group", group])
|
|
184
|
+
for extra in _extras_for_target(target, extras=extras, inferred_extras=inferred_extras):
|
|
185
|
+
command.extend(["--extra", extra])
|
|
186
|
+
for package_name in reinstall_queue:
|
|
187
|
+
command.extend(["--reinstall-package", package_name])
|
|
188
|
+
with _prepare_sync_execution(project_dir=project_dir, target=target, command=command, uv_binary=uv_binary, action=action) as prepared:
|
|
189
|
+
completed = subprocess.run(
|
|
190
|
+
prepared.command,
|
|
191
|
+
cwd=str(prepared.cwd),
|
|
192
|
+
check=False,
|
|
193
|
+
capture_output=True,
|
|
194
|
+
text=True,
|
|
195
|
+
env=prepared.env,
|
|
196
|
+
)
|
|
197
|
+
if completed.returncode == 0:
|
|
198
|
+
_canonicalize_editable_artifacts(venv_path, editable_source_paths=editable_source_paths)
|
|
199
|
+
_prune_generated_source_artifacts_for_target(project_dir=project_dir, target=target)
|
|
200
|
+
return
|
|
201
|
+
detail = (completed.stderr or completed.stdout or "dependency sync failed").strip()
|
|
202
|
+
raise PzError(
|
|
203
|
+
code="PZ_ENV_SYNC_FAILED",
|
|
204
|
+
message=f"failed to run dependency sync for {target.kind}:{target.name}.",
|
|
205
|
+
stage="cli",
|
|
206
|
+
owner=_OWNER,
|
|
207
|
+
action=action,
|
|
208
|
+
detail=detail,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _extras_for_target(
|
|
213
|
+
target: SyncTarget,
|
|
214
|
+
*,
|
|
215
|
+
extras: tuple[str, ...],
|
|
216
|
+
inferred_extras: tuple[str, ...] = (),
|
|
217
|
+
) -> tuple[str, ...]:
|
|
218
|
+
if target.kind == "project":
|
|
219
|
+
return tuple(extra for extra in extras if extra)
|
|
220
|
+
supported = _declared_optional_dependency_names(target)
|
|
221
|
+
resolved: list[str] = []
|
|
222
|
+
for extra in (*extras, *inferred_extras):
|
|
223
|
+
normalized = str(extra).strip()
|
|
224
|
+
if normalized and normalized in supported and normalized not in resolved:
|
|
225
|
+
resolved.append(normalized)
|
|
226
|
+
if target.kind == "runtime" and _runtime_target_has_contrib_checkout(target) and "contrib" not in resolved:
|
|
227
|
+
resolved.append("contrib")
|
|
228
|
+
return tuple(resolved)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _infer_required_extras_for_project(project_dir: Path, *, action: str) -> tuple[str, ...]:
|
|
232
|
+
required: list[str] = []
|
|
233
|
+
for config_name in _PROJECT_ENV_CONFIG_NAMES:
|
|
234
|
+
payload = load_jsonc_file(
|
|
235
|
+
project_dir / config_name,
|
|
236
|
+
owner=_OWNER,
|
|
237
|
+
action=action,
|
|
238
|
+
)
|
|
239
|
+
database = _project_database_config(payload)
|
|
240
|
+
if database is None or not _database_config_is_enabled(database):
|
|
241
|
+
continue
|
|
242
|
+
extra = _database_config_required_extra(database)
|
|
243
|
+
if extra is not None and extra not in required:
|
|
244
|
+
required.append(extra)
|
|
245
|
+
return tuple(required)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _project_database_config(payload: dict[str, object]) -> dict[str, object] | None:
|
|
249
|
+
apps = payload.get("apps")
|
|
250
|
+
if not isinstance(apps, dict):
|
|
251
|
+
return None
|
|
252
|
+
foundation = apps.get("pz_foundation")
|
|
253
|
+
if not isinstance(foundation, dict):
|
|
254
|
+
return None
|
|
255
|
+
database = foundation.get("database")
|
|
256
|
+
return database if isinstance(database, dict) else None
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _database_config_is_enabled(database: dict[str, object]) -> bool:
|
|
260
|
+
enabled = database.get("enabled")
|
|
261
|
+
state = str(database.get("state") or "").strip().lower()
|
|
262
|
+
if enabled is False or state == "disabled":
|
|
263
|
+
return False
|
|
264
|
+
if enabled is True or state == "enabled":
|
|
265
|
+
return True
|
|
266
|
+
return _database_config_required_extra(database) is not None
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _database_config_required_extra(database: dict[str, object]) -> str | None:
|
|
270
|
+
driver = str(database.get("driver") or "").strip().lower()
|
|
271
|
+
urls = database.get("urls")
|
|
272
|
+
url_values = [str(url).strip().lower() for url in urls] if isinstance(urls, list) else []
|
|
273
|
+
if driver in {"postgres", "postgresql"} or any(
|
|
274
|
+
url.startswith(("postgres://", "postgresql://")) for url in url_values
|
|
275
|
+
):
|
|
276
|
+
return "db-postgres"
|
|
277
|
+
if driver in {"mysql", "mariadb"} or any(
|
|
278
|
+
url.startswith(("mysql://", "mysql+pymysql://", "mariadb://")) for url in url_values
|
|
279
|
+
):
|
|
280
|
+
return "db-mysql"
|
|
281
|
+
return None
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _declared_optional_dependency_names(target: SyncTarget) -> frozenset[str]:
|
|
285
|
+
pyproject_path = target.path / "pyproject.toml"
|
|
286
|
+
if not pyproject_path.is_file():
|
|
287
|
+
return frozenset()
|
|
288
|
+
try:
|
|
289
|
+
payload = tomllib.loads(pyproject_path.read_text(encoding="utf-8"))
|
|
290
|
+
except tomllib.TOMLDecodeError as exc:
|
|
291
|
+
raise PzError(
|
|
292
|
+
code="PZ_ENV_INVALID_PYPROJECT",
|
|
293
|
+
message=f"failed to parse pyproject for {target.kind}:{target.name}.",
|
|
294
|
+
stage="cli",
|
|
295
|
+
owner=_OWNER,
|
|
296
|
+
action="sync",
|
|
297
|
+
detail=str(exc),
|
|
298
|
+
) from exc
|
|
299
|
+
optional_dependencies = payload.get("project", {}).get("optional-dependencies", {})
|
|
300
|
+
if not isinstance(optional_dependencies, dict):
|
|
301
|
+
return frozenset()
|
|
302
|
+
return frozenset(str(name).strip() for name in optional_dependencies if str(name).strip())
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _runtime_target_has_contrib_checkout(target: SyncTarget) -> bool:
|
|
306
|
+
path = target.path.resolve()
|
|
307
|
+
candidates = (
|
|
308
|
+
resolve_contrib_source_root(path),
|
|
309
|
+
resolve_contrib_source_root(path.parent),
|
|
310
|
+
)
|
|
311
|
+
return any(candidate.is_dir() for candidate in candidates)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
@dataclass(frozen=True, slots=True)
|
|
315
|
+
class _PreparedSyncExecution:
|
|
316
|
+
command: tuple[str, ...]
|
|
317
|
+
cwd: Path
|
|
318
|
+
env: dict[str, str]
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
@contextmanager
|
|
322
|
+
def _prepare_sync_execution(
|
|
323
|
+
*,
|
|
324
|
+
project_dir: Path,
|
|
325
|
+
target: SyncTarget,
|
|
326
|
+
command: list[str],
|
|
327
|
+
uv_binary: str,
|
|
328
|
+
action: str,
|
|
329
|
+
):
|
|
330
|
+
env = os.environ.copy()
|
|
331
|
+
prepared_command = list(command)
|
|
332
|
+
if target.kind != "project" or _project_virtualenv_exists(project_dir, target):
|
|
333
|
+
venv_path = _ensure_target_virtualenv(target.virtualenv_path, uv_binary=uv_binary, action=action)
|
|
334
|
+
env["VIRTUAL_ENV"] = str(venv_path)
|
|
335
|
+
prepared_command.append("--active")
|
|
336
|
+
yield _PreparedSyncExecution(
|
|
337
|
+
command=tuple(prepared_command),
|
|
338
|
+
cwd=target.path,
|
|
339
|
+
env=env,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _project_virtualenv_exists(project_dir: Path, target: SyncTarget) -> bool:
|
|
344
|
+
if target.kind != "project":
|
|
345
|
+
return False
|
|
346
|
+
if target.path.resolve() != project_dir.resolve():
|
|
347
|
+
return False
|
|
348
|
+
return _is_valid_virtualenv(project_dir / ".venv")
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def _discover_truth_source_root(target_path: Path) -> Path | None:
|
|
352
|
+
for candidate in (target_path.resolve(), *target_path.resolve().parents):
|
|
353
|
+
if candidate.name == ".sources":
|
|
354
|
+
if all(
|
|
355
|
+
_resolve_truth_source_repo_root(candidate, repo_name) is not None
|
|
356
|
+
for repo_name in OFFICIAL_TRUTH_SOURCE_REPO_NAMES
|
|
357
|
+
):
|
|
358
|
+
return candidate
|
|
359
|
+
return None
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _resolve_truth_source_repo_root(sources_root: Path, repo_name: str) -> Path | None:
|
|
363
|
+
exact = sources_root / repo_name
|
|
364
|
+
if exact.exists():
|
|
365
|
+
return exact.resolve()
|
|
366
|
+
return None
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _prune_generated_source_artifacts_for_target(*, project_dir: Path, target: SyncTarget) -> None:
|
|
370
|
+
cleanup_roots: list[Path] = []
|
|
371
|
+
direct_sources_root = _discover_truth_source_root(target.path)
|
|
372
|
+
if direct_sources_root is not None:
|
|
373
|
+
cleanup_roots.append(target.path.resolve())
|
|
374
|
+
elif target.kind == "project":
|
|
375
|
+
host_sources_root = _discover_truth_source_root(resolve_sources_root(project_dir))
|
|
376
|
+
if host_sources_root is not None:
|
|
377
|
+
for repo_name in OFFICIAL_TRUTH_SOURCE_REPO_NAMES:
|
|
378
|
+
repo_root = _resolve_truth_source_repo_root(host_sources_root, repo_name)
|
|
379
|
+
if repo_root is not None:
|
|
380
|
+
cleanup_roots.append(repo_root.resolve())
|
|
381
|
+
seen: set[Path] = set()
|
|
382
|
+
for root in cleanup_roots:
|
|
383
|
+
resolved = root.resolve()
|
|
384
|
+
if resolved in seen or not resolved.is_dir():
|
|
385
|
+
continue
|
|
386
|
+
seen.add(resolved)
|
|
387
|
+
_prune_generated_source_artifacts(resolved)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def _prune_generated_source_artifacts(root: Path) -> tuple[Path, ...]:
|
|
391
|
+
removed: list[Path] = []
|
|
392
|
+
seen: set[Path] = set()
|
|
393
|
+
for pattern in _GENERATED_SOURCE_ARTIFACT_DIR_PATTERNS:
|
|
394
|
+
for candidate in sorted(root.rglob(pattern)):
|
|
395
|
+
if not _is_generated_source_artifact_dir(candidate):
|
|
396
|
+
continue
|
|
397
|
+
if candidate.is_symlink():
|
|
398
|
+
link_path = candidate.absolute()
|
|
399
|
+
if link_path in seen:
|
|
400
|
+
continue
|
|
401
|
+
seen.add(link_path)
|
|
402
|
+
candidate.unlink(missing_ok=True)
|
|
403
|
+
removed.append(link_path)
|
|
404
|
+
continue
|
|
405
|
+
if not candidate.is_dir():
|
|
406
|
+
continue
|
|
407
|
+
resolved = candidate.resolve()
|
|
408
|
+
if resolved in seen or resolved == root:
|
|
409
|
+
continue
|
|
410
|
+
seen.add(resolved)
|
|
411
|
+
shutil.rmtree(resolved, ignore_errors=True)
|
|
412
|
+
removed.append(resolved)
|
|
413
|
+
return tuple(removed)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def _is_generated_source_artifact_dir(path: Path) -> bool:
|
|
417
|
+
if not any(fnmatchcase(path.name, pattern) for pattern in _GENERATED_SOURCE_ARTIFACT_DIR_PATTERNS):
|
|
418
|
+
return False
|
|
419
|
+
if path.name == "dist":
|
|
420
|
+
return (path.parent / "pyproject.toml").is_file()
|
|
421
|
+
return True
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _prune_stale_editable_artifacts(
|
|
425
|
+
venv_path: Path,
|
|
426
|
+
*,
|
|
427
|
+
allowed_editable_paths: tuple[Path, ...] = (),
|
|
428
|
+
) -> tuple[Path, ...]:
|
|
429
|
+
site_packages_dirs = _iter_site_packages_dirs(venv_path)
|
|
430
|
+
if not site_packages_dirs:
|
|
431
|
+
return ()
|
|
432
|
+
stale: set[Path] = set()
|
|
433
|
+
stale_finder_modules: set[str] = set()
|
|
434
|
+
finder_files: dict[str, Path] = {}
|
|
435
|
+
|
|
436
|
+
for site_packages in site_packages_dirs:
|
|
437
|
+
for candidate in site_packages.iterdir():
|
|
438
|
+
if not candidate.is_file():
|
|
439
|
+
continue
|
|
440
|
+
if candidate.suffix not in {".pth", ".py"}:
|
|
441
|
+
continue
|
|
442
|
+
text = candidate.read_text(encoding="utf-8", errors="ignore")
|
|
443
|
+
if candidate.name.endswith("_finder.py"):
|
|
444
|
+
finder_files[candidate.stem] = candidate
|
|
445
|
+
if _editable_artifact_is_stale(candidate, text, allowed_editable_paths=allowed_editable_paths):
|
|
446
|
+
stale.add(candidate)
|
|
447
|
+
if candidate.name.endswith("_finder.py"):
|
|
448
|
+
stale_finder_modules.add(candidate.stem)
|
|
449
|
+
|
|
450
|
+
for site_packages in site_packages_dirs:
|
|
451
|
+
for candidate in site_packages.iterdir():
|
|
452
|
+
if not candidate.is_file() or candidate.suffix != ".pth":
|
|
453
|
+
continue
|
|
454
|
+
if candidate in stale:
|
|
455
|
+
continue
|
|
456
|
+
text = candidate.read_text(encoding="utf-8", errors="ignore")
|
|
457
|
+
imported_finder = _imported_editable_finder_module(text)
|
|
458
|
+
if imported_finder is None:
|
|
459
|
+
continue
|
|
460
|
+
finder_path = finder_files.get(imported_finder)
|
|
461
|
+
if imported_finder in stale_finder_modules or finder_path is None or not finder_path.exists():
|
|
462
|
+
stale.add(candidate)
|
|
463
|
+
|
|
464
|
+
for candidate in sorted(stale):
|
|
465
|
+
candidate.unlink(missing_ok=True)
|
|
466
|
+
return tuple(sorted(stale))
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def _find_corrupt_installed_distributions(venv_path: Path) -> tuple[str, ...]:
|
|
470
|
+
broken: list[str] = []
|
|
471
|
+
for site_packages in _iter_site_packages_dirs(venv_path):
|
|
472
|
+
for dist_info in sorted(site_packages.glob("*.dist-info")):
|
|
473
|
+
record_path = dist_info / "RECORD"
|
|
474
|
+
if not record_path.is_file():
|
|
475
|
+
continue
|
|
476
|
+
if not _record_has_missing_site_packages_payload(record_path, site_packages):
|
|
477
|
+
continue
|
|
478
|
+
name = _read_distribution_name_from_metadata(dist_info / "METADATA")
|
|
479
|
+
if not name:
|
|
480
|
+
name = _distribution_name_from_dist_info_dir(dist_info)
|
|
481
|
+
if name and name not in broken:
|
|
482
|
+
broken.append(name)
|
|
483
|
+
return tuple(broken)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def _record_has_missing_site_packages_payload(record_path: Path, site_packages: Path) -> bool:
|
|
487
|
+
try:
|
|
488
|
+
rows = csv.reader(record_path.read_text(encoding="utf-8").splitlines())
|
|
489
|
+
except Exception:
|
|
490
|
+
return False
|
|
491
|
+
for row in rows:
|
|
492
|
+
if not row:
|
|
493
|
+
continue
|
|
494
|
+
rel_path = str(row[0]).strip()
|
|
495
|
+
if not rel_path or rel_path.startswith(".."):
|
|
496
|
+
continue
|
|
497
|
+
normalized = rel_path.replace("\\", "/")
|
|
498
|
+
if normalized.endswith((".pyc", ".pyo")) or "/__pycache__/" in normalized:
|
|
499
|
+
continue
|
|
500
|
+
if not (site_packages / rel_path).exists():
|
|
501
|
+
return True
|
|
502
|
+
return False
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def _read_distribution_name_from_metadata(metadata_path: Path) -> str | None:
|
|
506
|
+
try:
|
|
507
|
+
for line in metadata_path.read_text(encoding="utf-8").splitlines():
|
|
508
|
+
if line.startswith("Name: "):
|
|
509
|
+
value = line.split(":", 1)[1].strip()
|
|
510
|
+
if value:
|
|
511
|
+
return value
|
|
512
|
+
except Exception:
|
|
513
|
+
return None
|
|
514
|
+
return None
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
def _distribution_name_from_dist_info_dir(dist_info_dir: Path) -> str | None:
|
|
518
|
+
name = dist_info_dir.name
|
|
519
|
+
if not name.endswith(".dist-info"):
|
|
520
|
+
return None
|
|
521
|
+
stem = name[: -len(".dist-info")]
|
|
522
|
+
if "-" not in stem:
|
|
523
|
+
return stem.replace("_", "-")
|
|
524
|
+
distribution, _version = stem.rsplit("-", 1)
|
|
525
|
+
distribution = distribution.strip().replace("_", "-")
|
|
526
|
+
return distribution or None
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def _iter_site_packages_dirs(venv_path: Path) -> tuple[Path, ...]:
|
|
530
|
+
if not venv_path.is_dir():
|
|
531
|
+
return ()
|
|
532
|
+
discovered: list[Path] = []
|
|
533
|
+
for parent in (venv_path / "lib", venv_path / "Lib"):
|
|
534
|
+
if not parent.is_dir():
|
|
535
|
+
continue
|
|
536
|
+
for candidate in sorted(parent.glob("python*/site-packages")):
|
|
537
|
+
if candidate.is_dir():
|
|
538
|
+
discovered.append(candidate)
|
|
539
|
+
scripts_candidate = venv_path / "Lib" / "site-packages"
|
|
540
|
+
if scripts_candidate.is_dir():
|
|
541
|
+
discovered.append(scripts_candidate)
|
|
542
|
+
seen: set[Path] = set()
|
|
543
|
+
ordered: list[Path] = []
|
|
544
|
+
for candidate in discovered:
|
|
545
|
+
resolved = candidate.resolve()
|
|
546
|
+
if resolved in seen:
|
|
547
|
+
continue
|
|
548
|
+
seen.add(resolved)
|
|
549
|
+
ordered.append(resolved)
|
|
550
|
+
return tuple(ordered)
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def _editable_artifact_is_stale(
|
|
554
|
+
path: Path,
|
|
555
|
+
text: str,
|
|
556
|
+
*,
|
|
557
|
+
allowed_editable_paths: tuple[Path, ...] = (),
|
|
558
|
+
) -> bool:
|
|
559
|
+
if ".pz-sync-" in text:
|
|
560
|
+
return True
|
|
561
|
+
for raw_path in _quoted_absolute_paths(text):
|
|
562
|
+
if ".pz-sync-" in raw_path or not Path(raw_path).exists():
|
|
563
|
+
return True
|
|
564
|
+
if allowed_editable_paths and _is_editable_artifact(path) and not _path_is_allowed_editable_source(
|
|
565
|
+
raw_path,
|
|
566
|
+
allowed_editable_paths,
|
|
567
|
+
):
|
|
568
|
+
return True
|
|
569
|
+
if path.suffix == ".pth":
|
|
570
|
+
stripped = text.strip()
|
|
571
|
+
if stripped.startswith("/") and "\n" not in stripped and not Path(stripped).exists():
|
|
572
|
+
return True
|
|
573
|
+
if (
|
|
574
|
+
stripped.startswith("/")
|
|
575
|
+
and "\n" not in stripped
|
|
576
|
+
and allowed_editable_paths
|
|
577
|
+
and not _path_is_allowed_editable_source(stripped, allowed_editable_paths)
|
|
578
|
+
):
|
|
579
|
+
return True
|
|
580
|
+
return False
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
def _is_editable_artifact(path: Path) -> bool:
|
|
584
|
+
return path.suffix == ".pth" or path.name.startswith("__editable__") or path.name.endswith("_finder.py")
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def _path_is_allowed_editable_source(raw_path: str, allowed_editable_paths: tuple[Path, ...]) -> bool:
|
|
588
|
+
try:
|
|
589
|
+
candidate = Path(raw_path).expanduser().resolve()
|
|
590
|
+
except OSError:
|
|
591
|
+
candidate = Path(raw_path).expanduser().absolute()
|
|
592
|
+
for allowed in allowed_editable_paths:
|
|
593
|
+
try:
|
|
594
|
+
normalized = allowed.expanduser().resolve()
|
|
595
|
+
except OSError:
|
|
596
|
+
normalized = allowed.expanduser().absolute()
|
|
597
|
+
if candidate == normalized:
|
|
598
|
+
return True
|
|
599
|
+
try:
|
|
600
|
+
if candidate.is_relative_to(normalized):
|
|
601
|
+
return True
|
|
602
|
+
except ValueError:
|
|
603
|
+
continue
|
|
604
|
+
return False
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def _canonicalize_editable_artifacts(
|
|
608
|
+
venv_path: Path,
|
|
609
|
+
*,
|
|
610
|
+
editable_source_paths: tuple[_EditableSourcePath, ...],
|
|
611
|
+
) -> tuple[Path, ...]:
|
|
612
|
+
if not editable_source_paths:
|
|
613
|
+
return ()
|
|
614
|
+
changed: list[Path] = []
|
|
615
|
+
for site_packages in _iter_site_packages_dirs(venv_path):
|
|
616
|
+
for candidate in site_packages.iterdir():
|
|
617
|
+
if not candidate.is_file() or candidate.suffix not in {".pth", ".py"}:
|
|
618
|
+
continue
|
|
619
|
+
if not _is_editable_artifact(candidate):
|
|
620
|
+
continue
|
|
621
|
+
text = candidate.read_text(encoding="utf-8", errors="ignore")
|
|
622
|
+
rewritten = _canonicalize_editable_artifact_text(candidate, text, editable_source_paths)
|
|
623
|
+
if rewritten == text:
|
|
624
|
+
continue
|
|
625
|
+
candidate.write_text(rewritten, encoding="utf-8")
|
|
626
|
+
changed.append(candidate)
|
|
627
|
+
return tuple(changed)
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def _canonicalize_editable_artifact_text(
|
|
631
|
+
path: Path,
|
|
632
|
+
text: str,
|
|
633
|
+
editable_source_paths: tuple[_EditableSourcePath, ...],
|
|
634
|
+
) -> str:
|
|
635
|
+
rewritten = text
|
|
636
|
+
for raw_path in _quoted_absolute_paths(text):
|
|
637
|
+
preferred = _preferred_editable_path(raw_path, editable_source_paths)
|
|
638
|
+
if preferred is None:
|
|
639
|
+
continue
|
|
640
|
+
rewritten = rewritten.replace(raw_path, preferred.as_posix())
|
|
641
|
+
if path.suffix == ".pth":
|
|
642
|
+
stripped = rewritten.strip()
|
|
643
|
+
if stripped.startswith("/") and "\n" not in stripped:
|
|
644
|
+
preferred = _preferred_editable_path(stripped, editable_source_paths)
|
|
645
|
+
if preferred is not None:
|
|
646
|
+
rewritten = preferred.as_posix()
|
|
647
|
+
if text.endswith("\n"):
|
|
648
|
+
rewritten += "\n"
|
|
649
|
+
return rewritten
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def _preferred_editable_path(raw_path: str, editable_source_paths: tuple[_EditableSourcePath, ...]) -> Path | None:
|
|
653
|
+
try:
|
|
654
|
+
candidate = Path(raw_path).expanduser().resolve()
|
|
655
|
+
except OSError:
|
|
656
|
+
candidate = Path(raw_path).expanduser().absolute()
|
|
657
|
+
for source in editable_source_paths:
|
|
658
|
+
try:
|
|
659
|
+
if candidate == source.resolved:
|
|
660
|
+
return source.preferred
|
|
661
|
+
if candidate.is_relative_to(source.resolved):
|
|
662
|
+
return source.preferred / candidate.relative_to(source.resolved)
|
|
663
|
+
except ValueError:
|
|
664
|
+
continue
|
|
665
|
+
return None
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
def _editable_source_paths_for_target(target_path: Path) -> tuple[_EditableSourcePath, ...]:
|
|
669
|
+
target_root = target_path.expanduser().absolute()
|
|
670
|
+
roots: list[Path] = [target_root]
|
|
671
|
+
roots.extend(_collect_local_path_dependency_preferred_roots(target_root))
|
|
672
|
+
seen: set[Path] = set()
|
|
673
|
+
ordered: list[_EditableSourcePath] = []
|
|
674
|
+
for root in roots:
|
|
675
|
+
try:
|
|
676
|
+
resolved = root.resolve()
|
|
677
|
+
except OSError:
|
|
678
|
+
resolved = root.absolute()
|
|
679
|
+
if resolved in seen:
|
|
680
|
+
continue
|
|
681
|
+
seen.add(resolved)
|
|
682
|
+
ordered.append(_EditableSourcePath(resolved=resolved, preferred=root.absolute()))
|
|
683
|
+
return tuple(ordered)
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
def _quoted_absolute_paths(text: str) -> tuple[str, ...]:
|
|
687
|
+
return tuple(match.group(1) for match in _QUOTED_ABS_PATH_RE.finditer(text or ""))
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
def _imported_editable_finder_module(text: str) -> str | None:
|
|
691
|
+
match = _EDITABLE_IMPORT_RE.match(text or "")
|
|
692
|
+
if match is None:
|
|
693
|
+
return None
|
|
694
|
+
return str(match.group(1)).strip() or None
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
def _collect_local_path_dependency_preferred_roots(target_root: Path) -> tuple[Path, ...]:
|
|
698
|
+
pyproject_path = target_root / "pyproject.toml"
|
|
699
|
+
if not pyproject_path.is_file():
|
|
700
|
+
return ()
|
|
701
|
+
try:
|
|
702
|
+
document = tomllib.loads(pyproject_path.read_text(encoding="utf-8"))
|
|
703
|
+
except Exception:
|
|
704
|
+
return ()
|
|
705
|
+
tool = document.get("tool")
|
|
706
|
+
if not isinstance(tool, dict):
|
|
707
|
+
return ()
|
|
708
|
+
uv = tool.get("uv")
|
|
709
|
+
if not isinstance(uv, dict):
|
|
710
|
+
return ()
|
|
711
|
+
sources = uv.get("sources")
|
|
712
|
+
if not isinstance(sources, dict):
|
|
713
|
+
return ()
|
|
714
|
+
roots: list[Path] = []
|
|
715
|
+
for source_meta in sources.values():
|
|
716
|
+
if not isinstance(source_meta, dict):
|
|
717
|
+
continue
|
|
718
|
+
raw_path = str(source_meta.get("path") or "").strip()
|
|
719
|
+
if not raw_path:
|
|
720
|
+
continue
|
|
721
|
+
path = Path(raw_path)
|
|
722
|
+
if path.is_absolute():
|
|
723
|
+
preferred = path.absolute()
|
|
724
|
+
else:
|
|
725
|
+
preferred = (target_root / path).absolute()
|
|
726
|
+
if not preferred.exists():
|
|
727
|
+
continue
|
|
728
|
+
roots.append(preferred)
|
|
729
|
+
return tuple(roots)
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
def _source_project_root(target_path: Path) -> Path | None:
|
|
733
|
+
absolute = target_path.expanduser().absolute()
|
|
734
|
+
for candidate in (absolute, *absolute.parents):
|
|
735
|
+
if candidate.name == ".sources" and candidate.parent.name == "apps":
|
|
736
|
+
return candidate.parent.parent.absolute()
|
|
737
|
+
return None
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
def _target_virtualenv_path(target_path: Path) -> Path:
|
|
741
|
+
project_root = _source_project_root(target_path)
|
|
742
|
+
absolute = target_path.expanduser().absolute()
|
|
743
|
+
if project_root is None:
|
|
744
|
+
return absolute.resolve() / ".venv"
|
|
745
|
+
try:
|
|
746
|
+
relative = absolute.relative_to(project_root)
|
|
747
|
+
except ValueError:
|
|
748
|
+
return absolute.resolve() / ".venv"
|
|
749
|
+
return project_root / ".runtime" / "source-venvs" / relative / ".venv"
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
def _ensure_target_virtualenv(target_path: Path, *, uv_binary: str, action: str) -> Path:
|
|
753
|
+
venv_path = _target_virtualenv_path(target_path)
|
|
754
|
+
if _is_valid_virtualenv(venv_path):
|
|
755
|
+
return venv_path.resolve()
|
|
756
|
+
venv_path.parent.mkdir(parents=True, exist_ok=True)
|
|
757
|
+
completed = subprocess.run(
|
|
758
|
+
[uv_binary, "venv", str(venv_path)],
|
|
759
|
+
cwd=str(target_path),
|
|
760
|
+
check=False,
|
|
761
|
+
capture_output=True,
|
|
762
|
+
text=True,
|
|
763
|
+
env=os.environ.copy(),
|
|
764
|
+
)
|
|
765
|
+
if completed.returncode == 0 and _is_valid_virtualenv(venv_path):
|
|
766
|
+
return venv_path.resolve()
|
|
767
|
+
detail = (completed.stderr or completed.stdout or "failed to create virtual environment").strip()
|
|
768
|
+
raise PzError(
|
|
769
|
+
code="PZ_ENV_SYNC_VENV_FAILED",
|
|
770
|
+
message=f"failed to prepare virtual environment for {target_path}.",
|
|
771
|
+
stage="cli",
|
|
772
|
+
owner=_OWNER,
|
|
773
|
+
action=action,
|
|
774
|
+
detail=detail,
|
|
775
|
+
)
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
def _is_valid_virtualenv(path: Path) -> bool:
|
|
779
|
+
candidates = (
|
|
780
|
+
path / "bin" / "python",
|
|
781
|
+
path / "bin" / "python3",
|
|
782
|
+
path / "Scripts" / "python.exe",
|
|
783
|
+
)
|
|
784
|
+
return any(candidate.is_file() for candidate in candidates)
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
__all__ = ["SyncTarget", "build_sync_target", "collect_sync_targets", "sync_project_targets"]
|