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,916 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import tomllib
|
|
5
|
+
import uuid
|
|
6
|
+
from collections.abc import Mapping, Sequence
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path, PurePosixPath
|
|
9
|
+
|
|
10
|
+
from pz_core.authoring.dev_manifest import (
|
|
11
|
+
DevBinding,
|
|
12
|
+
DevManifest,
|
|
13
|
+
DevRepo,
|
|
14
|
+
validate_dev_manifest,
|
|
15
|
+
)
|
|
16
|
+
from pz_core.runtime.errors import PzError
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
BINDING_CONFLICT_CODE = "PZ_DEV_BINDING_CONFLICT"
|
|
20
|
+
BINDING_OVERLAP_CODE = "PZ_DEV_BINDING_OVERLAP"
|
|
21
|
+
BINDING_SOURCE_CONFLICT_CODE = "PZ_DEV_BINDING_SOURCE_CONFLICT"
|
|
22
|
+
BINDING_INVALID_PROJECTION_CODE = "PZ_DEV_BINDING_INVALID_PROJECTION"
|
|
23
|
+
_OWNER = "pz_core.authoring.dev_bindings"
|
|
24
|
+
_RESOLUTION_ERRORS = (OSError, RuntimeError)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True, slots=True)
|
|
28
|
+
class LinkMutation:
|
|
29
|
+
final_path: Path
|
|
30
|
+
staged_path: Path
|
|
31
|
+
relative_target: str
|
|
32
|
+
previous_target: str | None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True, slots=True)
|
|
36
|
+
class LinkRemoval:
|
|
37
|
+
final_path: Path
|
|
38
|
+
previous_target: str
|
|
39
|
+
expected_target: Path
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True, slots=True)
|
|
43
|
+
class _Projection:
|
|
44
|
+
key: str
|
|
45
|
+
binding: DevBinding
|
|
46
|
+
final_path: Path
|
|
47
|
+
target_path: Path
|
|
48
|
+
target_realpath: Path
|
|
49
|
+
base_realpath: Path
|
|
50
|
+
relative_target: str
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True, slots=True)
|
|
54
|
+
class _Preflight:
|
|
55
|
+
before: Mapping[Path, _Projection]
|
|
56
|
+
after: Mapping[Path, _Projection]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _error(
|
|
60
|
+
code: str, message: str, *, action: str, detail: str | None = None
|
|
61
|
+
) -> PzError:
|
|
62
|
+
return PzError(
|
|
63
|
+
code=code,
|
|
64
|
+
message=message,
|
|
65
|
+
stage="dev",
|
|
66
|
+
owner=_OWNER,
|
|
67
|
+
action=action,
|
|
68
|
+
detail=detail,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _lexists(path: Path) -> bool:
|
|
73
|
+
return os.path.lexists(path)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _paths_overlap(first: Path, second: Path) -> bool:
|
|
77
|
+
return first == second or first in second.parents or second in first.parents
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _join_posix(base: Path, child: PurePosixPath) -> Path:
|
|
81
|
+
if child == PurePosixPath("."):
|
|
82
|
+
return base
|
|
83
|
+
return base / Path(*child.parts)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _declared_source_path(
|
|
87
|
+
project_dir: Path,
|
|
88
|
+
binding: DevBinding,
|
|
89
|
+
repos: Mapping[str, DevRepo],
|
|
90
|
+
) -> Path | None:
|
|
91
|
+
if binding.source_kind == "git":
|
|
92
|
+
if not binding.repo_key:
|
|
93
|
+
return None
|
|
94
|
+
repo = repos.get(binding.repo_key)
|
|
95
|
+
if repo is None:
|
|
96
|
+
return None
|
|
97
|
+
base = project_dir / Path(*repo.checkout.parts)
|
|
98
|
+
elif binding.source_kind == "link":
|
|
99
|
+
base = Path(binding.source)
|
|
100
|
+
else:
|
|
101
|
+
return None
|
|
102
|
+
return _join_posix(base, binding.subpath).resolve(strict=False)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _check_granularity_overlap(
|
|
106
|
+
project_dir: Path,
|
|
107
|
+
before: DevManifest,
|
|
108
|
+
after: DevManifest,
|
|
109
|
+
) -> None:
|
|
110
|
+
desired_bindings = sorted(after.bindings.items())
|
|
111
|
+
for index, (first_key, first) in enumerate(desired_bindings):
|
|
112
|
+
for second_key, second in desired_bindings[index + 1 :]:
|
|
113
|
+
if first.kind == second.kind:
|
|
114
|
+
continue
|
|
115
|
+
first_source = _declared_source_path(project_dir, first, after.repos)
|
|
116
|
+
second_source = _declared_source_path(project_dir, second, after.repos)
|
|
117
|
+
if (
|
|
118
|
+
first_source is None
|
|
119
|
+
or second_source is None
|
|
120
|
+
or not _paths_overlap(first_source, second_source)
|
|
121
|
+
):
|
|
122
|
+
continue
|
|
123
|
+
raise _error(
|
|
124
|
+
BINDING_OVERLAP_CODE,
|
|
125
|
+
"directory and app bindings cannot expose overlapping sources",
|
|
126
|
+
action="plan_binding_projection",
|
|
127
|
+
detail=(
|
|
128
|
+
f"desired={first_key}:{first_source}; "
|
|
129
|
+
f"desired={second_key}:{second_source}"
|
|
130
|
+
),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
for before_key, existing in sorted(before.bindings.items()):
|
|
134
|
+
for after_key, desired in desired_bindings:
|
|
135
|
+
if existing.kind == desired.kind:
|
|
136
|
+
continue
|
|
137
|
+
existing_source = _declared_source_path(project_dir, existing, before.repos)
|
|
138
|
+
desired_source = _declared_source_path(project_dir, desired, after.repos)
|
|
139
|
+
if (
|
|
140
|
+
existing_source is None
|
|
141
|
+
or desired_source is None
|
|
142
|
+
or not _paths_overlap(existing_source, desired_source)
|
|
143
|
+
):
|
|
144
|
+
continue
|
|
145
|
+
raise _error(
|
|
146
|
+
BINDING_OVERLAP_CODE,
|
|
147
|
+
"directory and app bindings cannot expose overlapping sources",
|
|
148
|
+
action="plan_binding_projection",
|
|
149
|
+
detail=(
|
|
150
|
+
f"before={before_key}:{existing_source}; "
|
|
151
|
+
f"after={after_key}:{desired_source}"
|
|
152
|
+
),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _projection(
|
|
157
|
+
project_dir: Path,
|
|
158
|
+
key: str,
|
|
159
|
+
binding: DevBinding,
|
|
160
|
+
repos: Mapping[str, DevRepo],
|
|
161
|
+
*,
|
|
162
|
+
action: str,
|
|
163
|
+
) -> _Projection:
|
|
164
|
+
final_path = project_dir / Path(*binding.link.parts)
|
|
165
|
+
try:
|
|
166
|
+
parent_realpath = final_path.parent.resolve(strict=True)
|
|
167
|
+
except _RESOLUTION_ERRORS as exc:
|
|
168
|
+
raise _error(
|
|
169
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
170
|
+
f"binding parent does not exist: {final_path.parent}",
|
|
171
|
+
action=action,
|
|
172
|
+
detail=str(exc),
|
|
173
|
+
) from exc
|
|
174
|
+
if not final_path.parent.is_dir() or not parent_realpath.is_relative_to(
|
|
175
|
+
project_dir
|
|
176
|
+
):
|
|
177
|
+
raise _error(
|
|
178
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
179
|
+
f"binding parent escapes the project or is not a directory: {final_path.parent}",
|
|
180
|
+
action=action,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
if binding.source_kind == "git":
|
|
184
|
+
assert binding.repo_key is not None
|
|
185
|
+
repo = repos[binding.repo_key]
|
|
186
|
+
base = project_dir / Path(*repo.checkout.parts)
|
|
187
|
+
else:
|
|
188
|
+
base = Path(binding.source)
|
|
189
|
+
target_path = _join_posix(base, binding.subpath)
|
|
190
|
+
try:
|
|
191
|
+
base_realpath = base.resolve(strict=True)
|
|
192
|
+
target_realpath = target_path.resolve(strict=True)
|
|
193
|
+
except _RESOLUTION_ERRORS as exc:
|
|
194
|
+
raise _error(
|
|
195
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
196
|
+
f"binding source target is missing or broken: {target_path}",
|
|
197
|
+
action=action,
|
|
198
|
+
detail=str(exc),
|
|
199
|
+
) from exc
|
|
200
|
+
if not base_realpath.is_dir() or not target_realpath.is_dir():
|
|
201
|
+
raise _error(
|
|
202
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
203
|
+
f"binding source target must be a directory: {target_path}",
|
|
204
|
+
action=action,
|
|
205
|
+
)
|
|
206
|
+
if not target_realpath.is_relative_to(base_realpath):
|
|
207
|
+
raise _error(
|
|
208
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
209
|
+
f"binding source target escapes its declared source root: {target_path}",
|
|
210
|
+
action=action,
|
|
211
|
+
detail=f"source_root={base_realpath}; resolved_target={target_realpath}",
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
relative_target = Path(
|
|
215
|
+
os.path.relpath(target_path, start=final_path.parent)
|
|
216
|
+
).as_posix()
|
|
217
|
+
if Path(relative_target).is_absolute() or "\\" in relative_target:
|
|
218
|
+
raise _error(
|
|
219
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
220
|
+
f"binding target is not a POSIX relative path: {relative_target}",
|
|
221
|
+
action=action,
|
|
222
|
+
)
|
|
223
|
+
return _Projection(
|
|
224
|
+
key=key,
|
|
225
|
+
binding=binding,
|
|
226
|
+
final_path=final_path,
|
|
227
|
+
target_path=target_path,
|
|
228
|
+
target_realpath=target_realpath,
|
|
229
|
+
base_realpath=base_realpath,
|
|
230
|
+
relative_target=relative_target,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _projections(
|
|
235
|
+
project_dir: Path,
|
|
236
|
+
manifest: DevManifest,
|
|
237
|
+
*,
|
|
238
|
+
action: str,
|
|
239
|
+
) -> dict[Path, _Projection]:
|
|
240
|
+
result: dict[Path, _Projection] = {}
|
|
241
|
+
for key, binding in sorted(manifest.bindings.items()):
|
|
242
|
+
item = _projection(
|
|
243
|
+
project_dir,
|
|
244
|
+
key,
|
|
245
|
+
binding,
|
|
246
|
+
manifest.repos,
|
|
247
|
+
action=action,
|
|
248
|
+
)
|
|
249
|
+
result[item.final_path] = item
|
|
250
|
+
return result
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _verify_exact_link(
|
|
254
|
+
projection: _Projection,
|
|
255
|
+
*,
|
|
256
|
+
action: str,
|
|
257
|
+
code: str = BINDING_INVALID_PROJECTION_CODE,
|
|
258
|
+
) -> str:
|
|
259
|
+
path = projection.final_path
|
|
260
|
+
if not path.is_symlink():
|
|
261
|
+
state = "missing" if not _lexists(path) else "not a symbolic link"
|
|
262
|
+
raise _error(
|
|
263
|
+
code,
|
|
264
|
+
f"managed binding is {state}: {path}",
|
|
265
|
+
action=action,
|
|
266
|
+
)
|
|
267
|
+
try:
|
|
268
|
+
stored_target = os.readlink(path)
|
|
269
|
+
except _RESOLUTION_ERRORS as exc:
|
|
270
|
+
raise _error(
|
|
271
|
+
code,
|
|
272
|
+
f"cannot read managed binding: {path}",
|
|
273
|
+
action=action,
|
|
274
|
+
detail=str(exc),
|
|
275
|
+
) from exc
|
|
276
|
+
if (
|
|
277
|
+
Path(stored_target).is_absolute()
|
|
278
|
+
or "\\" in stored_target
|
|
279
|
+
or stored_target != projection.relative_target
|
|
280
|
+
):
|
|
281
|
+
raise _error(
|
|
282
|
+
code,
|
|
283
|
+
f"managed binding has an unexpected stored target: {path}",
|
|
284
|
+
action=action,
|
|
285
|
+
detail=(
|
|
286
|
+
f"expected={projection.relative_target!r}; actual={stored_target!r}"
|
|
287
|
+
),
|
|
288
|
+
)
|
|
289
|
+
try:
|
|
290
|
+
actual_realpath = path.resolve(strict=True)
|
|
291
|
+
except _RESOLUTION_ERRORS as exc:
|
|
292
|
+
raise _error(
|
|
293
|
+
code,
|
|
294
|
+
f"managed binding is broken: {path}",
|
|
295
|
+
action=action,
|
|
296
|
+
detail=str(exc),
|
|
297
|
+
) from exc
|
|
298
|
+
if actual_realpath != projection.target_realpath:
|
|
299
|
+
raise _error(
|
|
300
|
+
code,
|
|
301
|
+
f"managed binding resolves to the wrong source: {path}",
|
|
302
|
+
action=action,
|
|
303
|
+
detail=(f"expected={projection.target_realpath}; actual={actual_realpath}"),
|
|
304
|
+
)
|
|
305
|
+
return stored_target
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _distribution_name(pyproject: Path) -> str | None:
|
|
309
|
+
try:
|
|
310
|
+
payload = tomllib.loads(pyproject.read_text(encoding="utf-8"))
|
|
311
|
+
except (OSError, UnicodeError, tomllib.TOMLDecodeError):
|
|
312
|
+
return None
|
|
313
|
+
project = payload.get("project")
|
|
314
|
+
if not isinstance(project, dict):
|
|
315
|
+
return None
|
|
316
|
+
name = project.get("name")
|
|
317
|
+
if not isinstance(name, str) or not name.strip():
|
|
318
|
+
return None
|
|
319
|
+
return name.strip().replace("-", "_")
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _record_tree_distributions(
|
|
323
|
+
source_root: Path,
|
|
324
|
+
distributions: dict[str, set[Path]],
|
|
325
|
+
) -> None:
|
|
326
|
+
for raw_root, dirnames, filenames in os.walk(source_root, followlinks=False):
|
|
327
|
+
dirnames[:] = [name for name in dirnames if not name.startswith(".")]
|
|
328
|
+
if "pyproject.toml" not in filenames:
|
|
329
|
+
continue
|
|
330
|
+
root = Path(raw_root)
|
|
331
|
+
distribution = _distribution_name(root / "pyproject.toml")
|
|
332
|
+
if distribution is not None:
|
|
333
|
+
distributions.setdefault(distribution, set()).add(root.resolve(strict=True))
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _visible_sources(
|
|
337
|
+
project_dir: Path,
|
|
338
|
+
managed_paths: set[Path],
|
|
339
|
+
) -> tuple[dict[str, set[Path]], set[Path]]:
|
|
340
|
+
apps_root = project_dir / "apps"
|
|
341
|
+
distributions: dict[str, set[Path]] = {}
|
|
342
|
+
link_targets: set[Path] = set()
|
|
343
|
+
if not apps_root.is_dir():
|
|
344
|
+
return distributions, link_targets
|
|
345
|
+
|
|
346
|
+
for raw_root, dirnames, filenames in os.walk(apps_root, followlinks=False):
|
|
347
|
+
root = Path(raw_root)
|
|
348
|
+
if root == apps_root / ".sources" or (apps_root / ".sources") in root.parents:
|
|
349
|
+
dirnames[:] = []
|
|
350
|
+
continue
|
|
351
|
+
if root in managed_paths or any(
|
|
352
|
+
managed in root.parents for managed in managed_paths
|
|
353
|
+
):
|
|
354
|
+
dirnames[:] = []
|
|
355
|
+
continue
|
|
356
|
+
|
|
357
|
+
retained: list[str] = []
|
|
358
|
+
for dirname in dirnames:
|
|
359
|
+
child = root / dirname
|
|
360
|
+
if dirname.startswith(".") or child in managed_paths:
|
|
361
|
+
continue
|
|
362
|
+
if child.is_symlink():
|
|
363
|
+
try:
|
|
364
|
+
target = child.resolve(strict=True)
|
|
365
|
+
except _RESOLUTION_ERRORS:
|
|
366
|
+
pass
|
|
367
|
+
else:
|
|
368
|
+
link_targets.add(target)
|
|
369
|
+
if target.is_dir():
|
|
370
|
+
_record_tree_distributions(target, distributions)
|
|
371
|
+
continue
|
|
372
|
+
retained.append(dirname)
|
|
373
|
+
dirnames[:] = retained
|
|
374
|
+
|
|
375
|
+
if "pyproject.toml" not in filenames:
|
|
376
|
+
continue
|
|
377
|
+
distribution = _distribution_name(root / "pyproject.toml")
|
|
378
|
+
if distribution is None:
|
|
379
|
+
continue
|
|
380
|
+
distributions.setdefault(distribution, set()).add(root.resolve(strict=True))
|
|
381
|
+
return distributions, link_targets
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def _source_identities(projection: _Projection) -> tuple[tuple[str, Path], ...]:
|
|
385
|
+
if projection.binding.kind == "app":
|
|
386
|
+
return ((projection.key, projection.target_realpath),)
|
|
387
|
+
distributions: dict[str, set[Path]] = {}
|
|
388
|
+
_record_tree_distributions(projection.target_realpath, distributions)
|
|
389
|
+
return tuple(
|
|
390
|
+
(app_id, root)
|
|
391
|
+
for app_id, roots in sorted(distributions.items())
|
|
392
|
+
for root in sorted(roots)
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def _check_source_conflicts(
|
|
397
|
+
project_dir: Path,
|
|
398
|
+
before: Mapping[Path, _Projection],
|
|
399
|
+
after: Mapping[Path, _Projection],
|
|
400
|
+
*,
|
|
401
|
+
action: str,
|
|
402
|
+
) -> None:
|
|
403
|
+
distributions, link_targets = _visible_sources(project_dir, set(before))
|
|
404
|
+
for projection in after.values():
|
|
405
|
+
for link_target in link_targets:
|
|
406
|
+
if _paths_overlap(link_target, projection.target_realpath):
|
|
407
|
+
raise _error(
|
|
408
|
+
BINDING_SOURCE_CONFLICT_CODE,
|
|
409
|
+
f"binding source is already exposed by another link: {projection.key}",
|
|
410
|
+
action=action,
|
|
411
|
+
detail=(
|
|
412
|
+
f"source={projection.target_realpath}; "
|
|
413
|
+
f"existing_link_target={link_target}"
|
|
414
|
+
),
|
|
415
|
+
)
|
|
416
|
+
for app_id, expected_root in _source_identities(projection):
|
|
417
|
+
existing_roots = distributions.get(app_id, set())
|
|
418
|
+
if not existing_roots:
|
|
419
|
+
continue
|
|
420
|
+
raise _error(
|
|
421
|
+
BINDING_SOURCE_CONFLICT_CODE,
|
|
422
|
+
f"app id is already exposed by a local source: {app_id}",
|
|
423
|
+
action=action,
|
|
424
|
+
detail=(
|
|
425
|
+
f"expected={expected_root}; "
|
|
426
|
+
f"existing={', '.join(str(path) for path in sorted(existing_roots))}"
|
|
427
|
+
),
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def _preflight(
|
|
432
|
+
project_dir: Path,
|
|
433
|
+
before: DevManifest,
|
|
434
|
+
after: DevManifest,
|
|
435
|
+
*,
|
|
436
|
+
action: str,
|
|
437
|
+
) -> _Preflight:
|
|
438
|
+
project_root = project_dir.expanduser().resolve(strict=False)
|
|
439
|
+
try:
|
|
440
|
+
validate_dev_manifest(project_root, before)
|
|
441
|
+
except _RESOLUTION_ERRORS as exc:
|
|
442
|
+
raise _error(
|
|
443
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
444
|
+
"current manifest resolves through a broken source path",
|
|
445
|
+
action=action,
|
|
446
|
+
detail=str(exc),
|
|
447
|
+
) from exc
|
|
448
|
+
try:
|
|
449
|
+
_check_granularity_overlap(project_root, before, after)
|
|
450
|
+
except _RESOLUTION_ERRORS as exc:
|
|
451
|
+
raise _error(
|
|
452
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
453
|
+
"binding overlap check encountered a broken source path",
|
|
454
|
+
action=action,
|
|
455
|
+
detail=str(exc),
|
|
456
|
+
) from exc
|
|
457
|
+
try:
|
|
458
|
+
validate_dev_manifest(project_root, after)
|
|
459
|
+
except _RESOLUTION_ERRORS as exc:
|
|
460
|
+
raise _error(
|
|
461
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
462
|
+
"desired manifest resolves through a broken source path",
|
|
463
|
+
action=action,
|
|
464
|
+
detail=str(exc),
|
|
465
|
+
) from exc
|
|
466
|
+
before_projections = _projections(project_root, before, action=action)
|
|
467
|
+
after_projections = _projections(project_root, after, action=action)
|
|
468
|
+
|
|
469
|
+
for projection in before_projections.values():
|
|
470
|
+
_verify_exact_link(projection, action=action)
|
|
471
|
+
|
|
472
|
+
for final_path, projection in after_projections.items():
|
|
473
|
+
existing = before_projections.get(final_path)
|
|
474
|
+
if existing is not None:
|
|
475
|
+
continue
|
|
476
|
+
if _lexists(final_path):
|
|
477
|
+
raise _error(
|
|
478
|
+
BINDING_CONFLICT_CODE,
|
|
479
|
+
f"binding destination is occupied by an unmanaged path: {final_path}",
|
|
480
|
+
action=action,
|
|
481
|
+
detail=f"desired_source={projection.target_realpath}",
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
_check_source_conflicts(
|
|
485
|
+
project_root,
|
|
486
|
+
before_projections,
|
|
487
|
+
after_projections,
|
|
488
|
+
action=action,
|
|
489
|
+
)
|
|
490
|
+
return _Preflight(before=before_projections, after=after_projections)
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def _staged_path(final_path: Path) -> Path:
|
|
494
|
+
while True:
|
|
495
|
+
candidate = final_path.with_name(
|
|
496
|
+
f".{final_path.name}.pz-link-{uuid.uuid4().hex}"
|
|
497
|
+
)
|
|
498
|
+
if not _lexists(candidate):
|
|
499
|
+
return candidate
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def plan_binding_projection(
|
|
503
|
+
project_dir: Path,
|
|
504
|
+
before: DevManifest,
|
|
505
|
+
after: DevManifest,
|
|
506
|
+
) -> tuple[LinkMutation, ...]:
|
|
507
|
+
checked = _preflight(
|
|
508
|
+
project_dir,
|
|
509
|
+
before,
|
|
510
|
+
after,
|
|
511
|
+
action="plan_binding_projection",
|
|
512
|
+
)
|
|
513
|
+
mutations: list[LinkMutation] = []
|
|
514
|
+
for final_path, desired in sorted(checked.after.items()):
|
|
515
|
+
previous = checked.before.get(final_path)
|
|
516
|
+
if previous is not None:
|
|
517
|
+
previous_target = previous.relative_target
|
|
518
|
+
if (
|
|
519
|
+
previous.relative_target == desired.relative_target
|
|
520
|
+
and previous.target_realpath == desired.target_realpath
|
|
521
|
+
):
|
|
522
|
+
continue
|
|
523
|
+
else:
|
|
524
|
+
previous_target = None
|
|
525
|
+
mutations.append(
|
|
526
|
+
LinkMutation(
|
|
527
|
+
final_path=final_path,
|
|
528
|
+
staged_path=_staged_path(final_path),
|
|
529
|
+
relative_target=desired.relative_target,
|
|
530
|
+
previous_target=previous_target,
|
|
531
|
+
)
|
|
532
|
+
)
|
|
533
|
+
return tuple(mutations)
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def plan_binding_removals(
|
|
537
|
+
project_dir: Path,
|
|
538
|
+
before: DevManifest,
|
|
539
|
+
after: DevManifest,
|
|
540
|
+
) -> tuple[LinkRemoval, ...]:
|
|
541
|
+
checked = _preflight(
|
|
542
|
+
project_dir,
|
|
543
|
+
before,
|
|
544
|
+
after,
|
|
545
|
+
action="plan_binding_removals",
|
|
546
|
+
)
|
|
547
|
+
removals: list[LinkRemoval] = []
|
|
548
|
+
for final_path, existing in sorted(checked.before.items()):
|
|
549
|
+
if final_path in checked.after:
|
|
550
|
+
continue
|
|
551
|
+
removals.append(
|
|
552
|
+
LinkRemoval(
|
|
553
|
+
final_path=final_path,
|
|
554
|
+
previous_target=existing.relative_target,
|
|
555
|
+
expected_target=existing.target_realpath,
|
|
556
|
+
)
|
|
557
|
+
)
|
|
558
|
+
return tuple(removals)
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def _verify_mutation_before_stage(mutation: LinkMutation) -> None:
|
|
562
|
+
if mutation.staged_path.parent != mutation.final_path.parent:
|
|
563
|
+
raise _error(
|
|
564
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
565
|
+
"staged binding must be a sibling of its final path",
|
|
566
|
+
action="stage_binding_projection",
|
|
567
|
+
detail=f"final={mutation.final_path}; staged={mutation.staged_path}",
|
|
568
|
+
)
|
|
569
|
+
if Path(mutation.relative_target).is_absolute() or "\\" in mutation.relative_target:
|
|
570
|
+
raise _error(
|
|
571
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
572
|
+
"staged binding target must be a POSIX relative path",
|
|
573
|
+
action="stage_binding_projection",
|
|
574
|
+
detail=mutation.relative_target,
|
|
575
|
+
)
|
|
576
|
+
if _lexists(mutation.staged_path):
|
|
577
|
+
raise _error(
|
|
578
|
+
BINDING_CONFLICT_CODE,
|
|
579
|
+
f"staged binding path is occupied: {mutation.staged_path}",
|
|
580
|
+
action="stage_binding_projection",
|
|
581
|
+
)
|
|
582
|
+
if mutation.previous_target is None:
|
|
583
|
+
if _lexists(mutation.final_path):
|
|
584
|
+
raise _error(
|
|
585
|
+
BINDING_CONFLICT_CODE,
|
|
586
|
+
f"binding destination became occupied: {mutation.final_path}",
|
|
587
|
+
action="stage_binding_projection",
|
|
588
|
+
)
|
|
589
|
+
elif (
|
|
590
|
+
not mutation.final_path.is_symlink()
|
|
591
|
+
or os.readlink(mutation.final_path) != mutation.previous_target
|
|
592
|
+
):
|
|
593
|
+
raise _error(
|
|
594
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
595
|
+
f"managed binding changed after planning: {mutation.final_path}",
|
|
596
|
+
action="stage_binding_projection",
|
|
597
|
+
)
|
|
598
|
+
target = mutation.final_path.parent / mutation.relative_target
|
|
599
|
+
try:
|
|
600
|
+
target_realpath = target.resolve(strict=True)
|
|
601
|
+
except _RESOLUTION_ERRORS as exc:
|
|
602
|
+
raise _error(
|
|
603
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
604
|
+
f"binding target changed after planning: {target}",
|
|
605
|
+
action="stage_binding_projection",
|
|
606
|
+
detail=str(exc),
|
|
607
|
+
) from exc
|
|
608
|
+
if not target_realpath.is_dir():
|
|
609
|
+
raise _error(
|
|
610
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
611
|
+
f"binding target is not a directory: {target}",
|
|
612
|
+
action="stage_binding_projection",
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def _unlink_exact_link(path: Path, relative_target: str) -> None:
|
|
617
|
+
if not path.is_symlink() or os.readlink(path) != relative_target:
|
|
618
|
+
raise _error(
|
|
619
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
620
|
+
f"refusing to unlink an unverified symbolic link: {path}",
|
|
621
|
+
action="discard_staged_projection",
|
|
622
|
+
)
|
|
623
|
+
path.unlink()
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def discard_staged_projection(mutations: Sequence[LinkMutation]) -> None:
|
|
627
|
+
for mutation in reversed(tuple(mutations)):
|
|
628
|
+
if not _lexists(mutation.staged_path):
|
|
629
|
+
continue
|
|
630
|
+
_unlink_exact_link(mutation.staged_path, mutation.relative_target)
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
def stage_binding_projection(mutations: Sequence[LinkMutation]) -> None:
|
|
634
|
+
ordered = tuple(mutations)
|
|
635
|
+
for mutation in ordered:
|
|
636
|
+
_verify_mutation_before_stage(mutation)
|
|
637
|
+
staged: list[LinkMutation] = []
|
|
638
|
+
try:
|
|
639
|
+
for mutation in ordered:
|
|
640
|
+
os.symlink(
|
|
641
|
+
mutation.relative_target,
|
|
642
|
+
mutation.staged_path,
|
|
643
|
+
target_is_directory=True,
|
|
644
|
+
)
|
|
645
|
+
staged.append(mutation)
|
|
646
|
+
except BaseException:
|
|
647
|
+
discard_staged_projection(staged)
|
|
648
|
+
raise
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
def _verify_staged(mutation: LinkMutation, *, action: str) -> None:
|
|
652
|
+
if not mutation.staged_path.is_symlink():
|
|
653
|
+
raise _error(
|
|
654
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
655
|
+
f"staged binding is missing: {mutation.staged_path}",
|
|
656
|
+
action=action,
|
|
657
|
+
)
|
|
658
|
+
if os.readlink(mutation.staged_path) != mutation.relative_target:
|
|
659
|
+
raise _error(
|
|
660
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
661
|
+
f"staged binding target changed: {mutation.staged_path}",
|
|
662
|
+
action=action,
|
|
663
|
+
)
|
|
664
|
+
try:
|
|
665
|
+
expected = (mutation.final_path.parent / mutation.relative_target).resolve(
|
|
666
|
+
strict=True
|
|
667
|
+
)
|
|
668
|
+
actual = mutation.staged_path.resolve(strict=True)
|
|
669
|
+
except _RESOLUTION_ERRORS as exc:
|
|
670
|
+
raise _error(
|
|
671
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
672
|
+
f"staged binding is broken: {mutation.staged_path}",
|
|
673
|
+
action=action,
|
|
674
|
+
detail=str(exc),
|
|
675
|
+
) from exc
|
|
676
|
+
if actual != expected:
|
|
677
|
+
raise _error(
|
|
678
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
679
|
+
f"staged binding resolves to the wrong target: {mutation.staged_path}",
|
|
680
|
+
action=action,
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
def _verify_mutation_destination(mutation: LinkMutation, *, action: str) -> None:
|
|
685
|
+
if mutation.previous_target is None:
|
|
686
|
+
if _lexists(mutation.final_path):
|
|
687
|
+
raise _error(
|
|
688
|
+
BINDING_CONFLICT_CODE,
|
|
689
|
+
f"binding destination became occupied: {mutation.final_path}",
|
|
690
|
+
action=action,
|
|
691
|
+
)
|
|
692
|
+
return
|
|
693
|
+
if (
|
|
694
|
+
not mutation.final_path.is_symlink()
|
|
695
|
+
or os.readlink(mutation.final_path) != mutation.previous_target
|
|
696
|
+
):
|
|
697
|
+
raise _error(
|
|
698
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
699
|
+
f"managed binding changed after staging: {mutation.final_path}",
|
|
700
|
+
action=action,
|
|
701
|
+
)
|
|
702
|
+
try:
|
|
703
|
+
mutation.final_path.resolve(strict=True)
|
|
704
|
+
except _RESOLUTION_ERRORS as exc:
|
|
705
|
+
raise _error(
|
|
706
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
707
|
+
f"managed binding became broken: {mutation.final_path}",
|
|
708
|
+
action=action,
|
|
709
|
+
detail=str(exc),
|
|
710
|
+
) from exc
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
def _verify_removal(removal: LinkRemoval, *, action: str) -> None:
|
|
714
|
+
if not removal.final_path.is_symlink():
|
|
715
|
+
raise _error(
|
|
716
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
717
|
+
f"managed binding cannot be safely removed: {removal.final_path}",
|
|
718
|
+
action=action,
|
|
719
|
+
)
|
|
720
|
+
stored = os.readlink(removal.final_path)
|
|
721
|
+
if Path(stored).is_absolute() or stored != removal.previous_target:
|
|
722
|
+
raise _error(
|
|
723
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
724
|
+
f"managed binding target changed before removal: {removal.final_path}",
|
|
725
|
+
action=action,
|
|
726
|
+
)
|
|
727
|
+
try:
|
|
728
|
+
resolved = removal.final_path.resolve(strict=True)
|
|
729
|
+
except _RESOLUTION_ERRORS as exc:
|
|
730
|
+
raise _error(
|
|
731
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
732
|
+
f"managed binding became broken before removal: {removal.final_path}",
|
|
733
|
+
action=action,
|
|
734
|
+
detail=str(exc),
|
|
735
|
+
) from exc
|
|
736
|
+
if resolved != removal.expected_target:
|
|
737
|
+
raise _error(
|
|
738
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
739
|
+
f"managed binding resolves to the wrong source: {removal.final_path}",
|
|
740
|
+
action=action,
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
def _replacement_link(path: Path, relative_target: str) -> None:
|
|
745
|
+
staged = _staged_path(path)
|
|
746
|
+
try:
|
|
747
|
+
os.symlink(relative_target, staged, target_is_directory=True)
|
|
748
|
+
os.replace(staged, path)
|
|
749
|
+
finally:
|
|
750
|
+
if _lexists(staged):
|
|
751
|
+
_unlink_exact_link(staged, relative_target)
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
def _verify_committed_mutation(mutation: LinkMutation, *, action: str) -> None:
|
|
755
|
+
if (
|
|
756
|
+
not mutation.final_path.is_symlink()
|
|
757
|
+
or os.readlink(mutation.final_path) != mutation.relative_target
|
|
758
|
+
):
|
|
759
|
+
raise _error(
|
|
760
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
761
|
+
f"committed binding changed before rollback: {mutation.final_path}",
|
|
762
|
+
action=action,
|
|
763
|
+
)
|
|
764
|
+
try:
|
|
765
|
+
expected = (mutation.final_path.parent / mutation.relative_target).resolve(
|
|
766
|
+
strict=True
|
|
767
|
+
)
|
|
768
|
+
actual = mutation.final_path.resolve(strict=True)
|
|
769
|
+
except _RESOLUTION_ERRORS as exc:
|
|
770
|
+
raise _error(
|
|
771
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
772
|
+
f"committed binding is broken: {mutation.final_path}",
|
|
773
|
+
action=action,
|
|
774
|
+
detail=str(exc),
|
|
775
|
+
) from exc
|
|
776
|
+
if actual != expected:
|
|
777
|
+
raise _error(
|
|
778
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
779
|
+
f"committed binding resolves to the wrong source: {mutation.final_path}",
|
|
780
|
+
action=action,
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
def _restore_mutation(mutation: LinkMutation, *, action: str) -> None:
|
|
785
|
+
_verify_committed_mutation(mutation, action=action)
|
|
786
|
+
if mutation.previous_target is None:
|
|
787
|
+
mutation.final_path.unlink()
|
|
788
|
+
else:
|
|
789
|
+
_replacement_link(mutation.final_path, mutation.previous_target)
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
def _restore_removal(removal: LinkRemoval, *, action: str) -> None:
|
|
793
|
+
if _lexists(removal.final_path):
|
|
794
|
+
raise _error(
|
|
795
|
+
BINDING_CONFLICT_CODE,
|
|
796
|
+
f"removed binding destination became occupied: {removal.final_path}",
|
|
797
|
+
action=action,
|
|
798
|
+
)
|
|
799
|
+
_replacement_link(removal.final_path, removal.previous_target)
|
|
800
|
+
_verify_removal(removal, action=action)
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
def _rollback_completed(
|
|
804
|
+
mutations: Sequence[LinkMutation],
|
|
805
|
+
removals: Sequence[LinkRemoval],
|
|
806
|
+
*,
|
|
807
|
+
action: str,
|
|
808
|
+
) -> None:
|
|
809
|
+
failures: list[Exception] = []
|
|
810
|
+
for mutation in reversed(tuple(mutations)):
|
|
811
|
+
try:
|
|
812
|
+
_restore_mutation(mutation, action=action)
|
|
813
|
+
except Exception as exc: # pragma: no cover - emergency diagnostic path
|
|
814
|
+
failures.append(exc)
|
|
815
|
+
for removal in reversed(tuple(removals)):
|
|
816
|
+
try:
|
|
817
|
+
_restore_removal(removal, action=action)
|
|
818
|
+
except Exception as exc: # pragma: no cover - emergency diagnostic path
|
|
819
|
+
failures.append(exc)
|
|
820
|
+
if failures:
|
|
821
|
+
raise _error(
|
|
822
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
823
|
+
"binding rollback could not restore the exact previous projection",
|
|
824
|
+
action=action,
|
|
825
|
+
detail="; ".join(str(failure) for failure in failures),
|
|
826
|
+
)
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
def commit_binding_projection(
|
|
830
|
+
mutations: Sequence[LinkMutation],
|
|
831
|
+
removals: Sequence[LinkRemoval] = (),
|
|
832
|
+
) -> None:
|
|
833
|
+
ordered_mutations = tuple(mutations)
|
|
834
|
+
ordered_removals = tuple(removals)
|
|
835
|
+
mutation_paths = {mutation.final_path for mutation in ordered_mutations}
|
|
836
|
+
removal_paths = {removal.final_path for removal in ordered_removals}
|
|
837
|
+
if len(mutation_paths) != len(ordered_mutations) or len(removal_paths) != len(
|
|
838
|
+
ordered_removals
|
|
839
|
+
):
|
|
840
|
+
raise _error(
|
|
841
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
842
|
+
"binding commit plan contains duplicate final paths",
|
|
843
|
+
action="commit_binding_projection",
|
|
844
|
+
)
|
|
845
|
+
if mutation_paths & removal_paths:
|
|
846
|
+
raise _error(
|
|
847
|
+
BINDING_INVALID_PROJECTION_CODE,
|
|
848
|
+
"binding commit plan cannot mutate and remove the same path",
|
|
849
|
+
action="commit_binding_projection",
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
for removal in ordered_removals:
|
|
853
|
+
_verify_removal(removal, action="commit_binding_projection")
|
|
854
|
+
for mutation in ordered_mutations:
|
|
855
|
+
_verify_staged(mutation, action="commit_binding_projection")
|
|
856
|
+
_verify_mutation_destination(mutation, action="commit_binding_projection")
|
|
857
|
+
|
|
858
|
+
completed_removals: list[LinkRemoval] = []
|
|
859
|
+
completed_mutations: list[LinkMutation] = []
|
|
860
|
+
try:
|
|
861
|
+
for removal in ordered_removals:
|
|
862
|
+
removal.final_path.unlink()
|
|
863
|
+
completed_removals.append(removal)
|
|
864
|
+
for mutation in ordered_mutations:
|
|
865
|
+
os.replace(mutation.staged_path, mutation.final_path)
|
|
866
|
+
completed_mutations.append(mutation)
|
|
867
|
+
except BaseException as original:
|
|
868
|
+
try:
|
|
869
|
+
_rollback_completed(
|
|
870
|
+
completed_mutations,
|
|
871
|
+
completed_removals,
|
|
872
|
+
action="commit_binding_projection",
|
|
873
|
+
)
|
|
874
|
+
except Exception as rollback_error:
|
|
875
|
+
raise rollback_error from original
|
|
876
|
+
finally:
|
|
877
|
+
discard_staged_projection(ordered_mutations)
|
|
878
|
+
raise
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
def rollback_binding_projection(
|
|
882
|
+
mutations: Sequence[LinkMutation],
|
|
883
|
+
removals: Sequence[LinkRemoval] = (),
|
|
884
|
+
) -> None:
|
|
885
|
+
ordered_mutations = tuple(mutations)
|
|
886
|
+
ordered_removals = tuple(removals)
|
|
887
|
+
for mutation in ordered_mutations:
|
|
888
|
+
_verify_committed_mutation(mutation, action="rollback_binding_projection")
|
|
889
|
+
for removal in ordered_removals:
|
|
890
|
+
if _lexists(removal.final_path):
|
|
891
|
+
raise _error(
|
|
892
|
+
BINDING_CONFLICT_CODE,
|
|
893
|
+
f"removed binding destination became occupied: {removal.final_path}",
|
|
894
|
+
action="rollback_binding_projection",
|
|
895
|
+
)
|
|
896
|
+
_rollback_completed(
|
|
897
|
+
ordered_mutations,
|
|
898
|
+
ordered_removals,
|
|
899
|
+
action="rollback_binding_projection",
|
|
900
|
+
)
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
__all__ = [
|
|
904
|
+
"BINDING_CONFLICT_CODE",
|
|
905
|
+
"BINDING_INVALID_PROJECTION_CODE",
|
|
906
|
+
"BINDING_OVERLAP_CODE",
|
|
907
|
+
"BINDING_SOURCE_CONFLICT_CODE",
|
|
908
|
+
"LinkMutation",
|
|
909
|
+
"LinkRemoval",
|
|
910
|
+
"commit_binding_projection",
|
|
911
|
+
"discard_staged_projection",
|
|
912
|
+
"plan_binding_projection",
|
|
913
|
+
"plan_binding_removals",
|
|
914
|
+
"rollback_binding_projection",
|
|
915
|
+
"stage_binding_projection",
|
|
916
|
+
]
|