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,1382 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from email.parser import BytesParser
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import re
|
|
10
|
+
import shutil
|
|
11
|
+
import subprocess
|
|
12
|
+
import tempfile
|
|
13
|
+
import tomllib
|
|
14
|
+
import zipfile
|
|
15
|
+
from urllib.error import HTTPError, URLError
|
|
16
|
+
from urllib.request import Request, urlopen
|
|
17
|
+
from urllib.parse import quote, urlsplit, urlunsplit
|
|
18
|
+
|
|
19
|
+
from pz_core.runtime.errors import PzError
|
|
20
|
+
from pz_core.utils import load_jsonc_file
|
|
21
|
+
|
|
22
|
+
from .publish_lock import enforce_publish_lock
|
|
23
|
+
from .publish_git import (
|
|
24
|
+
PublishCommitResult,
|
|
25
|
+
PublishTagResult,
|
|
26
|
+
commit_publish_app,
|
|
27
|
+
preflight_publish_app,
|
|
28
|
+
require_publish_checkout_at_tag,
|
|
29
|
+
require_publish_tag,
|
|
30
|
+
tag_publish_app,
|
|
31
|
+
)
|
|
32
|
+
from .publish_source import resolve_publish_source
|
|
33
|
+
from .project_state import normalize_formal_app_id, resolve_project_config_path
|
|
34
|
+
from .workspace import resolve_workspace_package
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True, slots=True)
|
|
38
|
+
class PublishTarget:
|
|
39
|
+
name: str
|
|
40
|
+
index: str | None = None
|
|
41
|
+
publish_url: str | None = None
|
|
42
|
+
check_url: str | None = None
|
|
43
|
+
trusted_publishing: str | None = None
|
|
44
|
+
token_env: str | None = None
|
|
45
|
+
username_env: str | None = None
|
|
46
|
+
password_env: str | None = None
|
|
47
|
+
install_username_env: str | None = None
|
|
48
|
+
install_password_env: str | None = None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True, slots=True)
|
|
52
|
+
class InstallTarget:
|
|
53
|
+
name: str
|
|
54
|
+
index_url: str | None = None
|
|
55
|
+
username_env: str | None = None
|
|
56
|
+
password_env: str | None = None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(frozen=True, slots=True)
|
|
60
|
+
class PublishResult:
|
|
61
|
+
app_id: str
|
|
62
|
+
version: str
|
|
63
|
+
package_root: Path
|
|
64
|
+
dist_dir: Path
|
|
65
|
+
target: PublishTarget
|
|
66
|
+
dry_run: bool
|
|
67
|
+
commit: str | None = None
|
|
68
|
+
tag: str | None = None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass(frozen=True, slots=True)
|
|
72
|
+
class UnpublishResult:
|
|
73
|
+
app_id: str
|
|
74
|
+
distribution_name: str
|
|
75
|
+
version: str
|
|
76
|
+
package_root: Path
|
|
77
|
+
target: PublishTarget
|
|
78
|
+
project_url: str
|
|
79
|
+
removed_versions: tuple[str, ...]
|
|
80
|
+
dry_run: bool
|
|
81
|
+
force: bool
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def normalize_publish_app_id(
|
|
85
|
+
project_dir: Path,
|
|
86
|
+
app_id: str,
|
|
87
|
+
*,
|
|
88
|
+
action: str,
|
|
89
|
+
) -> str:
|
|
90
|
+
del project_dir
|
|
91
|
+
workspace_package = resolve_workspace_package(str(app_id))
|
|
92
|
+
if workspace_package is not None:
|
|
93
|
+
return workspace_package.app_id
|
|
94
|
+
if not isinstance(app_id, str) or not app_id.strip():
|
|
95
|
+
raise PzError(
|
|
96
|
+
code="PZ_PUBLISH_APP_ID_INVALID",
|
|
97
|
+
message="publish app id must be a non-empty string.",
|
|
98
|
+
stage="cli",
|
|
99
|
+
owner="pz_core.authoring.release",
|
|
100
|
+
action=action,
|
|
101
|
+
)
|
|
102
|
+
return normalize_formal_app_id(
|
|
103
|
+
app_id.strip().replace("-", "_"),
|
|
104
|
+
action=action,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def resolve_publish_package_root(
|
|
109
|
+
project_dir: Path,
|
|
110
|
+
app_id: str,
|
|
111
|
+
*,
|
|
112
|
+
action: str,
|
|
113
|
+
) -> Path:
|
|
114
|
+
normalized_app_id = normalize_publish_app_id(
|
|
115
|
+
project_dir,
|
|
116
|
+
app_id,
|
|
117
|
+
action=action,
|
|
118
|
+
)
|
|
119
|
+
workspace_package = resolve_workspace_package(normalized_app_id)
|
|
120
|
+
if workspace_package is not None and workspace_package.repo.is_dir():
|
|
121
|
+
return workspace_package.repo.resolve()
|
|
122
|
+
|
|
123
|
+
from pz_core.assembly.app_discovery import iter_visible_app_projections
|
|
124
|
+
|
|
125
|
+
for projection in iter_visible_app_projections(project_dir):
|
|
126
|
+
if projection.app_id == normalized_app_id:
|
|
127
|
+
return projection.source_root
|
|
128
|
+
|
|
129
|
+
requested_distribution = normalized_app_id.replace("_", "-")
|
|
130
|
+
if _read_project_distribution_name(project_dir) == requested_distribution:
|
|
131
|
+
return project_dir.resolve()
|
|
132
|
+
raise PzError(
|
|
133
|
+
code="PZ_PUBLISH_SOURCE_MISSING",
|
|
134
|
+
message=f"could not resolve package root for {normalized_app_id}.",
|
|
135
|
+
stage="cli",
|
|
136
|
+
owner="pz_core.authoring.release",
|
|
137
|
+
action=action,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _read_project_distribution_name(package_root: Path) -> str | None:
|
|
142
|
+
pyproject = package_root / "pyproject.toml"
|
|
143
|
+
if not pyproject.is_file():
|
|
144
|
+
return None
|
|
145
|
+
try:
|
|
146
|
+
payload = tomllib.loads(pyproject.read_text(encoding="utf-8"))
|
|
147
|
+
except (OSError, UnicodeError, tomllib.TOMLDecodeError):
|
|
148
|
+
return None
|
|
149
|
+
project = payload.get("project")
|
|
150
|
+
if not isinstance(project, dict):
|
|
151
|
+
return None
|
|
152
|
+
name = project.get("name")
|
|
153
|
+
return name.strip() if isinstance(name, str) and name.strip() else None
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _read_dotenv(path: Path, *, action: str) -> dict[str, str]:
|
|
157
|
+
values: dict[str, str] = {}
|
|
158
|
+
if not path.is_file():
|
|
159
|
+
return values
|
|
160
|
+
try:
|
|
161
|
+
lines = path.read_text(encoding="utf-8").splitlines()
|
|
162
|
+
except Exception as exc:
|
|
163
|
+
raise PzError(
|
|
164
|
+
code="PZ_SCAFFOLD_DOTENV_INVALID",
|
|
165
|
+
message=f"failed to read env file: {path}",
|
|
166
|
+
stage="cli",
|
|
167
|
+
owner="pz_core.authoring",
|
|
168
|
+
action=action,
|
|
169
|
+
detail=str(exc),
|
|
170
|
+
) from exc
|
|
171
|
+
for line_number, raw_line in enumerate(lines, start=1):
|
|
172
|
+
line = raw_line.strip()
|
|
173
|
+
if not line or line.startswith("#"):
|
|
174
|
+
continue
|
|
175
|
+
if "=" not in raw_line:
|
|
176
|
+
raise PzError(
|
|
177
|
+
code="PZ_SCAFFOLD_DOTENV_INVALID",
|
|
178
|
+
message=f"invalid env entry at line {line_number}: missing '=' delimiter.",
|
|
179
|
+
stage="cli",
|
|
180
|
+
owner="pz_core.authoring",
|
|
181
|
+
action=action,
|
|
182
|
+
detail=str(path),
|
|
183
|
+
)
|
|
184
|
+
key, raw_value = raw_line.split("=", 1)
|
|
185
|
+
normalized_key = key.strip()
|
|
186
|
+
if not normalized_key:
|
|
187
|
+
raise PzError(
|
|
188
|
+
code="PZ_SCAFFOLD_DOTENV_INVALID",
|
|
189
|
+
message=f"invalid env entry at line {line_number}: key must be non-empty.",
|
|
190
|
+
stage="cli",
|
|
191
|
+
owner="pz_core.authoring",
|
|
192
|
+
action=action,
|
|
193
|
+
detail=str(path),
|
|
194
|
+
)
|
|
195
|
+
value = raw_value.strip()
|
|
196
|
+
if value[:1] in {"'", '"'}:
|
|
197
|
+
quote_char = value[0]
|
|
198
|
+
if len(value) < 2 or not value.endswith(quote_char):
|
|
199
|
+
raise PzError(
|
|
200
|
+
code="PZ_SCAFFOLD_DOTENV_INVALID",
|
|
201
|
+
message=f"invalid env entry at line {line_number}: unterminated quoted value.",
|
|
202
|
+
stage="cli",
|
|
203
|
+
owner="pz_core.authoring",
|
|
204
|
+
action=action,
|
|
205
|
+
detail=str(path),
|
|
206
|
+
)
|
|
207
|
+
value = value[1:-1]
|
|
208
|
+
values[normalized_key] = value
|
|
209
|
+
return values
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def build_authoring_env(*roots: Path, action: str) -> dict[str, str]:
|
|
213
|
+
env = dict(os.environ)
|
|
214
|
+
seen: set[Path] = set()
|
|
215
|
+
for root in roots:
|
|
216
|
+
candidate_root = Path(root).expanduser().resolve()
|
|
217
|
+
if candidate_root in seen:
|
|
218
|
+
continue
|
|
219
|
+
seen.add(candidate_root)
|
|
220
|
+
for key, value in _read_dotenv(candidate_root / ".env", action=action).items():
|
|
221
|
+
env.setdefault(key, value)
|
|
222
|
+
return env
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _is_devpi_target(target: PublishTarget, *urls: str | None) -> bool:
|
|
226
|
+
target_name = str(target.name or "").strip().lower()
|
|
227
|
+
if target_name.startswith("devpi_") or target_name == "devpi":
|
|
228
|
+
return True
|
|
229
|
+
for url in urls:
|
|
230
|
+
if not isinstance(url, str) or not url.strip():
|
|
231
|
+
continue
|
|
232
|
+
path = str(urlsplit(url.strip()).path or "").strip().lower()
|
|
233
|
+
if path.endswith("/+simple/") or path.endswith("/+simple"):
|
|
234
|
+
return True
|
|
235
|
+
return False
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _inject_basic_auth(url: str, *, username: str, password: str) -> str:
|
|
239
|
+
parsed = urlsplit(url)
|
|
240
|
+
hostname = str(parsed.hostname or "").strip()
|
|
241
|
+
if not hostname:
|
|
242
|
+
return url
|
|
243
|
+
userinfo = f"{quote(username, safe='')}:{quote(password, safe='')}"
|
|
244
|
+
netloc = f"{userinfo}@{hostname}"
|
|
245
|
+
if parsed.port is not None:
|
|
246
|
+
netloc += f":{parsed.port}"
|
|
247
|
+
return urlunsplit(
|
|
248
|
+
(parsed.scheme, netloc, parsed.path, parsed.query, parsed.fragment)
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _require_named_env(
|
|
253
|
+
env: dict[str, str], env_name: str, *, code: str, message: str, action: str
|
|
254
|
+
) -> str:
|
|
255
|
+
value = str(env.get(env_name) or "").strip()
|
|
256
|
+
if value:
|
|
257
|
+
return value
|
|
258
|
+
raise PzError(
|
|
259
|
+
code=code,
|
|
260
|
+
message=message,
|
|
261
|
+
stage="cli",
|
|
262
|
+
owner="pz_core.authoring",
|
|
263
|
+
action=action,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _clean_build_artifacts(package_root: Path) -> None:
|
|
268
|
+
for relative_path in ("build", "dist"):
|
|
269
|
+
candidate = package_root / relative_path
|
|
270
|
+
if candidate.exists():
|
|
271
|
+
shutil.rmtree(candidate)
|
|
272
|
+
for candidate in package_root.glob("*.egg-info"):
|
|
273
|
+
if candidate.is_dir():
|
|
274
|
+
shutil.rmtree(candidate)
|
|
275
|
+
elif candidate.exists():
|
|
276
|
+
candidate.unlink()
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _validate_optional_string(
|
|
280
|
+
raw_value: object, *, field: str, action: str
|
|
281
|
+
) -> str | None:
|
|
282
|
+
if raw_value is None:
|
|
283
|
+
return None
|
|
284
|
+
if not isinstance(raw_value, str) or not raw_value.strip():
|
|
285
|
+
raise PzError(
|
|
286
|
+
code="PZ_SCAFFOLD_PUBLISH_CONFIG_INVALID",
|
|
287
|
+
message=f"publish config field `{field}` must be a non-empty string when provided.",
|
|
288
|
+
stage="cli",
|
|
289
|
+
owner="pz_core.authoring",
|
|
290
|
+
action=action,
|
|
291
|
+
)
|
|
292
|
+
return raw_value.strip()
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _load_project_payload(project_dir: Path, *, action: str) -> dict[str, object]:
|
|
296
|
+
if not project_dir.exists() or not project_dir.is_dir():
|
|
297
|
+
raise PzError(
|
|
298
|
+
code="PZ_SCAFFOLD_PROJECT_DIR_INVALID",
|
|
299
|
+
message=f"project directory must exist: {project_dir}",
|
|
300
|
+
stage="cli",
|
|
301
|
+
owner="pz_core.authoring",
|
|
302
|
+
action=action,
|
|
303
|
+
)
|
|
304
|
+
payload = load_jsonc_file(
|
|
305
|
+
resolve_project_config_path(project_dir),
|
|
306
|
+
code="PZ_SCAFFOLD_PROJECT_CONFIG_INVALID",
|
|
307
|
+
owner="pz_core.authoring",
|
|
308
|
+
action=action,
|
|
309
|
+
)
|
|
310
|
+
if payload and not isinstance(payload, dict):
|
|
311
|
+
raise PzError(
|
|
312
|
+
code="PZ_SCAFFOLD_PROJECT_CONFIG_INVALID",
|
|
313
|
+
message="project config must decode to an object.",
|
|
314
|
+
stage="cli",
|
|
315
|
+
owner="pz_core.authoring",
|
|
316
|
+
action=action,
|
|
317
|
+
)
|
|
318
|
+
return payload or {}
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _load_source_root_pyproject_payload(
|
|
322
|
+
source_root: Path, *, action: str
|
|
323
|
+
) -> dict[str, object]:
|
|
324
|
+
pyproject_path = source_root / "pyproject.toml"
|
|
325
|
+
if not pyproject_path.is_file():
|
|
326
|
+
return {}
|
|
327
|
+
try:
|
|
328
|
+
payload = tomllib.loads(pyproject_path.read_text(encoding="utf-8"))
|
|
329
|
+
except Exception as exc:
|
|
330
|
+
raise PzError(
|
|
331
|
+
code="PZ_SCAFFOLD_PUBLISH_CONFIG_INVALID",
|
|
332
|
+
message=f"source-root pyproject.toml is unreadable: {pyproject_path}",
|
|
333
|
+
stage="cli",
|
|
334
|
+
owner="pz_core.authoring",
|
|
335
|
+
action=action,
|
|
336
|
+
detail=str(exc),
|
|
337
|
+
) from exc
|
|
338
|
+
if not isinstance(payload, dict):
|
|
339
|
+
raise PzError(
|
|
340
|
+
code="PZ_SCAFFOLD_PUBLISH_CONFIG_INVALID",
|
|
341
|
+
message=f"source-root pyproject.toml must decode to an object: {pyproject_path}",
|
|
342
|
+
stage="cli",
|
|
343
|
+
owner="pz_core.authoring",
|
|
344
|
+
action=action,
|
|
345
|
+
)
|
|
346
|
+
return payload
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _resolve_pyproject_publish_config(
|
|
350
|
+
source_root: Path,
|
|
351
|
+
*,
|
|
352
|
+
action: str,
|
|
353
|
+
) -> tuple[dict[str, object] | None, str, str]:
|
|
354
|
+
pyproject_path = source_root / "pyproject.toml"
|
|
355
|
+
pyproject_payload = _load_source_root_pyproject_payload(source_root, action=action)
|
|
356
|
+
if not pyproject_path.is_file():
|
|
357
|
+
return None, str(pyproject_path), "tool.pz.publish"
|
|
358
|
+
tool = pyproject_payload.get("tool")
|
|
359
|
+
if tool is None:
|
|
360
|
+
return None, str(pyproject_path), "tool.pz.publish"
|
|
361
|
+
if not isinstance(tool, dict):
|
|
362
|
+
raise PzError(
|
|
363
|
+
code="PZ_SCAFFOLD_PUBLISH_CONFIG_INVALID",
|
|
364
|
+
message="`tool` in source-root pyproject.toml must be an object.",
|
|
365
|
+
stage="cli",
|
|
366
|
+
owner="pz_core.authoring",
|
|
367
|
+
action=action,
|
|
368
|
+
)
|
|
369
|
+
pz_tool = tool.get("pz")
|
|
370
|
+
if pz_tool is None:
|
|
371
|
+
return None, str(pyproject_path), "tool.pz.publish"
|
|
372
|
+
if not isinstance(pz_tool, dict):
|
|
373
|
+
raise PzError(
|
|
374
|
+
code="PZ_SCAFFOLD_PUBLISH_CONFIG_INVALID",
|
|
375
|
+
message="`tool.pz` in source-root pyproject.toml must be an object.",
|
|
376
|
+
stage="cli",
|
|
377
|
+
owner="pz_core.authoring",
|
|
378
|
+
action=action,
|
|
379
|
+
)
|
|
380
|
+
return pz_tool.get("publish"), str(pyproject_path), "tool.pz.publish"
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def _resolve_publish_config(
|
|
384
|
+
project_dir: Path,
|
|
385
|
+
*,
|
|
386
|
+
package_root: Path | None = None,
|
|
387
|
+
workspace_package_root: Path | None = None,
|
|
388
|
+
action: str,
|
|
389
|
+
) -> tuple[dict[str, object] | None, str, str]:
|
|
390
|
+
project_config_path = resolve_project_config_path(project_dir)
|
|
391
|
+
if project_config_path.is_file():
|
|
392
|
+
payload = _load_project_payload(project_dir, action=action)
|
|
393
|
+
publish_config = payload.get("publish")
|
|
394
|
+
if publish_config is not None:
|
|
395
|
+
if not isinstance(publish_config, dict):
|
|
396
|
+
raise PzError(
|
|
397
|
+
code="PZ_SCAFFOLD_PUBLISH_CONFIG_INVALID",
|
|
398
|
+
message="`publish` in project config must be an object.",
|
|
399
|
+
stage="cli",
|
|
400
|
+
owner="pz_core.authoring",
|
|
401
|
+
action=action,
|
|
402
|
+
)
|
|
403
|
+
return publish_config, str(project_config_path), "publish"
|
|
404
|
+
seen: set[Path] = set()
|
|
405
|
+
for search_root in (package_root, workspace_package_root):
|
|
406
|
+
if search_root is None:
|
|
407
|
+
continue
|
|
408
|
+
for candidate in (search_root, *search_root.parents):
|
|
409
|
+
resolved_candidate = candidate.resolve()
|
|
410
|
+
if resolved_candidate in seen:
|
|
411
|
+
continue
|
|
412
|
+
seen.add(resolved_candidate)
|
|
413
|
+
publish_config, config_label, field_prefix = (
|
|
414
|
+
_resolve_pyproject_publish_config(
|
|
415
|
+
resolved_candidate,
|
|
416
|
+
action=action,
|
|
417
|
+
)
|
|
418
|
+
)
|
|
419
|
+
if publish_config is not None:
|
|
420
|
+
return publish_config, config_label, field_prefix
|
|
421
|
+
publish_config, config_label, field_prefix = _resolve_pyproject_publish_config(
|
|
422
|
+
project_dir, action=action
|
|
423
|
+
)
|
|
424
|
+
if publish_config is not None:
|
|
425
|
+
return publish_config, config_label, field_prefix
|
|
426
|
+
return None, str(project_config_path), "publish"
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _load_publish_target(
|
|
430
|
+
project_dir: Path,
|
|
431
|
+
*,
|
|
432
|
+
package_root: Path | None = None,
|
|
433
|
+
workspace_package_root: Path | None = None,
|
|
434
|
+
target_name: str | None,
|
|
435
|
+
action: str,
|
|
436
|
+
) -> PublishTarget:
|
|
437
|
+
publish_config, config_label, field_prefix = _resolve_publish_config(
|
|
438
|
+
project_dir,
|
|
439
|
+
package_root=package_root,
|
|
440
|
+
workspace_package_root=workspace_package_root,
|
|
441
|
+
action=action,
|
|
442
|
+
)
|
|
443
|
+
target = (
|
|
444
|
+
target_name.strip()
|
|
445
|
+
if isinstance(target_name, str) and target_name.strip()
|
|
446
|
+
else None
|
|
447
|
+
)
|
|
448
|
+
if publish_config is None:
|
|
449
|
+
return PublishTarget(name=target or "default")
|
|
450
|
+
if not isinstance(publish_config, dict):
|
|
451
|
+
raise PzError(
|
|
452
|
+
code="PZ_SCAFFOLD_PUBLISH_CONFIG_INVALID",
|
|
453
|
+
message=f"`{field_prefix}` must be an object.",
|
|
454
|
+
stage="cli",
|
|
455
|
+
owner="pz_core.authoring",
|
|
456
|
+
action=action,
|
|
457
|
+
)
|
|
458
|
+
default_target = _validate_optional_string(
|
|
459
|
+
publish_config.get("default_target"),
|
|
460
|
+
field=f"{field_prefix}.default_target",
|
|
461
|
+
action=action,
|
|
462
|
+
)
|
|
463
|
+
resolved_target_name = target or default_target or "default"
|
|
464
|
+
raw_targets = publish_config.get("targets")
|
|
465
|
+
if raw_targets is None:
|
|
466
|
+
return PublishTarget(name=resolved_target_name)
|
|
467
|
+
if not isinstance(raw_targets, dict):
|
|
468
|
+
raise PzError(
|
|
469
|
+
code="PZ_SCAFFOLD_PUBLISH_CONFIG_INVALID",
|
|
470
|
+
message=f"`{field_prefix}.targets` must be an object.",
|
|
471
|
+
stage="cli",
|
|
472
|
+
owner="pz_core.authoring",
|
|
473
|
+
action=action,
|
|
474
|
+
)
|
|
475
|
+
raw_target = raw_targets.get(resolved_target_name)
|
|
476
|
+
if raw_target is None:
|
|
477
|
+
raise PzError(
|
|
478
|
+
code="PZ_SCAFFOLD_PUBLISH_TARGET_UNKNOWN",
|
|
479
|
+
message=(
|
|
480
|
+
f"publish target `{resolved_target_name}` is not defined in "
|
|
481
|
+
f"{config_label}."
|
|
482
|
+
),
|
|
483
|
+
stage="cli",
|
|
484
|
+
owner="pz_core.authoring",
|
|
485
|
+
action=action,
|
|
486
|
+
)
|
|
487
|
+
if not isinstance(raw_target, dict):
|
|
488
|
+
raise PzError(
|
|
489
|
+
code="PZ_SCAFFOLD_PUBLISH_CONFIG_INVALID",
|
|
490
|
+
message=f"`{field_prefix}.targets.{resolved_target_name}` must be an object.",
|
|
491
|
+
stage="cli",
|
|
492
|
+
owner="pz_core.authoring",
|
|
493
|
+
action=action,
|
|
494
|
+
)
|
|
495
|
+
return PublishTarget(
|
|
496
|
+
name=resolved_target_name,
|
|
497
|
+
index=_validate_optional_string(
|
|
498
|
+
raw_target.get("index"),
|
|
499
|
+
field=f"{field_prefix}.targets.{resolved_target_name}.index",
|
|
500
|
+
action=action,
|
|
501
|
+
),
|
|
502
|
+
publish_url=_validate_optional_string(
|
|
503
|
+
raw_target.get("publish_url"),
|
|
504
|
+
field=f"{field_prefix}.targets.{resolved_target_name}.publish_url",
|
|
505
|
+
action=action,
|
|
506
|
+
),
|
|
507
|
+
check_url=_validate_optional_string(
|
|
508
|
+
raw_target.get("check_url"),
|
|
509
|
+
field=f"{field_prefix}.targets.{resolved_target_name}.check_url",
|
|
510
|
+
action=action,
|
|
511
|
+
),
|
|
512
|
+
trusted_publishing=_validate_optional_string(
|
|
513
|
+
raw_target.get("trusted_publishing"),
|
|
514
|
+
field=f"{field_prefix}.targets.{resolved_target_name}.trusted_publishing",
|
|
515
|
+
action=action,
|
|
516
|
+
),
|
|
517
|
+
token_env=_validate_optional_string(
|
|
518
|
+
raw_target.get("token_env"),
|
|
519
|
+
field=f"{field_prefix}.targets.{resolved_target_name}.token_env",
|
|
520
|
+
action=action,
|
|
521
|
+
),
|
|
522
|
+
username_env=_validate_optional_string(
|
|
523
|
+
raw_target.get("username_env"),
|
|
524
|
+
field=f"{field_prefix}.targets.{resolved_target_name}.username_env",
|
|
525
|
+
action=action,
|
|
526
|
+
),
|
|
527
|
+
password_env=_validate_optional_string(
|
|
528
|
+
raw_target.get("password_env"),
|
|
529
|
+
field=f"{field_prefix}.targets.{resolved_target_name}.password_env",
|
|
530
|
+
action=action,
|
|
531
|
+
),
|
|
532
|
+
install_username_env=_validate_optional_string(
|
|
533
|
+
raw_target.get("install_username_env"),
|
|
534
|
+
field=f"{field_prefix}.targets.{resolved_target_name}.install_username_env",
|
|
535
|
+
action=action,
|
|
536
|
+
),
|
|
537
|
+
install_password_env=_validate_optional_string(
|
|
538
|
+
raw_target.get("install_password_env"),
|
|
539
|
+
field=f"{field_prefix}.targets.{resolved_target_name}.install_password_env",
|
|
540
|
+
action=action,
|
|
541
|
+
),
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def load_install_target(
|
|
546
|
+
app_id: str, *, project_dir: Path, action: str
|
|
547
|
+
) -> InstallTarget:
|
|
548
|
+
workspace_package = resolve_workspace_package(str(app_id), anchors=(project_dir,))
|
|
549
|
+
package_root = workspace_package.repo if workspace_package is not None else None
|
|
550
|
+
return load_install_target_for_package_root(
|
|
551
|
+
package_root, project_dir=project_dir, action=action
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
def load_install_target_for_package_root(
|
|
556
|
+
package_root: Path | None,
|
|
557
|
+
*,
|
|
558
|
+
project_dir: Path,
|
|
559
|
+
target_name: str | None = None,
|
|
560
|
+
action: str,
|
|
561
|
+
) -> InstallTarget:
|
|
562
|
+
target = _load_publish_target(
|
|
563
|
+
project_dir,
|
|
564
|
+
package_root=package_root,
|
|
565
|
+
workspace_package_root=package_root,
|
|
566
|
+
target_name=target_name,
|
|
567
|
+
action=action,
|
|
568
|
+
)
|
|
569
|
+
index_url = target.check_url or target.index
|
|
570
|
+
username_env = target.install_username_env
|
|
571
|
+
password_env = target.install_password_env
|
|
572
|
+
if index_url and _is_devpi_target(
|
|
573
|
+
target, index_url, target.check_url, target.publish_url
|
|
574
|
+
):
|
|
575
|
+
username_env = username_env or "DEVPI_INSTALL_USER"
|
|
576
|
+
password_env = password_env or "DEVPI_INSTALL_PASSWORD"
|
|
577
|
+
return InstallTarget(
|
|
578
|
+
name=target.name,
|
|
579
|
+
index_url=index_url,
|
|
580
|
+
username_env=username_env,
|
|
581
|
+
password_env=password_env,
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
def install_env(
|
|
586
|
+
*, target: InstallTarget, project_dir: Path, action: str
|
|
587
|
+
) -> dict[str, str]:
|
|
588
|
+
return install_env_for_targets((target,), project_dir=project_dir, action=action)
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def install_env_for_targets(
|
|
592
|
+
targets: tuple[InstallTarget, ...],
|
|
593
|
+
*,
|
|
594
|
+
project_dir: Path,
|
|
595
|
+
action: str,
|
|
596
|
+
) -> dict[str, str]:
|
|
597
|
+
env = build_authoring_env(project_dir, action=action)
|
|
598
|
+
index_urls = tuple(
|
|
599
|
+
_install_index_url(target=target, env=env, action=action)
|
|
600
|
+
for target in targets
|
|
601
|
+
if target.index_url is not None
|
|
602
|
+
)
|
|
603
|
+
if not index_urls:
|
|
604
|
+
return env
|
|
605
|
+
if len(index_urls) == 1:
|
|
606
|
+
env["UV_DEFAULT_INDEX"] = index_urls[0]
|
|
607
|
+
return env
|
|
608
|
+
env["UV_INDEX"] = " ".join(index_urls[:-1])
|
|
609
|
+
env["UV_DEFAULT_INDEX"] = index_urls[-1]
|
|
610
|
+
return env
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
def _install_index_url(
|
|
614
|
+
*, target: InstallTarget, env: dict[str, str], action: str
|
|
615
|
+
) -> str:
|
|
616
|
+
index_url = str(target.index_url or "").strip()
|
|
617
|
+
if target.username_env is not None or target.password_env is not None:
|
|
618
|
+
if target.username_env is None or target.password_env is None:
|
|
619
|
+
raise PzError(
|
|
620
|
+
code="PZ_SCAFFOLD_INSTALL_TARGET_INVALID",
|
|
621
|
+
message=f"install target `{target.name}` must declare both install username and password env vars.",
|
|
622
|
+
stage="cli",
|
|
623
|
+
owner="pz_core.authoring",
|
|
624
|
+
action=action,
|
|
625
|
+
)
|
|
626
|
+
username = _require_named_env(
|
|
627
|
+
env,
|
|
628
|
+
target.username_env,
|
|
629
|
+
code="PZ_SCAFFOLD_INSTALL_AUTH_MISSING",
|
|
630
|
+
message=f"install target `{target.name}` requires env var `{target.username_env}`.",
|
|
631
|
+
action=action,
|
|
632
|
+
)
|
|
633
|
+
password = _require_named_env(
|
|
634
|
+
env,
|
|
635
|
+
target.password_env,
|
|
636
|
+
code="PZ_SCAFFOLD_INSTALL_AUTH_MISSING",
|
|
637
|
+
message=f"install target `{target.name}` requires env var `{target.password_env}`.",
|
|
638
|
+
action=action,
|
|
639
|
+
)
|
|
640
|
+
index_url = _inject_basic_auth(index_url, username=username, password=password)
|
|
641
|
+
return index_url
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def _publish_env(
|
|
645
|
+
*, target: PublishTarget, project_dir: Path, action: str
|
|
646
|
+
) -> dict[str, str]:
|
|
647
|
+
env = build_authoring_env(project_dir, action=action)
|
|
648
|
+
for source_name, destination_name in (
|
|
649
|
+
(target.token_env, "UV_PUBLISH_TOKEN"),
|
|
650
|
+
(target.username_env, "UV_PUBLISH_USERNAME"),
|
|
651
|
+
(target.password_env, "UV_PUBLISH_PASSWORD"),
|
|
652
|
+
):
|
|
653
|
+
if source_name is None:
|
|
654
|
+
continue
|
|
655
|
+
env[destination_name] = _require_named_env(
|
|
656
|
+
env,
|
|
657
|
+
source_name,
|
|
658
|
+
code="PZ_SCAFFOLD_PUBLISH_AUTH_MISSING",
|
|
659
|
+
message=f"publish target `{target.name}` requires env var `{source_name}`.",
|
|
660
|
+
action=action,
|
|
661
|
+
)
|
|
662
|
+
return env
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
def _publish_argv(
|
|
666
|
+
*, uv_binary: str, target: PublishTarget, dist_dir: Path, dry_run: bool
|
|
667
|
+
) -> list[str]:
|
|
668
|
+
argv = [uv_binary, "publish"]
|
|
669
|
+
if target.index:
|
|
670
|
+
argv.extend(["--index", target.index])
|
|
671
|
+
if target.publish_url:
|
|
672
|
+
argv.extend(["--publish-url", target.publish_url])
|
|
673
|
+
if target.check_url:
|
|
674
|
+
argv.extend(["--check-url", target.check_url])
|
|
675
|
+
if target.trusted_publishing:
|
|
676
|
+
argv.extend(["--trusted-publishing", target.trusted_publishing])
|
|
677
|
+
if dry_run:
|
|
678
|
+
argv.append("--dry-run")
|
|
679
|
+
argv.append(str(dist_dir / "*"))
|
|
680
|
+
return argv
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
def _run_uv(argv: list[str], *, cwd: Path, env: dict[str, str], action: str) -> None:
|
|
684
|
+
completed = subprocess.run(
|
|
685
|
+
argv,
|
|
686
|
+
cwd=str(cwd),
|
|
687
|
+
env=env,
|
|
688
|
+
check=False,
|
|
689
|
+
capture_output=True,
|
|
690
|
+
text=True,
|
|
691
|
+
)
|
|
692
|
+
if completed.returncode == 0:
|
|
693
|
+
return
|
|
694
|
+
detail = (
|
|
695
|
+
completed.stderr or completed.stdout or "dependency backend command failed"
|
|
696
|
+
).strip()
|
|
697
|
+
if len(argv) > 1 and argv[1] == "build":
|
|
698
|
+
raise PzError(
|
|
699
|
+
code="PZ_SCAFFOLD_PUBLISH_BUILD_FAILED",
|
|
700
|
+
message="publish failed while building release artifacts.",
|
|
701
|
+
stage="cli",
|
|
702
|
+
owner="pz_core.authoring",
|
|
703
|
+
action=action,
|
|
704
|
+
detail=detail,
|
|
705
|
+
)
|
|
706
|
+
raise PzError(
|
|
707
|
+
code="PZ_SCAFFOLD_PUBLISH_UPLOAD_FAILED",
|
|
708
|
+
message="publish failed while uploading release artifacts.",
|
|
709
|
+
stage="cli",
|
|
710
|
+
owner="pz_core.authoring",
|
|
711
|
+
action=action,
|
|
712
|
+
detail=detail,
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
def _read_project_version(package_root: Path, *, action: str) -> str:
|
|
717
|
+
pyproject_path = package_root / "pyproject.toml"
|
|
718
|
+
if not pyproject_path.is_file():
|
|
719
|
+
raise PzError(
|
|
720
|
+
code="PZ_SCAFFOLD_PUBLISH_LAYOUT_INVALID",
|
|
721
|
+
message=f"package root is missing pyproject.toml: {package_root}",
|
|
722
|
+
stage="cli",
|
|
723
|
+
owner="pz_core.authoring",
|
|
724
|
+
action=action,
|
|
725
|
+
)
|
|
726
|
+
try:
|
|
727
|
+
payload = tomllib.loads(pyproject_path.read_text(encoding="utf-8"))
|
|
728
|
+
except Exception as exc:
|
|
729
|
+
raise PzError(
|
|
730
|
+
code="PZ_SCAFFOLD_PUBLISH_LAYOUT_INVALID",
|
|
731
|
+
message=f"package root has an unreadable pyproject.toml: {pyproject_path}",
|
|
732
|
+
stage="cli",
|
|
733
|
+
owner="pz_core.authoring",
|
|
734
|
+
action=action,
|
|
735
|
+
detail=str(exc),
|
|
736
|
+
) from exc
|
|
737
|
+
project = payload.get("project")
|
|
738
|
+
if not isinstance(project, dict):
|
|
739
|
+
raise PzError(
|
|
740
|
+
code="PZ_SCAFFOLD_PUBLISH_LAYOUT_INVALID",
|
|
741
|
+
message=f"package root is missing [project] metadata: {pyproject_path}",
|
|
742
|
+
stage="cli",
|
|
743
|
+
owner="pz_core.authoring",
|
|
744
|
+
action=action,
|
|
745
|
+
)
|
|
746
|
+
version = project.get("version")
|
|
747
|
+
if not isinstance(version, str) or not version.strip():
|
|
748
|
+
raise PzError(
|
|
749
|
+
code="PZ_SCAFFOLD_PUBLISH_VERSION_MISSING",
|
|
750
|
+
message="publish requires an explicit project.version before building release artifacts.",
|
|
751
|
+
stage="cli",
|
|
752
|
+
owner="pz_core.authoring",
|
|
753
|
+
action=action,
|
|
754
|
+
)
|
|
755
|
+
return version.strip()
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
def _read_project_name(package_root: Path, *, action: str) -> str:
|
|
759
|
+
pyproject_path = package_root / "pyproject.toml"
|
|
760
|
+
if not pyproject_path.is_file():
|
|
761
|
+
raise PzError(
|
|
762
|
+
code="PZ_SCAFFOLD_PUBLISH_LAYOUT_INVALID",
|
|
763
|
+
message=f"package root is missing pyproject.toml: {package_root}",
|
|
764
|
+
stage="cli",
|
|
765
|
+
owner="pz_core.authoring",
|
|
766
|
+
action=action,
|
|
767
|
+
)
|
|
768
|
+
try:
|
|
769
|
+
payload = tomllib.loads(pyproject_path.read_text(encoding="utf-8"))
|
|
770
|
+
except Exception as exc:
|
|
771
|
+
raise PzError(
|
|
772
|
+
code="PZ_SCAFFOLD_PUBLISH_LAYOUT_INVALID",
|
|
773
|
+
message=f"package root has an unreadable pyproject.toml: {pyproject_path}",
|
|
774
|
+
stage="cli",
|
|
775
|
+
owner="pz_core.authoring",
|
|
776
|
+
action=action,
|
|
777
|
+
detail=str(exc),
|
|
778
|
+
) from exc
|
|
779
|
+
project = payload.get("project")
|
|
780
|
+
if not isinstance(project, dict):
|
|
781
|
+
raise PzError(
|
|
782
|
+
code="PZ_SCAFFOLD_PUBLISH_LAYOUT_INVALID",
|
|
783
|
+
message=f"package root is missing [project] metadata: {pyproject_path}",
|
|
784
|
+
stage="cli",
|
|
785
|
+
owner="pz_core.authoring",
|
|
786
|
+
action=action,
|
|
787
|
+
)
|
|
788
|
+
name = project.get("name")
|
|
789
|
+
if not isinstance(name, str) or not name.strip():
|
|
790
|
+
raise PzError(
|
|
791
|
+
code="PZ_SCAFFOLD_PUBLISH_LAYOUT_INVALID",
|
|
792
|
+
message="publish/unpublish requires an explicit project.name.",
|
|
793
|
+
stage="cli",
|
|
794
|
+
owner="pz_core.authoring",
|
|
795
|
+
action=action,
|
|
796
|
+
)
|
|
797
|
+
return name.strip()
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
def _derive_devpi_index_url(target: PublishTarget, *, action: str) -> str:
|
|
801
|
+
if isinstance(target.publish_url, str) and target.publish_url.strip():
|
|
802
|
+
return target.publish_url.strip()
|
|
803
|
+
if isinstance(target.index, str) and target.index.strip():
|
|
804
|
+
return target.index.strip()
|
|
805
|
+
check_url = str(target.check_url or "").strip()
|
|
806
|
+
if check_url.endswith("/+simple/"):
|
|
807
|
+
return check_url[: -len("/+simple/")] + "/"
|
|
808
|
+
if check_url.endswith("/+simple"):
|
|
809
|
+
return check_url[: -len("/+simple")] + "/"
|
|
810
|
+
raise PzError(
|
|
811
|
+
code="PZ_SCAFFOLD_UNPUBLISH_TARGET_INVALID",
|
|
812
|
+
message=(
|
|
813
|
+
f"publish target `{target.name}` must define `publish_url`, `index`, "
|
|
814
|
+
"or a devpi `check_url` to support unpublish."
|
|
815
|
+
),
|
|
816
|
+
stage="cli",
|
|
817
|
+
owner="pz_core.authoring",
|
|
818
|
+
action=action,
|
|
819
|
+
)
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
def _build_basic_auth_header(*, username: str, password: str) -> str:
|
|
823
|
+
token = base64.b64encode(f"{username}:{password}".encode("utf-8")).decode("ascii")
|
|
824
|
+
return f"Basic {token}"
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+
def _add_empty_query_flag(url: str, name: str) -> str:
|
|
828
|
+
parsed = urlsplit(url)
|
|
829
|
+
query = parsed.query
|
|
830
|
+
query = f"{query}&{name}=" if query else f"{name}="
|
|
831
|
+
return urlunsplit(
|
|
832
|
+
(parsed.scheme, parsed.netloc, parsed.path, query, parsed.fragment)
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
|
|
836
|
+
def _devpi_request_json(
|
|
837
|
+
url: str,
|
|
838
|
+
*,
|
|
839
|
+
method: str,
|
|
840
|
+
username: str,
|
|
841
|
+
password: str,
|
|
842
|
+
action: str,
|
|
843
|
+
) -> dict[str, object]:
|
|
844
|
+
headers = {
|
|
845
|
+
"Accept": "application/json",
|
|
846
|
+
"content-type": "application/json",
|
|
847
|
+
"Authorization": _build_basic_auth_header(username=username, password=password),
|
|
848
|
+
}
|
|
849
|
+
request = Request(url, method=method.upper(), headers=headers)
|
|
850
|
+
try:
|
|
851
|
+
with urlopen(request) as response:
|
|
852
|
+
raw = response.read().decode("utf-8")
|
|
853
|
+
except HTTPError as exc:
|
|
854
|
+
detail = exc.read().decode("utf-8", errors="ignore").strip()
|
|
855
|
+
if exc.code == 404:
|
|
856
|
+
raise PzError(
|
|
857
|
+
code="PZ_SCAFFOLD_UNPUBLISH_NOT_FOUND",
|
|
858
|
+
message="publish target does not contain the requested package.",
|
|
859
|
+
stage="cli",
|
|
860
|
+
owner="pz_core.authoring",
|
|
861
|
+
action=action,
|
|
862
|
+
detail=detail or url,
|
|
863
|
+
) from exc
|
|
864
|
+
raise PzError(
|
|
865
|
+
code="PZ_SCAFFOLD_UNPUBLISH_REQUEST_FAILED",
|
|
866
|
+
message="unpublish request failed.",
|
|
867
|
+
stage="cli",
|
|
868
|
+
owner="pz_core.authoring",
|
|
869
|
+
action=action,
|
|
870
|
+
detail=detail or f"HTTP {exc.code} {exc.reason}",
|
|
871
|
+
) from exc
|
|
872
|
+
except URLError as exc:
|
|
873
|
+
raise PzError(
|
|
874
|
+
code="PZ_SCAFFOLD_UNPUBLISH_REQUEST_FAILED",
|
|
875
|
+
message="unpublish request failed.",
|
|
876
|
+
stage="cli",
|
|
877
|
+
owner="pz_core.authoring",
|
|
878
|
+
action=action,
|
|
879
|
+
detail=str(exc.reason),
|
|
880
|
+
) from exc
|
|
881
|
+
if not raw.strip():
|
|
882
|
+
return {}
|
|
883
|
+
try:
|
|
884
|
+
payload = json.loads(raw)
|
|
885
|
+
except Exception as exc:
|
|
886
|
+
raise PzError(
|
|
887
|
+
code="PZ_SCAFFOLD_UNPUBLISH_RESPONSE_INVALID",
|
|
888
|
+
message="unpublish target returned a non-JSON response.",
|
|
889
|
+
stage="cli",
|
|
890
|
+
owner="pz_core.authoring",
|
|
891
|
+
action=action,
|
|
892
|
+
detail=raw[:500],
|
|
893
|
+
) from exc
|
|
894
|
+
if not isinstance(payload, dict):
|
|
895
|
+
raise PzError(
|
|
896
|
+
code="PZ_SCAFFOLD_UNPUBLISH_RESPONSE_INVALID",
|
|
897
|
+
message="unpublish target returned an invalid JSON payload.",
|
|
898
|
+
stage="cli",
|
|
899
|
+
owner="pz_core.authoring",
|
|
900
|
+
action=action,
|
|
901
|
+
)
|
|
902
|
+
return payload
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
def _extract_devpi_versions_for_project(
|
|
906
|
+
payload: dict[str, object], *, index_url: str, action: str
|
|
907
|
+
) -> tuple[str, ...]:
|
|
908
|
+
result = payload.get("result")
|
|
909
|
+
if not isinstance(result, dict):
|
|
910
|
+
raise PzError(
|
|
911
|
+
code="PZ_SCAFFOLD_UNPUBLISH_RESPONSE_INVALID",
|
|
912
|
+
message="unpublish target did not return a projectconfig object.",
|
|
913
|
+
stage="cli",
|
|
914
|
+
owner="pz_core.authoring",
|
|
915
|
+
action=action,
|
|
916
|
+
)
|
|
917
|
+
index_prefix = index_url.rstrip("/")
|
|
918
|
+
versions: list[str] = []
|
|
919
|
+
for version, metadata in result.items():
|
|
920
|
+
if not isinstance(version, str) or not isinstance(metadata, dict):
|
|
921
|
+
continue
|
|
922
|
+
links = metadata.get("+links")
|
|
923
|
+
if not isinstance(links, list):
|
|
924
|
+
continue
|
|
925
|
+
if any(
|
|
926
|
+
isinstance(link, dict)
|
|
927
|
+
and isinstance(link.get("href"), str)
|
|
928
|
+
and str(link.get("href")).startswith(index_prefix)
|
|
929
|
+
for link in links
|
|
930
|
+
):
|
|
931
|
+
versions.append(version)
|
|
932
|
+
return tuple(sorted(versions))
|
|
933
|
+
|
|
934
|
+
|
|
935
|
+
def _build_and_publish_package(
|
|
936
|
+
*,
|
|
937
|
+
app_id: str,
|
|
938
|
+
project_dir: Path,
|
|
939
|
+
package_root: Path,
|
|
940
|
+
workspace_package_root: Path | None,
|
|
941
|
+
target_name: str | None,
|
|
942
|
+
dry_run: bool,
|
|
943
|
+
action: str,
|
|
944
|
+
commit: str | None = None,
|
|
945
|
+
tag: str | None = None,
|
|
946
|
+
) -> PublishResult:
|
|
947
|
+
version = _read_project_version(package_root, action=action)
|
|
948
|
+
distribution_name = _read_project_name(package_root, action=action)
|
|
949
|
+
target = _load_publish_target(
|
|
950
|
+
project_dir,
|
|
951
|
+
package_root=package_root,
|
|
952
|
+
workspace_package_root=workspace_package_root,
|
|
953
|
+
target_name=target_name,
|
|
954
|
+
action=action,
|
|
955
|
+
)
|
|
956
|
+
enforce_publish_lock(
|
|
957
|
+
project_dir=project_dir,
|
|
958
|
+
app_id=app_id,
|
|
959
|
+
distribution_name=distribution_name,
|
|
960
|
+
target=target,
|
|
961
|
+
action=action,
|
|
962
|
+
)
|
|
963
|
+
uv_binary = shutil.which("uv")
|
|
964
|
+
if not uv_binary:
|
|
965
|
+
raise PzError(
|
|
966
|
+
code="PZ_SCAFFOLD_PUBLISH_UV_MISSING",
|
|
967
|
+
message="required publish backend was not found on PATH.",
|
|
968
|
+
stage="cli",
|
|
969
|
+
owner="pz_core.authoring",
|
|
970
|
+
action=action,
|
|
971
|
+
)
|
|
972
|
+
env = _publish_env(target=target, project_dir=project_dir, action=action)
|
|
973
|
+
_clean_build_artifacts(package_root)
|
|
974
|
+
_run_uv([uv_binary, "build"], cwd=package_root, env=env, action=action)
|
|
975
|
+
_validate_and_smoke_wheel(
|
|
976
|
+
app_id=app_id,
|
|
977
|
+
distribution_name=distribution_name,
|
|
978
|
+
version=version,
|
|
979
|
+
dist_dir=package_root / "dist",
|
|
980
|
+
uv_binary=uv_binary,
|
|
981
|
+
env=env,
|
|
982
|
+
action=action,
|
|
983
|
+
)
|
|
984
|
+
_run_uv(
|
|
985
|
+
_publish_argv(
|
|
986
|
+
uv_binary=uv_binary,
|
|
987
|
+
target=target,
|
|
988
|
+
dist_dir=package_root / "dist",
|
|
989
|
+
dry_run=dry_run,
|
|
990
|
+
),
|
|
991
|
+
cwd=package_root,
|
|
992
|
+
env=env,
|
|
993
|
+
action=action,
|
|
994
|
+
)
|
|
995
|
+
return PublishResult(
|
|
996
|
+
app_id=app_id,
|
|
997
|
+
version=version,
|
|
998
|
+
package_root=package_root,
|
|
999
|
+
dist_dir=package_root / "dist",
|
|
1000
|
+
target=target,
|
|
1001
|
+
dry_run=dry_run,
|
|
1002
|
+
commit=commit,
|
|
1003
|
+
tag=tag,
|
|
1004
|
+
)
|
|
1005
|
+
|
|
1006
|
+
|
|
1007
|
+
def _normalized_distribution(value: str) -> str:
|
|
1008
|
+
return re.sub(r"[-_.]+", "-", value).lower()
|
|
1009
|
+
|
|
1010
|
+
|
|
1011
|
+
def _wheel_import_roots(names: tuple[str, ...] | list[str]) -> tuple[str, ...]:
|
|
1012
|
+
roots: set[str] = set()
|
|
1013
|
+
for name in names:
|
|
1014
|
+
parts = name.split("/")
|
|
1015
|
+
if len(parts) >= 2 and parts[1] == "__init__.py":
|
|
1016
|
+
candidate = parts[0]
|
|
1017
|
+
elif len(parts) == 1 and name.endswith(".py"):
|
|
1018
|
+
candidate = name[:-3]
|
|
1019
|
+
else:
|
|
1020
|
+
continue
|
|
1021
|
+
if candidate.isidentifier():
|
|
1022
|
+
roots.add(candidate)
|
|
1023
|
+
return tuple(sorted(roots))
|
|
1024
|
+
|
|
1025
|
+
|
|
1026
|
+
def _validate_and_smoke_wheel(
|
|
1027
|
+
*,
|
|
1028
|
+
app_id: str,
|
|
1029
|
+
distribution_name: str,
|
|
1030
|
+
version: str,
|
|
1031
|
+
dist_dir: Path,
|
|
1032
|
+
uv_binary: str,
|
|
1033
|
+
env: dict[str, str],
|
|
1034
|
+
action: str,
|
|
1035
|
+
) -> None:
|
|
1036
|
+
wheels = sorted(dist_dir.glob("*.whl"))
|
|
1037
|
+
if len(wheels) != 1:
|
|
1038
|
+
raise PzError(
|
|
1039
|
+
code="PZ_PUB_WHEEL_INVALID",
|
|
1040
|
+
message="构建结果必须且只能包含一个 wheel。",
|
|
1041
|
+
stage="cli",
|
|
1042
|
+
owner="pz_core.authoring.release",
|
|
1043
|
+
action=action,
|
|
1044
|
+
detail=str(dist_dir),
|
|
1045
|
+
)
|
|
1046
|
+
wheel = wheels[0]
|
|
1047
|
+
try:
|
|
1048
|
+
with zipfile.ZipFile(wheel) as archive:
|
|
1049
|
+
metadata_names = [
|
|
1050
|
+
name for name in archive.namelist() if name.endswith(".dist-info/METADATA")
|
|
1051
|
+
]
|
|
1052
|
+
if len(metadata_names) != 1:
|
|
1053
|
+
raise ValueError("wheel METADATA is missing or ambiguous")
|
|
1054
|
+
metadata = BytesParser().parsebytes(archive.read(metadata_names[0]))
|
|
1055
|
+
import_roots = _wheel_import_roots(archive.namelist())
|
|
1056
|
+
except Exception as exc:
|
|
1057
|
+
raise PzError(
|
|
1058
|
+
code="PZ_PUB_WHEEL_INVALID",
|
|
1059
|
+
message="无法读取发布 wheel 的 metadata。",
|
|
1060
|
+
stage="cli",
|
|
1061
|
+
owner="pz_core.authoring.release",
|
|
1062
|
+
action=action,
|
|
1063
|
+
detail=str(exc),
|
|
1064
|
+
) from exc
|
|
1065
|
+
actual_name = str(metadata.get("Name") or "")
|
|
1066
|
+
actual_version = str(metadata.get("Version") or "")
|
|
1067
|
+
if (
|
|
1068
|
+
_normalized_distribution(actual_name)
|
|
1069
|
+
!= _normalized_distribution(distribution_name)
|
|
1070
|
+
or actual_version != version
|
|
1071
|
+
):
|
|
1072
|
+
raise PzError(
|
|
1073
|
+
code="PZ_PUB_WHEEL_IDENTITY_MISMATCH",
|
|
1074
|
+
message="wheel metadata 与目标子包的 distribution/version 不一致。",
|
|
1075
|
+
stage="cli",
|
|
1076
|
+
owner="pz_core.authoring.release",
|
|
1077
|
+
action=action,
|
|
1078
|
+
detail=f"expected={distribution_name}=={version}; actual={actual_name}=={actual_version}",
|
|
1079
|
+
)
|
|
1080
|
+
if not import_roots:
|
|
1081
|
+
raise PzError(
|
|
1082
|
+
code="PZ_PUB_WHEEL_INVALID",
|
|
1083
|
+
message="发布 wheel 中没有可验证的顶层 Python import package。",
|
|
1084
|
+
stage="cli",
|
|
1085
|
+
owner="pz_core.authoring.release",
|
|
1086
|
+
action=action,
|
|
1087
|
+
detail=str(wheel),
|
|
1088
|
+
)
|
|
1089
|
+
with tempfile.TemporaryDirectory(prefix="pz-pub-smoke-") as raw_temp:
|
|
1090
|
+
venv = Path(raw_temp) / "venv"
|
|
1091
|
+
_run_uv([uv_binary, "venv", str(venv)], cwd=dist_dir, env=env, action=action)
|
|
1092
|
+
python = venv / ("Scripts/python.exe" if os.name == "nt" else "bin/python")
|
|
1093
|
+
_run_uv(
|
|
1094
|
+
[uv_binary, "pip", "install", "--python", str(python), "--no-deps", str(wheel)],
|
|
1095
|
+
cwd=dist_dir,
|
|
1096
|
+
env=env,
|
|
1097
|
+
action=action,
|
|
1098
|
+
)
|
|
1099
|
+
smoke = subprocess.run(
|
|
1100
|
+
(
|
|
1101
|
+
str(python),
|
|
1102
|
+
"-I",
|
|
1103
|
+
"-c",
|
|
1104
|
+
"import importlib,sys; [importlib.import_module(name) for name in sys.argv[1:]]",
|
|
1105
|
+
*import_roots,
|
|
1106
|
+
),
|
|
1107
|
+
cwd=str(dist_dir),
|
|
1108
|
+
env=env,
|
|
1109
|
+
check=False,
|
|
1110
|
+
capture_output=True,
|
|
1111
|
+
text=True,
|
|
1112
|
+
)
|
|
1113
|
+
if smoke.returncode != 0:
|
|
1114
|
+
raise PzError(
|
|
1115
|
+
code="PZ_PUB_WHEEL_SMOKE_FAILED",
|
|
1116
|
+
message="wheel 在干净环境中的安装/import smoke 失败。",
|
|
1117
|
+
stage="cli",
|
|
1118
|
+
owner="pz_core.authoring.release",
|
|
1119
|
+
action=action,
|
|
1120
|
+
detail=(smoke.stderr or smoke.stdout).strip()[:500],
|
|
1121
|
+
)
|
|
1122
|
+
|
|
1123
|
+
|
|
1124
|
+
def publish_commit(
|
|
1125
|
+
app_id: str,
|
|
1126
|
+
*,
|
|
1127
|
+
project_dir: Path,
|
|
1128
|
+
action: str = "publish_commit_main",
|
|
1129
|
+
) -> PublishCommitResult:
|
|
1130
|
+
source = resolve_publish_source(project_dir, app_id, action=action)
|
|
1131
|
+
version = _read_project_version(source.package_root, action=action)
|
|
1132
|
+
return commit_publish_app(
|
|
1133
|
+
source.app_id,
|
|
1134
|
+
project_dir=project_dir,
|
|
1135
|
+
version=version,
|
|
1136
|
+
action=action,
|
|
1137
|
+
)
|
|
1138
|
+
|
|
1139
|
+
|
|
1140
|
+
def publish_tag(
|
|
1141
|
+
app_id: str,
|
|
1142
|
+
*,
|
|
1143
|
+
project_dir: Path,
|
|
1144
|
+
commit: str | None = None,
|
|
1145
|
+
action: str = "publish_tag_main",
|
|
1146
|
+
) -> PublishTagResult:
|
|
1147
|
+
source = resolve_publish_source(project_dir, app_id, action=action)
|
|
1148
|
+
version = _read_project_version(source.package_root, action=action)
|
|
1149
|
+
return tag_publish_app(
|
|
1150
|
+
source.app_id,
|
|
1151
|
+
project_dir=project_dir,
|
|
1152
|
+
version=version,
|
|
1153
|
+
commit=commit,
|
|
1154
|
+
action=action,
|
|
1155
|
+
)
|
|
1156
|
+
|
|
1157
|
+
|
|
1158
|
+
def publish_upload(
|
|
1159
|
+
app_id: str,
|
|
1160
|
+
*,
|
|
1161
|
+
project_dir: Path,
|
|
1162
|
+
target_name: str | None = None,
|
|
1163
|
+
dry_run: bool = False,
|
|
1164
|
+
version: str | None = None,
|
|
1165
|
+
tag_result: PublishTagResult | None = None,
|
|
1166
|
+
action: str = "publish_upload_main",
|
|
1167
|
+
) -> PublishResult:
|
|
1168
|
+
source = resolve_publish_source(project_dir, app_id, action=action)
|
|
1169
|
+
resolved_version = version or _read_project_version(source.package_root, action=action)
|
|
1170
|
+
resolved_tag = tag_result or require_publish_tag(
|
|
1171
|
+
source.app_id,
|
|
1172
|
+
project_dir=project_dir,
|
|
1173
|
+
version=resolved_version,
|
|
1174
|
+
action=action,
|
|
1175
|
+
)
|
|
1176
|
+
package_root = require_publish_checkout_at_tag(resolved_tag, action=action)
|
|
1177
|
+
current_version = _read_project_version(package_root, action=action)
|
|
1178
|
+
if current_version != resolved_version:
|
|
1179
|
+
raise PzError(
|
|
1180
|
+
code="PZ_PUB_RELEASE_STATE_INVALID",
|
|
1181
|
+
message="当前 project.version 与发布版本不一致。",
|
|
1182
|
+
stage="cli",
|
|
1183
|
+
owner="pz_core.authoring.release",
|
|
1184
|
+
action=action,
|
|
1185
|
+
)
|
|
1186
|
+
result = _build_and_publish_package(
|
|
1187
|
+
app_id=source.app_id,
|
|
1188
|
+
project_dir=project_dir,
|
|
1189
|
+
package_root=package_root,
|
|
1190
|
+
workspace_package_root=source.git_root,
|
|
1191
|
+
target_name=target_name,
|
|
1192
|
+
dry_run=dry_run,
|
|
1193
|
+
action=action,
|
|
1194
|
+
commit=resolved_tag.commit_result.commit,
|
|
1195
|
+
tag=resolved_tag.tag,
|
|
1196
|
+
)
|
|
1197
|
+
return PublishResult(
|
|
1198
|
+
app_id=result.app_id,
|
|
1199
|
+
version=result.version,
|
|
1200
|
+
package_root=source.package_root,
|
|
1201
|
+
dist_dir=source.package_root / "dist",
|
|
1202
|
+
target=result.target,
|
|
1203
|
+
dry_run=result.dry_run,
|
|
1204
|
+
commit=result.commit,
|
|
1205
|
+
tag=result.tag,
|
|
1206
|
+
)
|
|
1207
|
+
|
|
1208
|
+
|
|
1209
|
+
def publish_app(
|
|
1210
|
+
app_id: str,
|
|
1211
|
+
*,
|
|
1212
|
+
project_dir: Path,
|
|
1213
|
+
target_name: str | None = None,
|
|
1214
|
+
dry_run: bool = False,
|
|
1215
|
+
action: str = "publish_main",
|
|
1216
|
+
) -> PublishResult:
|
|
1217
|
+
source = resolve_publish_source(project_dir, app_id, action=action)
|
|
1218
|
+
if dry_run:
|
|
1219
|
+
source = preflight_publish_app(
|
|
1220
|
+
source.app_id,
|
|
1221
|
+
project_dir=project_dir,
|
|
1222
|
+
action=action,
|
|
1223
|
+
source=source,
|
|
1224
|
+
)
|
|
1225
|
+
return _build_and_publish_package(
|
|
1226
|
+
app_id=source.app_id,
|
|
1227
|
+
project_dir=project_dir,
|
|
1228
|
+
package_root=source.package_root,
|
|
1229
|
+
workspace_package_root=source.git_root,
|
|
1230
|
+
target_name=target_name,
|
|
1231
|
+
dry_run=True,
|
|
1232
|
+
action=action,
|
|
1233
|
+
)
|
|
1234
|
+
source = preflight_publish_app(
|
|
1235
|
+
source.app_id,
|
|
1236
|
+
project_dir=project_dir,
|
|
1237
|
+
action=action,
|
|
1238
|
+
source=source,
|
|
1239
|
+
)
|
|
1240
|
+
# The index/hash conflict check performed by `uv publish --dry-run` must
|
|
1241
|
+
# happen before release history is mutated.
|
|
1242
|
+
try:
|
|
1243
|
+
_build_and_publish_package(
|
|
1244
|
+
app_id=source.app_id,
|
|
1245
|
+
project_dir=project_dir,
|
|
1246
|
+
package_root=source.package_root,
|
|
1247
|
+
workspace_package_root=source.git_root,
|
|
1248
|
+
target_name=target_name,
|
|
1249
|
+
dry_run=True,
|
|
1250
|
+
action=action,
|
|
1251
|
+
)
|
|
1252
|
+
finally:
|
|
1253
|
+
_clean_build_artifacts(source.package_root)
|
|
1254
|
+
commit_result = publish_commit(source.app_id, project_dir=project_dir, action=action)
|
|
1255
|
+
tag_result = publish_tag(
|
|
1256
|
+
source.app_id,
|
|
1257
|
+
project_dir=project_dir,
|
|
1258
|
+
commit=commit_result.commit,
|
|
1259
|
+
action=action,
|
|
1260
|
+
)
|
|
1261
|
+
return publish_upload(
|
|
1262
|
+
source.app_id,
|
|
1263
|
+
project_dir=project_dir,
|
|
1264
|
+
target_name=target_name,
|
|
1265
|
+
tag_result=tag_result,
|
|
1266
|
+
action=action,
|
|
1267
|
+
)
|
|
1268
|
+
|
|
1269
|
+
|
|
1270
|
+
def unpublish_app(
|
|
1271
|
+
app_id: str,
|
|
1272
|
+
*,
|
|
1273
|
+
project_dir: Path,
|
|
1274
|
+
target_name: str | None = None,
|
|
1275
|
+
dry_run: bool = False,
|
|
1276
|
+
force: bool = False,
|
|
1277
|
+
action: str = "publish_rm_main",
|
|
1278
|
+
) -> UnpublishResult:
|
|
1279
|
+
workspace_package = resolve_workspace_package(str(app_id), anchors=(project_dir,))
|
|
1280
|
+
normalized_app_id = (
|
|
1281
|
+
workspace_package.app_id
|
|
1282
|
+
if workspace_package is not None
|
|
1283
|
+
else normalize_publish_app_id(project_dir, str(app_id), action=action)
|
|
1284
|
+
)
|
|
1285
|
+
package_root = resolve_publish_package_root(
|
|
1286
|
+
project_dir, normalized_app_id, action=action
|
|
1287
|
+
)
|
|
1288
|
+
version = _read_project_version(package_root, action=action)
|
|
1289
|
+
distribution_name = _read_project_name(package_root, action=action)
|
|
1290
|
+
target = _load_publish_target(
|
|
1291
|
+
project_dir,
|
|
1292
|
+
package_root=package_root,
|
|
1293
|
+
workspace_package_root=workspace_package.repo
|
|
1294
|
+
if workspace_package is not None
|
|
1295
|
+
else None,
|
|
1296
|
+
target_name=target_name,
|
|
1297
|
+
action=action,
|
|
1298
|
+
)
|
|
1299
|
+
index_url = _derive_devpi_index_url(target, action=action)
|
|
1300
|
+
if not _is_devpi_target(target, index_url, target.publish_url, target.check_url):
|
|
1301
|
+
raise PzError(
|
|
1302
|
+
code="PZ_SCAFFOLD_UNPUBLISH_TARGET_UNSUPPORTED",
|
|
1303
|
+
message="unpublish currently supports formal devpi targets only.",
|
|
1304
|
+
stage="cli",
|
|
1305
|
+
owner="pz_core.authoring",
|
|
1306
|
+
action=action,
|
|
1307
|
+
)
|
|
1308
|
+
username_env = target.username_env or "DEVPI_OWNER"
|
|
1309
|
+
password_env = target.password_env or "DEVPI_OWNER_PASSWORD"
|
|
1310
|
+
env = build_authoring_env(project_dir, action=action)
|
|
1311
|
+
username = _require_named_env(
|
|
1312
|
+
env,
|
|
1313
|
+
username_env,
|
|
1314
|
+
code="PZ_SCAFFOLD_PUBLISH_AUTH_MISSING",
|
|
1315
|
+
message=f"publish target `{target.name}` requires env var `{username_env}`.",
|
|
1316
|
+
action=action,
|
|
1317
|
+
)
|
|
1318
|
+
password = _require_named_env(
|
|
1319
|
+
env,
|
|
1320
|
+
password_env,
|
|
1321
|
+
code="PZ_SCAFFOLD_PUBLISH_AUTH_MISSING",
|
|
1322
|
+
message=f"publish target `{target.name}` requires env var `{password_env}`.",
|
|
1323
|
+
action=action,
|
|
1324
|
+
)
|
|
1325
|
+
project_url = f"{index_url.rstrip('/')}/{quote(distribution_name, safe='')}"
|
|
1326
|
+
inspect_url = _add_empty_query_flag(project_url, "ignore_bases")
|
|
1327
|
+
payload = _devpi_request_json(
|
|
1328
|
+
inspect_url,
|
|
1329
|
+
method="GET",
|
|
1330
|
+
username=username,
|
|
1331
|
+
password=password,
|
|
1332
|
+
action=action,
|
|
1333
|
+
)
|
|
1334
|
+
removed_versions = _extract_devpi_versions_for_project(
|
|
1335
|
+
payload, index_url=index_url, action=action
|
|
1336
|
+
)
|
|
1337
|
+
if not removed_versions:
|
|
1338
|
+
raise PzError(
|
|
1339
|
+
code="PZ_SCAFFOLD_UNPUBLISH_NOT_FOUND",
|
|
1340
|
+
message="publish target does not contain the requested package.",
|
|
1341
|
+
stage="cli",
|
|
1342
|
+
owner="pz_core.authoring",
|
|
1343
|
+
action=action,
|
|
1344
|
+
detail=distribution_name,
|
|
1345
|
+
)
|
|
1346
|
+
delete_url = _add_empty_query_flag(project_url, "force") if force else project_url
|
|
1347
|
+
if not dry_run:
|
|
1348
|
+
_devpi_request_json(
|
|
1349
|
+
delete_url,
|
|
1350
|
+
method="DELETE",
|
|
1351
|
+
username=username,
|
|
1352
|
+
password=password,
|
|
1353
|
+
action=action,
|
|
1354
|
+
)
|
|
1355
|
+
return UnpublishResult(
|
|
1356
|
+
app_id=normalized_app_id,
|
|
1357
|
+
distribution_name=distribution_name,
|
|
1358
|
+
version=version,
|
|
1359
|
+
package_root=package_root,
|
|
1360
|
+
target=target,
|
|
1361
|
+
project_url=project_url,
|
|
1362
|
+
removed_versions=removed_versions,
|
|
1363
|
+
dry_run=dry_run,
|
|
1364
|
+
force=force,
|
|
1365
|
+
)
|
|
1366
|
+
|
|
1367
|
+
|
|
1368
|
+
__all__ = [
|
|
1369
|
+
"InstallTarget",
|
|
1370
|
+
"PublishResult",
|
|
1371
|
+
"PublishTarget",
|
|
1372
|
+
"UnpublishResult",
|
|
1373
|
+
"build_authoring_env",
|
|
1374
|
+
"install_env",
|
|
1375
|
+
"install_env_for_targets",
|
|
1376
|
+
"load_install_target",
|
|
1377
|
+
"publish_app",
|
|
1378
|
+
"publish_commit",
|
|
1379
|
+
"publish_tag",
|
|
1380
|
+
"publish_upload",
|
|
1381
|
+
"unpublish_app",
|
|
1382
|
+
]
|