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,888 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
from contextvars import ContextVar
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from functools import lru_cache
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Callable, Iterator, TypeVar
|
|
11
|
+
|
|
12
|
+
from pz_core.assembly.project import compile_project_context
|
|
13
|
+
from pz_core.assembly.settings import SettingsSnapshot, compile_settings_snapshot, load_project_env_file
|
|
14
|
+
from pz_core.core.extensions import SettingsPatch, SettingsProviderContext, SettingsProviderSpec
|
|
15
|
+
from pz_core.runtime.errors import PzError
|
|
16
|
+
from pz_core.runtime.plan_context import current_runtime_plan
|
|
17
|
+
from pz_core.utils import (
|
|
18
|
+
has_env_value,
|
|
19
|
+
iter_env_items,
|
|
20
|
+
load_jsonc_file,
|
|
21
|
+
read_env_bool,
|
|
22
|
+
read_env_float,
|
|
23
|
+
read_env_int,
|
|
24
|
+
read_env_string,
|
|
25
|
+
)
|
|
26
|
+
from pz_core.utils.project_jsonc import PROJECT_JSONC_NAME
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
TAppSettings = TypeVar("TAppSettings")
|
|
30
|
+
|
|
31
|
+
_SETTINGS_SNAPSHOTS: dict[str, SettingsSnapshot] = {}
|
|
32
|
+
_SETTINGS_SNAPSHOT_REFCOUNTS: dict[str, int] = {}
|
|
33
|
+
_SETTINGS_NAMESPACE_CACHE: dict[int, tuple[SettingsSnapshot, frozenset[str]]] = {}
|
|
34
|
+
_SETTINGS_PROVIDER_SPECS_CACHE: dict[int, tuple[object, tuple[SettingsProviderSpec, ...]]] = {}
|
|
35
|
+
_SETTINGS_PROVIDER_VALUES_CACHE: dict[tuple[int, str | None], tuple[SettingsSnapshot, dict[str, dict[str, Any]]]] = {}
|
|
36
|
+
_RESOLVED_SETTINGS_CACHE: dict[tuple[int, str | None], tuple[SettingsSnapshot, "ResolvedSettings"]] = {}
|
|
37
|
+
_ACTIVE_SETTINGS_PROJECT_DIR: ContextVar[str | None] = ContextVar(
|
|
38
|
+
"pz_runtime_settings_active_project_dir",
|
|
39
|
+
default=None,
|
|
40
|
+
)
|
|
41
|
+
_SETTINGS_PROVIDER_RESOLUTION_DEPTH: ContextVar[int] = ContextVar(
|
|
42
|
+
"pz_runtime_settings_provider_resolution_depth",
|
|
43
|
+
default=0,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
_ROUTE_TOKEN_CATALOG_PROVIDER: Callable[[str | Path | None], Any] | None = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _invalidate_settings_runtime_caches() -> None:
|
|
50
|
+
_SETTINGS_NAMESPACE_CACHE.clear()
|
|
51
|
+
_SETTINGS_PROVIDER_SPECS_CACHE.clear()
|
|
52
|
+
_SETTINGS_PROVIDER_VALUES_CACHE.clear()
|
|
53
|
+
_RESOLVED_SETTINGS_CACHE.clear()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@lru_cache(maxsize=None)
|
|
57
|
+
def _resolve_path_text(value: str) -> str:
|
|
58
|
+
return str(Path(value).resolve())
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _local_default_config_path(anchor: str | os.PathLike[str]) -> Path:
|
|
62
|
+
location = Path(anchor).resolve()
|
|
63
|
+
base = location.parent if location.is_file() else location
|
|
64
|
+
if base.name == "settings":
|
|
65
|
+
base = base.parent
|
|
66
|
+
return base / "configs" / "default.jsonc"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@lru_cache(maxsize=None)
|
|
70
|
+
def _cached_local_default_settings(anchor: str) -> dict[str, Any]:
|
|
71
|
+
jsonc_path = _local_default_config_path(anchor)
|
|
72
|
+
if not jsonc_path.is_file():
|
|
73
|
+
return {}
|
|
74
|
+
return load_jsonc_file(
|
|
75
|
+
jsonc_path,
|
|
76
|
+
code="PZ_APP_DEFAULTS_INVALID",
|
|
77
|
+
owner="runtime.settings",
|
|
78
|
+
action="load_local_default_settings",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def load_local_default_settings(anchor: str | os.PathLike[str]) -> dict[str, Any]:
|
|
83
|
+
"""Read package default templates for authoring-time settings tools.
|
|
84
|
+
|
|
85
|
+
This helper is kept for compatibility with settings init/update style code.
|
|
86
|
+
Runtime settings compilation must not call it.
|
|
87
|
+
"""
|
|
88
|
+
return copy.deepcopy(_cached_local_default_settings(_resolve_path_text(str(anchor))))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def build_local_default_settings(anchor: str | os.PathLike[str], settings_type: type[TAppSettings]) -> TAppSettings:
|
|
92
|
+
return settings_type(**load_local_default_settings(anchor))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _deep_merge_local_defaults(base: dict[str, Any], incoming: dict[str, Any]) -> dict[str, Any]:
|
|
96
|
+
merged = dict(base)
|
|
97
|
+
for key, value in incoming.items():
|
|
98
|
+
if isinstance(value, dict) and isinstance(merged.get(key), dict):
|
|
99
|
+
merged[key] = _deep_merge_local_defaults(merged[key], value)
|
|
100
|
+
continue
|
|
101
|
+
merged[key] = copy.deepcopy(value)
|
|
102
|
+
return merged
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def load_local_default_settings_chain(*anchors: str | os.PathLike[str]) -> dict[str, Any]:
|
|
106
|
+
merged: dict[str, Any] = {}
|
|
107
|
+
for anchor in anchors:
|
|
108
|
+
merged = _deep_merge_local_defaults(merged, load_local_default_settings(anchor))
|
|
109
|
+
return merged
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def build_local_default_settings_chain(
|
|
113
|
+
settings_type: type[TAppSettings],
|
|
114
|
+
*anchors: str | os.PathLike[str],
|
|
115
|
+
) -> TAppSettings:
|
|
116
|
+
return settings_type(**load_local_default_settings_chain(*anchors))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _deep_merge_payload(base: dict[str, Any], incoming: dict[str, Any]) -> dict[str, Any]:
|
|
120
|
+
merged = dict(base)
|
|
121
|
+
for key, value in incoming.items():
|
|
122
|
+
if isinstance(value, dict) and isinstance(merged.get(key), dict):
|
|
123
|
+
merged[key] = _deep_merge_payload(dict(merged[key]), value)
|
|
124
|
+
continue
|
|
125
|
+
merged[key] = copy.deepcopy(value)
|
|
126
|
+
return merged
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _normalize_project_dir(project_dir: str | Path | None) -> str | None:
|
|
130
|
+
if project_dir is None:
|
|
131
|
+
return None
|
|
132
|
+
value = str(project_dir).strip()
|
|
133
|
+
if not value:
|
|
134
|
+
return None
|
|
135
|
+
return _resolve_path_text(value)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _runtime_plan_project_dir() -> str | None:
|
|
139
|
+
runtime_plan = current_runtime_plan()
|
|
140
|
+
return _normalize_project_dir(getattr(getattr(runtime_plan, "project", None), "project_dir", None))
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _configured_project_dir() -> str | None:
|
|
144
|
+
raw_env_project_dir = os.getenv("PZ_PROJECT_DIR")
|
|
145
|
+
if raw_env_project_dir is not None:
|
|
146
|
+
return _normalize_project_dir(compile_project_context(project_dir=None).project_dir)
|
|
147
|
+
|
|
148
|
+
current = Path.cwd().resolve()
|
|
149
|
+
for candidate in (current, *current.parents):
|
|
150
|
+
if (candidate / PROJECT_JSONC_NAME).is_file():
|
|
151
|
+
return _normalize_project_dir(candidate)
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _effective_project_dir(project_dir: str | Path | None = None) -> str | None:
|
|
156
|
+
explicit_project_dir = _normalize_project_dir(project_dir)
|
|
157
|
+
if explicit_project_dir:
|
|
158
|
+
return explicit_project_dir
|
|
159
|
+
active = _ACTIVE_SETTINGS_PROJECT_DIR.get()
|
|
160
|
+
if active:
|
|
161
|
+
return active
|
|
162
|
+
runtime_project_dir = _runtime_plan_project_dir()
|
|
163
|
+
if runtime_project_dir:
|
|
164
|
+
return runtime_project_dir
|
|
165
|
+
configured_project_dir = _configured_project_dir()
|
|
166
|
+
if configured_project_dir:
|
|
167
|
+
return configured_project_dir
|
|
168
|
+
if len(_SETTINGS_SNAPSHOTS) == 1:
|
|
169
|
+
return next(iter(_SETTINGS_SNAPSHOTS.keys()))
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def current_settings_project_dir() -> str | None:
|
|
174
|
+
active = _ACTIVE_SETTINGS_PROJECT_DIR.get()
|
|
175
|
+
if active:
|
|
176
|
+
return active
|
|
177
|
+
runtime_project_dir = _runtime_plan_project_dir()
|
|
178
|
+
if runtime_project_dir:
|
|
179
|
+
return runtime_project_dir
|
|
180
|
+
if len(_SETTINGS_SNAPSHOTS) == 1:
|
|
181
|
+
return next(iter(_SETTINGS_SNAPSHOTS.keys()))
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _normalize_current_pz_env(value: object, *, default: str = "develop") -> str:
|
|
186
|
+
raw = str(value or "").strip().lower()
|
|
187
|
+
if raw in {"dev", "development"}:
|
|
188
|
+
return "develop"
|
|
189
|
+
if raw in {"prod", "production"}:
|
|
190
|
+
return "product"
|
|
191
|
+
if raw in {"develop", "test", "product"}:
|
|
192
|
+
return raw
|
|
193
|
+
fallback = str(default or "develop").strip().lower()
|
|
194
|
+
if fallback in {"test", "product"}:
|
|
195
|
+
return fallback
|
|
196
|
+
return "develop"
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def current_pz_env(*, project_dir: str | Path | None = None, default: str = "develop") -> str:
|
|
200
|
+
bound_snapshot = _resolve_bound_snapshot(project_dir)
|
|
201
|
+
if bound_snapshot is not None:
|
|
202
|
+
try:
|
|
203
|
+
resolved = resolve_settings(project_dir=_normalize_project_dir(project_dir))
|
|
204
|
+
return _normalize_current_pz_env(getattr(resolved.project, "pz_env", None), default=default)
|
|
205
|
+
except Exception:
|
|
206
|
+
pass
|
|
207
|
+
payload = getattr(bound_snapshot, "data", {}) or {}
|
|
208
|
+
project_payload = payload.get("project") if isinstance(payload, dict) else {}
|
|
209
|
+
return _normalize_current_pz_env(
|
|
210
|
+
project_payload.get("pz_env") if isinstance(project_payload, dict) else None,
|
|
211
|
+
default=default,
|
|
212
|
+
)
|
|
213
|
+
resolved = resolve_settings(project_dir=_normalize_project_dir(project_dir))
|
|
214
|
+
return _normalize_current_pz_env(getattr(resolved.project, "pz_env", None), default=default)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def bind_settings_snapshot(
|
|
218
|
+
snapshot: SettingsSnapshot,
|
|
219
|
+
*,
|
|
220
|
+
project_dir: str | Path | None = None,
|
|
221
|
+
) -> SettingsSnapshot:
|
|
222
|
+
normalized_project_dir = _normalize_project_dir(
|
|
223
|
+
project_dir if project_dir is not None else _snapshot_project_dir(snapshot)
|
|
224
|
+
)
|
|
225
|
+
if normalized_project_dir:
|
|
226
|
+
_SETTINGS_SNAPSHOTS[normalized_project_dir] = snapshot
|
|
227
|
+
_SETTINGS_SNAPSHOT_REFCOUNTS[normalized_project_dir] = (
|
|
228
|
+
int(_SETTINGS_SNAPSHOT_REFCOUNTS.get(normalized_project_dir, 0)) + 1
|
|
229
|
+
)
|
|
230
|
+
_invalidate_settings_runtime_caches()
|
|
231
|
+
return snapshot
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def reset_settings_binding(project_dir: str | Path | None = None) -> None:
|
|
235
|
+
normalized_project_dir = _normalize_project_dir(project_dir)
|
|
236
|
+
if normalized_project_dir is None:
|
|
237
|
+
_SETTINGS_SNAPSHOTS.clear()
|
|
238
|
+
_SETTINGS_SNAPSHOT_REFCOUNTS.clear()
|
|
239
|
+
_invalidate_settings_runtime_caches()
|
|
240
|
+
return
|
|
241
|
+
remaining = int(_SETTINGS_SNAPSHOT_REFCOUNTS.get(normalized_project_dir, 0)) - 1
|
|
242
|
+
if remaining > 0:
|
|
243
|
+
_SETTINGS_SNAPSHOT_REFCOUNTS[normalized_project_dir] = remaining
|
|
244
|
+
return
|
|
245
|
+
_SETTINGS_SNAPSHOT_REFCOUNTS.pop(normalized_project_dir, None)
|
|
246
|
+
_SETTINGS_SNAPSHOTS.pop(normalized_project_dir, None)
|
|
247
|
+
_invalidate_settings_runtime_caches()
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@contextmanager
|
|
251
|
+
def active_settings_project(project_dir: str | Path | None) -> Iterator[None]:
|
|
252
|
+
token = _ACTIVE_SETTINGS_PROJECT_DIR.set(_normalize_project_dir(project_dir))
|
|
253
|
+
try:
|
|
254
|
+
yield
|
|
255
|
+
finally:
|
|
256
|
+
_ACTIVE_SETTINGS_PROJECT_DIR.reset(token)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@contextmanager
|
|
260
|
+
def bound_settings_snapshot(
|
|
261
|
+
snapshot: SettingsSnapshot,
|
|
262
|
+
*,
|
|
263
|
+
project_dir: str | Path | None = None,
|
|
264
|
+
) -> Iterator[None]:
|
|
265
|
+
bind_settings_snapshot(snapshot, project_dir=project_dir)
|
|
266
|
+
try:
|
|
267
|
+
yield
|
|
268
|
+
finally:
|
|
269
|
+
reset_settings_binding(project_dir=project_dir)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _env_string(*names: str) -> str:
|
|
273
|
+
return read_env_string("", *names)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _env_int(default: int, *names: str) -> int:
|
|
277
|
+
return read_env_int(default, *names)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def has_runtime_env_value(*names: str) -> bool:
|
|
281
|
+
return has_env_value(*names)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def iter_runtime_env_items() -> tuple[tuple[str, str], ...]:
|
|
285
|
+
return iter_env_items()
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def read_runtime_env_string(*names: str) -> str:
|
|
289
|
+
return _env_string(*names)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def read_runtime_env_int(default: int, *names: str) -> int:
|
|
293
|
+
return _env_int(default, *names)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def read_runtime_env_bool(default: bool, *names: str) -> bool:
|
|
297
|
+
return read_env_bool(default, *names)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def read_runtime_env_float(default: float, *names: str) -> float:
|
|
301
|
+
return read_env_float(default, *names)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def register_route_token_catalog_provider(provider: Callable[[str | Path | None], Any]) -> None:
|
|
305
|
+
global _ROUTE_TOKEN_CATALOG_PROVIDER
|
|
306
|
+
_ROUTE_TOKEN_CATALOG_PROVIDER = provider
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def reset_route_token_catalog_provider() -> None:
|
|
310
|
+
global _ROUTE_TOKEN_CATALOG_PROVIDER
|
|
311
|
+
_ROUTE_TOKEN_CATALOG_PROVIDER = None
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _ensure_route_token_catalog_provider() -> Callable[[str | Path | None], Any]:
|
|
315
|
+
provider = _ROUTE_TOKEN_CATALOG_PROVIDER
|
|
316
|
+
if provider is not None:
|
|
317
|
+
return provider
|
|
318
|
+
raise PzError(
|
|
319
|
+
code="PZ_ROUTE_TOKEN_CATALOG_PROVIDER_MISSING",
|
|
320
|
+
message=(
|
|
321
|
+
"Route token catalog provider is not registered. "
|
|
322
|
+
"Owner packages must register it from their own formal surface."
|
|
323
|
+
),
|
|
324
|
+
stage="settings",
|
|
325
|
+
owner="runtime.settings",
|
|
326
|
+
action="get_route_token_catalog",
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
class _RouteTokenCatalogProxy:
|
|
331
|
+
def __init__(self, project_dir: str | Path | None = None) -> None:
|
|
332
|
+
self._project_dir = _normalize_project_dir(project_dir)
|
|
333
|
+
|
|
334
|
+
def _facade(self) -> Any:
|
|
335
|
+
return _ensure_route_token_catalog_provider()(self._project_dir)
|
|
336
|
+
|
|
337
|
+
def __getattr__(self, name: str) -> Any:
|
|
338
|
+
return getattr(self._facade(), name)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def get_route_token_catalog(*, project_dir: str | Path | None = None) -> _RouteTokenCatalogProxy:
|
|
342
|
+
return _RouteTokenCatalogProxy(project_dir=project_dir)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def _resolve_bound_snapshot(project_dir: str | Path | None = None) -> SettingsSnapshot | None:
|
|
346
|
+
normalized_project_dir = _normalize_project_dir(project_dir)
|
|
347
|
+
runtime_plan = current_runtime_plan()
|
|
348
|
+
runtime_project_dir = _runtime_plan_project_dir()
|
|
349
|
+
runtime_settings = getattr(runtime_plan, "settings", None) if runtime_plan is not None else None
|
|
350
|
+
if runtime_settings is not None and (normalized_project_dir is None or runtime_project_dir == normalized_project_dir):
|
|
351
|
+
return runtime_settings
|
|
352
|
+
if normalized_project_dir is not None:
|
|
353
|
+
return _SETTINGS_SNAPSHOTS.get(normalized_project_dir)
|
|
354
|
+
active_project_dir = current_settings_project_dir()
|
|
355
|
+
if active_project_dir and active_project_dir in _SETTINGS_SNAPSHOTS:
|
|
356
|
+
return _SETTINGS_SNAPSHOTS[active_project_dir]
|
|
357
|
+
if len(_SETTINGS_SNAPSHOTS) == 1:
|
|
358
|
+
return next(iter(_SETTINGS_SNAPSHOTS.values()))
|
|
359
|
+
return None
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def load_project_env_values(project_dir: str | None = None) -> dict[str, str]:
|
|
363
|
+
project = compile_project_context(project_dir=project_dir)
|
|
364
|
+
return load_project_env_file(project)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _provider_values(snapshot: SettingsSnapshot, app_id: str, *, project_dir: str | None = None) -> dict[str, Any]:
|
|
368
|
+
if _SETTINGS_PROVIDER_RESOLUTION_DEPTH.get() > 0:
|
|
369
|
+
return {}
|
|
370
|
+
structured_values = _structured_provider_values(
|
|
371
|
+
snapshot,
|
|
372
|
+
project_dir=_normalize_project_dir(project_dir if project_dir is not None else _snapshot_project_dir(snapshot)),
|
|
373
|
+
)
|
|
374
|
+
return copy.deepcopy(structured_values.get(str(app_id).strip(), {}) or {})
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def _settings_provider_specs(
|
|
378
|
+
snapshot: SettingsSnapshot,
|
|
379
|
+
*,
|
|
380
|
+
project_dir: str | None,
|
|
381
|
+
) -> tuple[SettingsProviderSpec, ...]:
|
|
382
|
+
runtime_plan = current_runtime_plan()
|
|
383
|
+
normalized_project_dir = _normalize_project_dir(
|
|
384
|
+
project_dir if project_dir is not None else _snapshot_project_dir(snapshot)
|
|
385
|
+
)
|
|
386
|
+
if runtime_plan is not None:
|
|
387
|
+
runtime_project_dir = _normalize_project_dir(getattr(getattr(runtime_plan, "project", None), "project_dir", None))
|
|
388
|
+
if runtime_project_dir == normalized_project_dir:
|
|
389
|
+
return tuple(getattr(runtime_plan, "settings_providers", ()) or ())
|
|
390
|
+
|
|
391
|
+
return _discover_settings_provider_extensions(snapshot)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def _discover_settings_provider_extensions(snapshot: SettingsSnapshot) -> tuple[SettingsProviderSpec, ...]:
|
|
395
|
+
from pz_core.assembly.apps import compile_app_registry
|
|
396
|
+
from pz_core.assembly.extensions import compile_extension_registry
|
|
397
|
+
|
|
398
|
+
cache_key = id(snapshot)
|
|
399
|
+
cached = _SETTINGS_PROVIDER_SPECS_CACHE.get(cache_key)
|
|
400
|
+
if cached is not None and cached[0] is snapshot:
|
|
401
|
+
return cached[1]
|
|
402
|
+
|
|
403
|
+
app_registry = compile_app_registry(
|
|
404
|
+
snapshot,
|
|
405
|
+
project_dir=_snapshot_project_dir(snapshot),
|
|
406
|
+
run_init_hooks=False,
|
|
407
|
+
)
|
|
408
|
+
extension_registry = compile_extension_registry(snapshot, app_registry)
|
|
409
|
+
discovered = tuple(
|
|
410
|
+
spec
|
|
411
|
+
for spec in extension_registry.by_kind("settings.provider")
|
|
412
|
+
if isinstance(spec, SettingsProviderSpec)
|
|
413
|
+
)
|
|
414
|
+
_SETTINGS_PROVIDER_SPECS_CACHE[cache_key] = (snapshot, discovered)
|
|
415
|
+
return discovered
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def _normalize_settings_patch(
|
|
419
|
+
payload: Any,
|
|
420
|
+
*,
|
|
421
|
+
owner: str,
|
|
422
|
+
default_priority: int,
|
|
423
|
+
) -> tuple[SettingsPatch, ...]:
|
|
424
|
+
def _coerce_patch(item: Any) -> SettingsPatch:
|
|
425
|
+
if isinstance(item, SettingsPatch):
|
|
426
|
+
return item
|
|
427
|
+
app_id = getattr(item, "app_id", None)
|
|
428
|
+
patch_payload = getattr(item, "payload", None)
|
|
429
|
+
source = getattr(item, "source", None)
|
|
430
|
+
priority = getattr(item, "priority", default_priority)
|
|
431
|
+
if not isinstance(app_id, str) or not app_id.strip():
|
|
432
|
+
raise PzError(
|
|
433
|
+
code="PZ_SETTINGS_PROVIDER_RESULT_INVALID",
|
|
434
|
+
message=f"Settings provider '{owner}' must return SettingsPatch objects.",
|
|
435
|
+
stage="settings",
|
|
436
|
+
owner=owner,
|
|
437
|
+
action="load_app_settings",
|
|
438
|
+
)
|
|
439
|
+
if not isinstance(patch_payload, dict):
|
|
440
|
+
raise PzError(
|
|
441
|
+
code="PZ_SETTINGS_PROVIDER_RESULT_INVALID",
|
|
442
|
+
message=f"Settings provider '{owner}' must return SettingsPatch objects.",
|
|
443
|
+
stage="settings",
|
|
444
|
+
owner=owner,
|
|
445
|
+
action="load_app_settings",
|
|
446
|
+
)
|
|
447
|
+
if not isinstance(source, str) or not source.strip():
|
|
448
|
+
raise PzError(
|
|
449
|
+
code="PZ_SETTINGS_PROVIDER_RESULT_INVALID",
|
|
450
|
+
message=f"Settings provider '{owner}' must return SettingsPatch objects.",
|
|
451
|
+
stage="settings",
|
|
452
|
+
owner=owner,
|
|
453
|
+
action="load_app_settings",
|
|
454
|
+
)
|
|
455
|
+
return SettingsPatch(
|
|
456
|
+
app_id=app_id.strip(),
|
|
457
|
+
payload=dict(patch_payload),
|
|
458
|
+
source=source.strip(),
|
|
459
|
+
priority=int(priority if priority is not None else default_priority),
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
if payload in (None, (), []):
|
|
463
|
+
return ()
|
|
464
|
+
if payload is not None and not isinstance(payload, (list, tuple)):
|
|
465
|
+
return (_coerce_patch(payload),)
|
|
466
|
+
if isinstance(payload, (list, tuple)):
|
|
467
|
+
out: list[SettingsPatch] = []
|
|
468
|
+
for item in payload:
|
|
469
|
+
out.append(_coerce_patch(item))
|
|
470
|
+
return tuple(out)
|
|
471
|
+
raise PzError(
|
|
472
|
+
code="PZ_SETTINGS_PROVIDER_RESULT_INVALID",
|
|
473
|
+
message=f"Settings provider '{owner}' must return a SettingsPatch or tuple of SettingsPatch.",
|
|
474
|
+
stage="settings",
|
|
475
|
+
owner=owner,
|
|
476
|
+
action="load_app_settings",
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def _iter_patch_paths(payload: dict[str, Any], prefix: tuple[str, ...] = ()) -> tuple[tuple[str, ...], ...]:
|
|
481
|
+
paths: list[tuple[str, ...]] = []
|
|
482
|
+
for key, value in payload.items():
|
|
483
|
+
current = prefix + (str(key),)
|
|
484
|
+
if isinstance(value, dict) and value:
|
|
485
|
+
paths.extend(_iter_patch_paths(value, current))
|
|
486
|
+
continue
|
|
487
|
+
paths.append(current)
|
|
488
|
+
return tuple(paths)
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def _structured_provider_values(
|
|
492
|
+
snapshot: SettingsSnapshot,
|
|
493
|
+
*,
|
|
494
|
+
project_dir: str | None,
|
|
495
|
+
) -> dict[str, dict[str, Any]]:
|
|
496
|
+
normalized_project_dir = _normalize_project_dir(project_dir)
|
|
497
|
+
cache_key = (id(snapshot), normalized_project_dir)
|
|
498
|
+
cached = _SETTINGS_PROVIDER_VALUES_CACHE.get(cache_key)
|
|
499
|
+
if cached is not None and cached[0] is snapshot:
|
|
500
|
+
return cached[1]
|
|
501
|
+
token = _SETTINGS_PROVIDER_RESOLUTION_DEPTH.set(_SETTINGS_PROVIDER_RESOLUTION_DEPTH.get() + 1)
|
|
502
|
+
try:
|
|
503
|
+
providers = sorted(
|
|
504
|
+
_settings_provider_specs(snapshot, project_dir=normalized_project_dir),
|
|
505
|
+
key=lambda item: (int(item.priority), str(item.owner_app), str(item.id)),
|
|
506
|
+
)
|
|
507
|
+
if not providers:
|
|
508
|
+
return {}
|
|
509
|
+
|
|
510
|
+
project = compile_project_context(project_dir=normalized_project_dir)
|
|
511
|
+
resolved_settings = ResolvedSettings(
|
|
512
|
+
snapshot=snapshot,
|
|
513
|
+
project_dir=normalized_project_dir or _snapshot_project_dir(snapshot),
|
|
514
|
+
)
|
|
515
|
+
merged_by_app: dict[str, dict[str, Any]] = {}
|
|
516
|
+
seen_paths: dict[tuple[str, tuple[str, ...]], tuple[int, str]] = {}
|
|
517
|
+
|
|
518
|
+
for spec in providers:
|
|
519
|
+
provide = getattr(spec.provider, "provide", None)
|
|
520
|
+
if not callable(provide):
|
|
521
|
+
raise PzError(
|
|
522
|
+
code="PZ_SETTINGS_PROVIDER_INVALID",
|
|
523
|
+
message=f"Settings provider '{spec.id}' must declare a provider object with provide(ctx).",
|
|
524
|
+
stage="settings",
|
|
525
|
+
owner=spec.owner_app,
|
|
526
|
+
action="load_app_settings",
|
|
527
|
+
)
|
|
528
|
+
try:
|
|
529
|
+
patches = _normalize_settings_patch(
|
|
530
|
+
provide(
|
|
531
|
+
SettingsProviderContext(
|
|
532
|
+
project=project,
|
|
533
|
+
snapshot=snapshot,
|
|
534
|
+
settings=resolved_settings,
|
|
535
|
+
)
|
|
536
|
+
),
|
|
537
|
+
owner=spec.id,
|
|
538
|
+
default_priority=int(spec.priority),
|
|
539
|
+
)
|
|
540
|
+
except PzError:
|
|
541
|
+
raise
|
|
542
|
+
except Exception as exc:
|
|
543
|
+
raise PzError(
|
|
544
|
+
code="PZ_SETTINGS_PROVIDER_FAILED",
|
|
545
|
+
message=f"Settings provider '{spec.id}' failed: {exc}",
|
|
546
|
+
stage="settings",
|
|
547
|
+
owner=spec.owner_app,
|
|
548
|
+
action="load_app_settings",
|
|
549
|
+
) from exc
|
|
550
|
+
for patch in patches:
|
|
551
|
+
app_name = _read_app_id(patch.app_id, action="load_app_settings")
|
|
552
|
+
payload = dict(patch.payload or {})
|
|
553
|
+
priority = int(patch.priority)
|
|
554
|
+
for path in _iter_patch_paths(payload):
|
|
555
|
+
key = (app_name, path)
|
|
556
|
+
previous = seen_paths.get(key)
|
|
557
|
+
if previous is not None and previous[0] == priority:
|
|
558
|
+
raise PzError(
|
|
559
|
+
code="PZ_SETTINGS_PROVIDER_CONFLICT",
|
|
560
|
+
message=(
|
|
561
|
+
f"Settings providers '{previous[1]}' and '{patch.source}' both write "
|
|
562
|
+
f"'{app_name}.{'.'.join(path)}' at priority {priority}."
|
|
563
|
+
),
|
|
564
|
+
stage="settings",
|
|
565
|
+
owner=app_name,
|
|
566
|
+
action="load_app_settings",
|
|
567
|
+
)
|
|
568
|
+
seen_paths[key] = (priority, patch.source)
|
|
569
|
+
merged_by_app[app_name] = _deep_merge_payload(dict(merged_by_app.get(app_name, {}) or {}), payload)
|
|
570
|
+
_SETTINGS_PROVIDER_VALUES_CACHE[cache_key] = (snapshot, merged_by_app)
|
|
571
|
+
return merged_by_app
|
|
572
|
+
finally:
|
|
573
|
+
_SETTINGS_PROVIDER_RESOLUTION_DEPTH.reset(token)
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def _snapshot_project_dir(snapshot: SettingsSnapshot) -> str | None:
|
|
577
|
+
value = getattr(snapshot, "project_dir", None)
|
|
578
|
+
if value is None:
|
|
579
|
+
return None
|
|
580
|
+
try:
|
|
581
|
+
return str(value)
|
|
582
|
+
except Exception:
|
|
583
|
+
return None
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
def _load_snapshot(project_dir: str | None = None) -> SettingsSnapshot:
|
|
587
|
+
project = compile_project_context(project_dir=project_dir)
|
|
588
|
+
return compile_settings_snapshot(project)
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def _snapshot() -> SettingsSnapshot:
|
|
592
|
+
return _load_snapshot()
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
def _lookup_key(mapping: dict[str, Any], name: str) -> Any:
|
|
596
|
+
if name in mapping:
|
|
597
|
+
return mapping[name]
|
|
598
|
+
raise KeyError(name)
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def _declared_settings_namespaces(snapshot: SettingsSnapshot) -> frozenset[str]:
|
|
602
|
+
cache_key = id(snapshot)
|
|
603
|
+
cached = _SETTINGS_NAMESPACE_CACHE.get(cache_key)
|
|
604
|
+
if cached is not None and cached[0] is snapshot:
|
|
605
|
+
return cached[1]
|
|
606
|
+
namespaces: set[str] = set()
|
|
607
|
+
for app_id in dict(getattr(snapshot, "apps", {}) or {}).keys():
|
|
608
|
+
normalized = str(app_id or "").strip()
|
|
609
|
+
if normalized:
|
|
610
|
+
namespaces.add(normalized)
|
|
611
|
+
result = frozenset(namespaces)
|
|
612
|
+
_SETTINGS_NAMESPACE_CACHE[cache_key] = (snapshot, result)
|
|
613
|
+
return result
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def _merged_app_settings(
|
|
617
|
+
snapshot: SettingsSnapshot,
|
|
618
|
+
app_id: str,
|
|
619
|
+
*,
|
|
620
|
+
project_dir: str | None = None,
|
|
621
|
+
) -> tuple[str, dict[str, Any]]:
|
|
622
|
+
values = dict(snapshot.app_settings(app_id))
|
|
623
|
+
provider_values = _provider_values(
|
|
624
|
+
snapshot,
|
|
625
|
+
app_id,
|
|
626
|
+
project_dir=project_dir if project_dir is not None else _snapshot_project_dir(snapshot),
|
|
627
|
+
)
|
|
628
|
+
if provider_values:
|
|
629
|
+
values.update(provider_values)
|
|
630
|
+
return app_id, values
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
def _root_settings_payload(snapshot: SettingsSnapshot) -> dict[str, Any]:
|
|
634
|
+
payload = dict(getattr(snapshot, "data", {}) or {})
|
|
635
|
+
payload.setdefault("project_dir", getattr(snapshot, "project_dir", None))
|
|
636
|
+
payload.setdefault("installed_apps", tuple(getattr(snapshot, "installed_apps", ()) or ()))
|
|
637
|
+
payload.setdefault("app_replacements", dict(getattr(snapshot, "app_replacements", {}) or {}))
|
|
638
|
+
payload.setdefault("apps", dict(getattr(snapshot, "apps", {}) or {}))
|
|
639
|
+
payload["project"] = _project_settings_payload(snapshot, payload)
|
|
640
|
+
return payload
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
def _project_settings_payload(snapshot: SettingsSnapshot, root_payload: dict[str, Any]) -> dict[str, Any]:
|
|
644
|
+
project_payload: dict[str, Any] = {}
|
|
645
|
+
for key, value in root_payload.items():
|
|
646
|
+
if key in {"project", "apps", "app_replacements"}:
|
|
647
|
+
continue
|
|
648
|
+
project_payload[key] = copy.deepcopy(value)
|
|
649
|
+
|
|
650
|
+
declared_project = root_payload.get("project")
|
|
651
|
+
if isinstance(declared_project, dict):
|
|
652
|
+
project_payload = _deep_merge_payload(project_payload, copy.deepcopy(declared_project))
|
|
653
|
+
|
|
654
|
+
if "pz_env" not in project_payload and root_payload.get("PZ_ENV") is not None:
|
|
655
|
+
project_payload["pz_env"] = copy.deepcopy(root_payload.get("PZ_ENV"))
|
|
656
|
+
project_payload.setdefault("project_dir", getattr(snapshot, "project_dir", None))
|
|
657
|
+
project_payload.setdefault("installed_apps", tuple(getattr(snapshot, "installed_apps", ()) or ()))
|
|
658
|
+
project_payload.setdefault("app_replacements", dict(getattr(snapshot, "app_replacements", {}) or {}))
|
|
659
|
+
return project_payload
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
def _mapping_path_value(mapping: dict[str, Any], path: tuple[str, ...], *, default: Any = None) -> Any:
|
|
663
|
+
current: Any = mapping
|
|
664
|
+
for raw_key in path:
|
|
665
|
+
key = str(raw_key)
|
|
666
|
+
if not isinstance(current, dict):
|
|
667
|
+
return default
|
|
668
|
+
try:
|
|
669
|
+
current = _lookup_key(current, key)
|
|
670
|
+
except KeyError:
|
|
671
|
+
return default
|
|
672
|
+
return default if current is None else current
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
@dataclass(frozen=True, slots=True)
|
|
676
|
+
class ResolvedSettings:
|
|
677
|
+
snapshot: SettingsSnapshot
|
|
678
|
+
project_dir: str | None = None
|
|
679
|
+
_root_settings_cache: dict[str, Any] | None = field(default=None, init=False, repr=False, compare=False)
|
|
680
|
+
_project_namespace_cache: "NamespaceProxy | None" = field(default=None, init=False, repr=False, compare=False)
|
|
681
|
+
_app_settings_cache: dict[str, dict[str, Any]] = field(default_factory=dict, init=False, repr=False, compare=False)
|
|
682
|
+
|
|
683
|
+
def _root_settings_cached(self) -> dict[str, Any]:
|
|
684
|
+
cached = self._root_settings_cache
|
|
685
|
+
if cached is None:
|
|
686
|
+
cached = _root_settings_payload(self.snapshot)
|
|
687
|
+
object.__setattr__(self, "_root_settings_cache", cached)
|
|
688
|
+
return cached
|
|
689
|
+
|
|
690
|
+
def _app_settings_payload(self, app_id: str) -> tuple[str, dict[str, Any]]:
|
|
691
|
+
normalized_app_id = _read_app_id(app_id, action="resolved_settings.app_settings")
|
|
692
|
+
cached = self._app_settings_cache.get(normalized_app_id)
|
|
693
|
+
if cached is not None:
|
|
694
|
+
return normalized_app_id, cached
|
|
695
|
+
resolved_app_id, values = _merged_app_settings(
|
|
696
|
+
self.snapshot,
|
|
697
|
+
normalized_app_id,
|
|
698
|
+
project_dir=self.project_dir if self.project_dir is not None else _snapshot_project_dir(self.snapshot),
|
|
699
|
+
)
|
|
700
|
+
cached_values = dict(values)
|
|
701
|
+
self._app_settings_cache[normalized_app_id] = cached_values
|
|
702
|
+
return resolved_app_id, cached_values
|
|
703
|
+
|
|
704
|
+
def root_settings(self) -> dict[str, Any]:
|
|
705
|
+
return dict(self._root_settings_cached())
|
|
706
|
+
|
|
707
|
+
@property
|
|
708
|
+
def project(self) -> "NamespaceProxy":
|
|
709
|
+
cached = self._project_namespace_cache
|
|
710
|
+
if cached is not None:
|
|
711
|
+
return cached
|
|
712
|
+
payload = _mapping_path_value(self._root_settings_cached(), ("project",), default={})
|
|
713
|
+
namespace = NamespaceProxy(payload if isinstance(payload, dict) else {})
|
|
714
|
+
object.__setattr__(self, "_project_namespace_cache", namespace)
|
|
715
|
+
return namespace
|
|
716
|
+
|
|
717
|
+
@property
|
|
718
|
+
def route_token_catalog(self) -> _RouteTokenCatalogProxy:
|
|
719
|
+
return get_route_token_catalog(
|
|
720
|
+
project_dir=self.project_dir if self.project_dir is not None else _snapshot_project_dir(self.snapshot)
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
def app_settings(self, app_id: str) -> dict[str, Any]:
|
|
724
|
+
_resolved, values = self._app_settings_payload(app_id)
|
|
725
|
+
return dict(values)
|
|
726
|
+
|
|
727
|
+
def typed_app_settings(
|
|
728
|
+
self,
|
|
729
|
+
app_id: str,
|
|
730
|
+
settings_type: type[TAppSettings],
|
|
731
|
+
*,
|
|
732
|
+
normalize: Callable[[dict[str, Any]], dict[str, Any]] | None = None,
|
|
733
|
+
) -> TAppSettings:
|
|
734
|
+
normalized_app_id = _read_app_id(app_id, action="resolved_settings.typed_app_settings")
|
|
735
|
+
payload = self.app_settings(normalized_app_id)
|
|
736
|
+
if isinstance(payload, settings_type):
|
|
737
|
+
return payload
|
|
738
|
+
values = dict(payload or {})
|
|
739
|
+
if normalize is not None:
|
|
740
|
+
try:
|
|
741
|
+
values = dict(normalize(values) or {})
|
|
742
|
+
except Exception as exc:
|
|
743
|
+
raise PzError(
|
|
744
|
+
code="PZ_APP_SETTINGS_NORMALIZE_FAILED",
|
|
745
|
+
message=f"Failed to normalize typed app settings for '{normalized_app_id}': {exc}",
|
|
746
|
+
stage="settings",
|
|
747
|
+
owner=normalized_app_id,
|
|
748
|
+
action="load_typed_app_settings",
|
|
749
|
+
) from exc
|
|
750
|
+
try:
|
|
751
|
+
return settings_type(**values)
|
|
752
|
+
except Exception as exc:
|
|
753
|
+
raise PzError(
|
|
754
|
+
code="PZ_APP_SETTINGS_TYPE_INVALID",
|
|
755
|
+
message=f"Failed to build typed app settings for '{normalized_app_id}': {exc}",
|
|
756
|
+
stage="settings",
|
|
757
|
+
owner=normalized_app_id,
|
|
758
|
+
action="load_typed_app_settings",
|
|
759
|
+
) from exc
|
|
760
|
+
|
|
761
|
+
def get(self, *path: str, default: Any = None) -> Any:
|
|
762
|
+
if not path:
|
|
763
|
+
return default
|
|
764
|
+
head = str(path[0]).strip()
|
|
765
|
+
if head in _declared_settings_namespaces(self.snapshot):
|
|
766
|
+
_resolved, app_values = self._app_settings_payload(head)
|
|
767
|
+
if len(path) == 1:
|
|
768
|
+
return dict(app_values)
|
|
769
|
+
return _mapping_path_value(app_values, tuple(str(item) for item in path[1:]), default=default)
|
|
770
|
+
return _mapping_path_value(self._root_settings_cached(), tuple(str(item) for item in path), default=default)
|
|
771
|
+
|
|
772
|
+
def to_dict(self) -> dict[str, Any]:
|
|
773
|
+
payload = dict(self._root_settings_cached())
|
|
774
|
+
payload["apps"] = {
|
|
775
|
+
str(app_id): self.app_settings(str(app_id))
|
|
776
|
+
for app_id in dict(getattr(self.snapshot, "apps", {}) or {}).keys()
|
|
777
|
+
}
|
|
778
|
+
return payload
|
|
779
|
+
|
|
780
|
+
def __getattr__(self, name: str) -> Any:
|
|
781
|
+
if name in _declared_settings_namespaces(self.snapshot):
|
|
782
|
+
_resolved, values = self._app_settings_payload(name)
|
|
783
|
+
return NamespaceProxy(values)
|
|
784
|
+
raise AttributeError(name)
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
class NamespaceProxy:
|
|
788
|
+
def __init__(self, payload: Any) -> None:
|
|
789
|
+
self._payload = payload
|
|
790
|
+
|
|
791
|
+
def _mapping(self) -> dict[str, Any]:
|
|
792
|
+
if isinstance(self._payload, dict):
|
|
793
|
+
return self._payload
|
|
794
|
+
return getattr(self._payload, "__dict__", {})
|
|
795
|
+
|
|
796
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
797
|
+
try:
|
|
798
|
+
value = _lookup_key(self._mapping(), key)
|
|
799
|
+
except KeyError:
|
|
800
|
+
return default
|
|
801
|
+
return _wrap(value)
|
|
802
|
+
|
|
803
|
+
def to_dict(self) -> dict[str, Any]:
|
|
804
|
+
return dict(self._mapping())
|
|
805
|
+
|
|
806
|
+
def __getattr__(self, name: str) -> Any:
|
|
807
|
+
try:
|
|
808
|
+
value = _lookup_key(self._mapping(), name)
|
|
809
|
+
except KeyError as exc:
|
|
810
|
+
raise AttributeError(name) from exc
|
|
811
|
+
return _wrap(value)
|
|
812
|
+
|
|
813
|
+
def __bool__(self) -> bool:
|
|
814
|
+
return bool(self._payload)
|
|
815
|
+
|
|
816
|
+
def __iter__(self):
|
|
817
|
+
return iter(self._mapping())
|
|
818
|
+
|
|
819
|
+
def items(self):
|
|
820
|
+
return self._mapping().items()
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
def _wrap(value: Any) -> Any:
|
|
824
|
+
if isinstance(value, dict):
|
|
825
|
+
return NamespaceProxy(value)
|
|
826
|
+
return value
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
def _read_app_id(app_id: object, *, action: str) -> str:
|
|
830
|
+
if not isinstance(app_id, str) or not app_id.strip():
|
|
831
|
+
raise PzError(
|
|
832
|
+
code="PZ_APP_SETTINGS_IDENTIFIER_INVALID",
|
|
833
|
+
message="app_id must be a non-empty string.",
|
|
834
|
+
stage="settings",
|
|
835
|
+
owner="runtime.settings",
|
|
836
|
+
action=action,
|
|
837
|
+
)
|
|
838
|
+
return app_id.strip()
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
def resolve_settings(project_dir: str | None = None) -> ResolvedSettings:
|
|
842
|
+
effective_project_dir = _effective_project_dir(project_dir)
|
|
843
|
+
snapshot = ensure_full_settings(project_dir=effective_project_dir)
|
|
844
|
+
resolved_project_dir = effective_project_dir or _snapshot_project_dir(snapshot)
|
|
845
|
+
cache_key = (id(snapshot), resolved_project_dir)
|
|
846
|
+
cached = _RESOLVED_SETTINGS_CACHE.get(cache_key)
|
|
847
|
+
if cached is not None and cached[0] is snapshot:
|
|
848
|
+
return cached[1]
|
|
849
|
+
resolved = ResolvedSettings(
|
|
850
|
+
snapshot=snapshot,
|
|
851
|
+
project_dir=resolved_project_dir,
|
|
852
|
+
)
|
|
853
|
+
_RESOLVED_SETTINGS_CACHE[cache_key] = (snapshot, resolved)
|
|
854
|
+
return resolved
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
def ensure_full_settings(_current: Any = None, *, project_dir: str | None = None):
|
|
858
|
+
effective_project_dir = _effective_project_dir(project_dir)
|
|
859
|
+
snapshot = _resolve_bound_snapshot(effective_project_dir)
|
|
860
|
+
if snapshot is not None:
|
|
861
|
+
return snapshot
|
|
862
|
+
return _load_snapshot(project_dir=effective_project_dir)
|
|
863
|
+
|
|
864
|
+
__all__ = [
|
|
865
|
+
"active_settings_project",
|
|
866
|
+
"bound_settings_snapshot",
|
|
867
|
+
"build_local_default_settings",
|
|
868
|
+
"build_local_default_settings_chain",
|
|
869
|
+
"bind_settings_snapshot",
|
|
870
|
+
"current_pz_env",
|
|
871
|
+
"current_settings_project_dir",
|
|
872
|
+
"ensure_full_settings",
|
|
873
|
+
"get_route_token_catalog",
|
|
874
|
+
"has_runtime_env_value",
|
|
875
|
+
"iter_runtime_env_items",
|
|
876
|
+
"load_local_default_settings",
|
|
877
|
+
"load_local_default_settings_chain",
|
|
878
|
+
"load_project_env_values",
|
|
879
|
+
"ResolvedSettings",
|
|
880
|
+
"resolve_settings",
|
|
881
|
+
"register_route_token_catalog_provider",
|
|
882
|
+
"read_runtime_env_bool",
|
|
883
|
+
"read_runtime_env_float",
|
|
884
|
+
"read_runtime_env_int",
|
|
885
|
+
"read_runtime_env_string",
|
|
886
|
+
"reset_route_token_catalog_provider",
|
|
887
|
+
"reset_settings_binding",
|
|
888
|
+
]
|