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,380 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
import inspect
|
|
6
|
+
from typing import Any, Awaitable, Callable
|
|
7
|
+
from uuid import uuid4
|
|
8
|
+
|
|
9
|
+
from pz_core.runtime.errors import PzError
|
|
10
|
+
from pz_core.core.events import EventRegistry
|
|
11
|
+
|
|
12
|
+
EventHandler = Callable[[dict[str, Any]], Any]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True, slots=True)
|
|
16
|
+
class EventSubscription:
|
|
17
|
+
subscription_id: str
|
|
18
|
+
event_name: str
|
|
19
|
+
owner: str
|
|
20
|
+
handler_name: str
|
|
21
|
+
wildcard: bool = False
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True, slots=True)
|
|
25
|
+
class EventDispatchResult:
|
|
26
|
+
event_name: str
|
|
27
|
+
owner: str
|
|
28
|
+
handler_name: str
|
|
29
|
+
ok: bool
|
|
30
|
+
subscribed_event: str | None = None
|
|
31
|
+
scheduler: str | None = None
|
|
32
|
+
code: str | None = None
|
|
33
|
+
message: str | None = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True, slots=True)
|
|
37
|
+
class EventDispatchTicket:
|
|
38
|
+
event_name: str
|
|
39
|
+
owner: str
|
|
40
|
+
scheduled_handlers: int
|
|
41
|
+
scheduler: str
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _coerce_handler_name(handler: EventHandler) -> str:
|
|
45
|
+
return getattr(handler, "__qualname__", getattr(handler, "__name__", handler.__class__.__name__))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _read_required_event_name(
|
|
49
|
+
value: object,
|
|
50
|
+
*,
|
|
51
|
+
code: str,
|
|
52
|
+
action: str,
|
|
53
|
+
owner: str,
|
|
54
|
+
) -> str:
|
|
55
|
+
if not isinstance(value, str) or not value.strip():
|
|
56
|
+
raise PzError(
|
|
57
|
+
code=code,
|
|
58
|
+
message="event_name must be a non-empty string.",
|
|
59
|
+
stage="events",
|
|
60
|
+
owner=owner,
|
|
61
|
+
action=action,
|
|
62
|
+
)
|
|
63
|
+
return value.strip()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _read_required_owner(
|
|
67
|
+
value: object,
|
|
68
|
+
*,
|
|
69
|
+
code: str,
|
|
70
|
+
message: str,
|
|
71
|
+
action: str,
|
|
72
|
+
owner: str,
|
|
73
|
+
) -> str:
|
|
74
|
+
if not isinstance(value, str) or not value.strip():
|
|
75
|
+
raise PzError(
|
|
76
|
+
code=code,
|
|
77
|
+
message=message,
|
|
78
|
+
stage="events",
|
|
79
|
+
owner=owner,
|
|
80
|
+
action=action,
|
|
81
|
+
)
|
|
82
|
+
return value.strip()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _read_dispatched_event_name(
|
|
86
|
+
envelope: dict[str, Any],
|
|
87
|
+
*,
|
|
88
|
+
fallback: str,
|
|
89
|
+
) -> str:
|
|
90
|
+
raw = envelope.get("event")
|
|
91
|
+
if raw is None:
|
|
92
|
+
return fallback
|
|
93
|
+
return _read_required_event_name(
|
|
94
|
+
raw,
|
|
95
|
+
code="PZ_EVENT_NAME_INVALID",
|
|
96
|
+
action="dispatch",
|
|
97
|
+
owner="runtime.events",
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass(slots=True)
|
|
102
|
+
class EventBusFacade:
|
|
103
|
+
registry: EventRegistry
|
|
104
|
+
_subscriptions: dict[str, tuple[EventSubscription, EventHandler]] = field(default_factory=dict)
|
|
105
|
+
_dispatch_results: list[EventDispatchResult] = field(default_factory=list)
|
|
106
|
+
|
|
107
|
+
def __post_init__(self) -> None:
|
|
108
|
+
if not isinstance(self.registry, EventRegistry):
|
|
109
|
+
raise PzError(
|
|
110
|
+
code="PZ_EVENT_REGISTRY_INVALID",
|
|
111
|
+
message="EventBusFacade requires an EventRegistry instance.",
|
|
112
|
+
stage="events",
|
|
113
|
+
owner="runtime.events",
|
|
114
|
+
action="bind_event_bus",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def _ensure_declared(self, event_name: str) -> None:
|
|
118
|
+
normalized = _read_required_event_name(
|
|
119
|
+
event_name,
|
|
120
|
+
code="PZ_EVENT_NAME_INVALID",
|
|
121
|
+
action="publish",
|
|
122
|
+
owner="runtime.events",
|
|
123
|
+
)
|
|
124
|
+
if normalized not in self.registry.by_name():
|
|
125
|
+
raise PzError(
|
|
126
|
+
code="PZ_EVENT_UNDECLARED",
|
|
127
|
+
message=f"Event '{normalized}' is not declared in EventRegistry.",
|
|
128
|
+
stage="events",
|
|
129
|
+
owner=normalized,
|
|
130
|
+
action="publish",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def subscribe(
|
|
134
|
+
self,
|
|
135
|
+
event_name: str,
|
|
136
|
+
handler: EventHandler,
|
|
137
|
+
*,
|
|
138
|
+
owner: str,
|
|
139
|
+
wildcard: bool = False,
|
|
140
|
+
) -> str:
|
|
141
|
+
normalized_owner = _read_required_owner(
|
|
142
|
+
owner,
|
|
143
|
+
code="PZ_EVENT_SUBSCRIBER_OWNER_INVALID",
|
|
144
|
+
message="event subscriber must declare owner as a non-empty string.",
|
|
145
|
+
action="subscribe",
|
|
146
|
+
owner="runtime.events",
|
|
147
|
+
)
|
|
148
|
+
normalized_event = _read_required_event_name(
|
|
149
|
+
event_name,
|
|
150
|
+
code="PZ_EVENT_NAME_INVALID",
|
|
151
|
+
action="subscribe",
|
|
152
|
+
owner=normalized_owner,
|
|
153
|
+
)
|
|
154
|
+
if not wildcard:
|
|
155
|
+
self._ensure_declared(normalized_event)
|
|
156
|
+
subscription = EventSubscription(
|
|
157
|
+
subscription_id=uuid4().hex,
|
|
158
|
+
event_name=normalized_event,
|
|
159
|
+
owner=normalized_owner,
|
|
160
|
+
handler_name=_coerce_handler_name(handler),
|
|
161
|
+
wildcard=wildcard,
|
|
162
|
+
)
|
|
163
|
+
self._subscriptions[subscription.subscription_id] = (subscription, handler)
|
|
164
|
+
return subscription.subscription_id
|
|
165
|
+
|
|
166
|
+
def unsubscribe(self, subscription_id: str) -> bool:
|
|
167
|
+
if not isinstance(subscription_id, str) or not subscription_id.strip():
|
|
168
|
+
raise PzError(
|
|
169
|
+
code="PZ_EVENT_SUBSCRIPTION_ID_INVALID",
|
|
170
|
+
message="event subscription_id must be a non-empty string.",
|
|
171
|
+
stage="events",
|
|
172
|
+
owner="runtime.events",
|
|
173
|
+
action="unsubscribe",
|
|
174
|
+
)
|
|
175
|
+
return self._subscriptions.pop(subscription_id, None) is not None
|
|
176
|
+
|
|
177
|
+
def reset(self, *, owner: str | None = None) -> int:
|
|
178
|
+
if owner is None:
|
|
179
|
+
removed = len(self._subscriptions)
|
|
180
|
+
self._subscriptions.clear()
|
|
181
|
+
self._dispatch_results.clear()
|
|
182
|
+
return removed
|
|
183
|
+
target = _read_required_owner(
|
|
184
|
+
owner,
|
|
185
|
+
code="PZ_EVENT_RESET_OWNER_INVALID",
|
|
186
|
+
message="event reset owner must be a non-empty string.",
|
|
187
|
+
action="reset",
|
|
188
|
+
owner="runtime.events",
|
|
189
|
+
)
|
|
190
|
+
to_remove = [
|
|
191
|
+
subscription_id
|
|
192
|
+
for subscription_id, (subscription, _) in self._subscriptions.items()
|
|
193
|
+
if subscription.owner == target
|
|
194
|
+
]
|
|
195
|
+
for subscription_id in to_remove:
|
|
196
|
+
self._subscriptions.pop(subscription_id, None)
|
|
197
|
+
self._dispatch_results[:] = [
|
|
198
|
+
result for result in self._dispatch_results if result.owner != target
|
|
199
|
+
]
|
|
200
|
+
return len(to_remove)
|
|
201
|
+
|
|
202
|
+
def subscriptions_for(self, event_name: str) -> tuple[EventSubscription, ...]:
|
|
203
|
+
normalized = _read_required_event_name(
|
|
204
|
+
event_name,
|
|
205
|
+
code="PZ_EVENT_NAME_INVALID",
|
|
206
|
+
action="subscriptions_for",
|
|
207
|
+
owner="runtime.events",
|
|
208
|
+
)
|
|
209
|
+
return tuple(
|
|
210
|
+
subscription
|
|
211
|
+
for subscription, _ in self._subscriptions.values()
|
|
212
|
+
if subscription.event_name == normalized or subscription.wildcard
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
def diagnostics(self) -> tuple[EventDispatchResult, ...]:
|
|
216
|
+
return tuple(self._dispatch_results)
|
|
217
|
+
|
|
218
|
+
async def _invoke_handler(
|
|
219
|
+
self,
|
|
220
|
+
subscription: EventSubscription,
|
|
221
|
+
handler: EventHandler,
|
|
222
|
+
envelope: dict[str, Any],
|
|
223
|
+
*,
|
|
224
|
+
scheduler: str,
|
|
225
|
+
) -> EventDispatchResult:
|
|
226
|
+
try:
|
|
227
|
+
dispatched_event = _read_dispatched_event_name(
|
|
228
|
+
envelope,
|
|
229
|
+
fallback=subscription.event_name,
|
|
230
|
+
)
|
|
231
|
+
except PzError as exc:
|
|
232
|
+
dispatch = EventDispatchResult(
|
|
233
|
+
event_name=subscription.event_name,
|
|
234
|
+
owner=subscription.owner,
|
|
235
|
+
handler_name=subscription.handler_name,
|
|
236
|
+
ok=False,
|
|
237
|
+
subscribed_event=subscription.event_name,
|
|
238
|
+
scheduler=scheduler,
|
|
239
|
+
code=exc.code,
|
|
240
|
+
message=exc.summary or exc.message,
|
|
241
|
+
)
|
|
242
|
+
self._dispatch_results.append(dispatch)
|
|
243
|
+
return dispatch
|
|
244
|
+
try:
|
|
245
|
+
if inspect.iscoroutinefunction(handler):
|
|
246
|
+
await handler(envelope)
|
|
247
|
+
else:
|
|
248
|
+
result = await asyncio.to_thread(handler, envelope)
|
|
249
|
+
if inspect.isawaitable(result):
|
|
250
|
+
await result
|
|
251
|
+
except PzError as exc:
|
|
252
|
+
dispatch = EventDispatchResult(
|
|
253
|
+
event_name=dispatched_event,
|
|
254
|
+
owner=subscription.owner,
|
|
255
|
+
handler_name=subscription.handler_name,
|
|
256
|
+
ok=False,
|
|
257
|
+
subscribed_event=subscription.event_name,
|
|
258
|
+
scheduler=scheduler,
|
|
259
|
+
code=exc.code,
|
|
260
|
+
message=exc.summary or exc.message,
|
|
261
|
+
)
|
|
262
|
+
self._dispatch_results.append(dispatch)
|
|
263
|
+
return dispatch
|
|
264
|
+
except Exception as exc:
|
|
265
|
+
dispatch = EventDispatchResult(
|
|
266
|
+
event_name=dispatched_event,
|
|
267
|
+
owner=subscription.owner,
|
|
268
|
+
handler_name=subscription.handler_name,
|
|
269
|
+
ok=False,
|
|
270
|
+
subscribed_event=subscription.event_name,
|
|
271
|
+
scheduler=scheduler,
|
|
272
|
+
code="PZ_EVENT_HANDLER_FAILED",
|
|
273
|
+
message=str(exc) or exc.__class__.__name__,
|
|
274
|
+
)
|
|
275
|
+
self._dispatch_results.append(dispatch)
|
|
276
|
+
return dispatch
|
|
277
|
+
dispatch = EventDispatchResult(
|
|
278
|
+
event_name=dispatched_event,
|
|
279
|
+
owner=subscription.owner,
|
|
280
|
+
handler_name=subscription.handler_name,
|
|
281
|
+
ok=True,
|
|
282
|
+
subscribed_event=subscription.event_name,
|
|
283
|
+
scheduler=scheduler,
|
|
284
|
+
)
|
|
285
|
+
self._dispatch_results.append(dispatch)
|
|
286
|
+
return dispatch
|
|
287
|
+
|
|
288
|
+
async def publish(
|
|
289
|
+
self,
|
|
290
|
+
event_name: str,
|
|
291
|
+
payload: dict[str, Any] | None = None,
|
|
292
|
+
*,
|
|
293
|
+
owner: str,
|
|
294
|
+
) -> tuple[EventDispatchResult, ...]:
|
|
295
|
+
normalized_event = _read_required_event_name(
|
|
296
|
+
event_name,
|
|
297
|
+
code="PZ_EVENT_NAME_INVALID",
|
|
298
|
+
action="publish",
|
|
299
|
+
owner="runtime.events",
|
|
300
|
+
)
|
|
301
|
+
normalized_owner = _read_required_owner(
|
|
302
|
+
owner,
|
|
303
|
+
code="PZ_EVENT_PUBLISH_OWNER_INVALID",
|
|
304
|
+
message="event publisher must declare owner as a non-empty string.",
|
|
305
|
+
action="publish",
|
|
306
|
+
owner=normalized_event,
|
|
307
|
+
)
|
|
308
|
+
self._ensure_declared(normalized_event)
|
|
309
|
+
envelope = {
|
|
310
|
+
"event": normalized_event,
|
|
311
|
+
"owner": normalized_owner,
|
|
312
|
+
"payload": dict(payload or {}),
|
|
313
|
+
}
|
|
314
|
+
subscriptions = self.subscriptions_for(normalized_event)
|
|
315
|
+
results: list[EventDispatchResult] = []
|
|
316
|
+
for subscription in subscriptions:
|
|
317
|
+
_, handler = self._subscriptions[subscription.subscription_id]
|
|
318
|
+
results.append(
|
|
319
|
+
await self._invoke_handler(
|
|
320
|
+
subscription,
|
|
321
|
+
handler,
|
|
322
|
+
envelope,
|
|
323
|
+
scheduler="await",
|
|
324
|
+
)
|
|
325
|
+
)
|
|
326
|
+
return tuple(results)
|
|
327
|
+
|
|
328
|
+
def publish_nowait(
|
|
329
|
+
self,
|
|
330
|
+
event_name: str,
|
|
331
|
+
payload: dict[str, Any] | None = None,
|
|
332
|
+
*,
|
|
333
|
+
owner: str,
|
|
334
|
+
) -> EventDispatchTicket:
|
|
335
|
+
normalized_event = _read_required_event_name(
|
|
336
|
+
event_name,
|
|
337
|
+
code="PZ_EVENT_NAME_INVALID",
|
|
338
|
+
action="publish_nowait",
|
|
339
|
+
owner="runtime.events",
|
|
340
|
+
)
|
|
341
|
+
normalized_owner = _read_required_owner(
|
|
342
|
+
owner,
|
|
343
|
+
code="PZ_EVENT_PUBLISH_OWNER_INVALID",
|
|
344
|
+
message="event publisher must declare owner as a non-empty string.",
|
|
345
|
+
action="publish_nowait",
|
|
346
|
+
owner=normalized_event,
|
|
347
|
+
)
|
|
348
|
+
self._ensure_declared(normalized_event)
|
|
349
|
+
try:
|
|
350
|
+
loop = asyncio.get_running_loop()
|
|
351
|
+
except RuntimeError as exc:
|
|
352
|
+
raise PzError(
|
|
353
|
+
code="PZ_EVENT_LOOP_REQUIRED",
|
|
354
|
+
message="publish_nowait() requires an active asyncio loop.",
|
|
355
|
+
stage="events",
|
|
356
|
+
owner=normalized_event,
|
|
357
|
+
action="publish_nowait",
|
|
358
|
+
) from exc
|
|
359
|
+
subscriptions = self.subscriptions_for(normalized_event)
|
|
360
|
+
envelope = {
|
|
361
|
+
"event": normalized_event,
|
|
362
|
+
"owner": normalized_owner,
|
|
363
|
+
"payload": dict(payload or {}),
|
|
364
|
+
}
|
|
365
|
+
for subscription in subscriptions:
|
|
366
|
+
_, handler = self._subscriptions[subscription.subscription_id]
|
|
367
|
+
loop.create_task(
|
|
368
|
+
self._invoke_handler(
|
|
369
|
+
subscription,
|
|
370
|
+
handler,
|
|
371
|
+
envelope,
|
|
372
|
+
scheduler="asyncio.create_task",
|
|
373
|
+
)
|
|
374
|
+
)
|
|
375
|
+
return EventDispatchTicket(
|
|
376
|
+
event_name=normalized_event,
|
|
377
|
+
owner=normalized_owner,
|
|
378
|
+
scheduled_handlers=len(subscriptions),
|
|
379
|
+
scheduler="asyncio.create_task",
|
|
380
|
+
)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import replace
|
|
4
|
+
import time
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from pz_core.runtime.facades._capabilities import merged_capability_status
|
|
8
|
+
from pz_core.runtime.health import HealthSnapshot, build_health_snapshot
|
|
9
|
+
from pz_core.runtime.http_timing import log_http_timing
|
|
10
|
+
from pz_core.runtime.lifecycle import LifecycleState
|
|
11
|
+
from pz_core.runtime.plan import RuntimePlan
|
|
12
|
+
from pz_core.runtime.registries import CapabilityStatus
|
|
13
|
+
from pz_core.runtime.errors import PzError
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from pz_core.runtime.facades.providers import ProviderFacade
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class HealthFacade:
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
plan: RuntimePlan,
|
|
23
|
+
state: LifecycleState | None = None,
|
|
24
|
+
*,
|
|
25
|
+
providers: "ProviderFacade | None" = None,
|
|
26
|
+
) -> None:
|
|
27
|
+
if not isinstance(plan, RuntimePlan):
|
|
28
|
+
raise PzError(
|
|
29
|
+
code="PZ_HEALTH_PLAN_INVALID",
|
|
30
|
+
message="HealthFacade requires a RuntimePlan instance.",
|
|
31
|
+
stage="health",
|
|
32
|
+
owner="runtime.health",
|
|
33
|
+
action="bind_health_facade",
|
|
34
|
+
)
|
|
35
|
+
if state is not None and not isinstance(state, LifecycleState):
|
|
36
|
+
raise PzError(
|
|
37
|
+
code="PZ_HEALTH_STATE_INVALID",
|
|
38
|
+
message="HealthFacade state must be a LifecycleState when configured.",
|
|
39
|
+
stage="health",
|
|
40
|
+
owner="runtime.health",
|
|
41
|
+
action="bind_health_facade",
|
|
42
|
+
)
|
|
43
|
+
if providers is not None and not hasattr(providers, "declared"):
|
|
44
|
+
raise PzError(
|
|
45
|
+
code="PZ_HEALTH_PROVIDERS_INVALID",
|
|
46
|
+
message="HealthFacade providers must expose declared() when configured.",
|
|
47
|
+
stage="health",
|
|
48
|
+
owner="runtime.health",
|
|
49
|
+
action="bind_health_facade",
|
|
50
|
+
)
|
|
51
|
+
self._plan = plan
|
|
52
|
+
self._state = state
|
|
53
|
+
self._providers = providers
|
|
54
|
+
|
|
55
|
+
def _provider_capability_statuses(self) -> dict[str, CapabilityStatus]:
|
|
56
|
+
if self._providers is None:
|
|
57
|
+
return {}
|
|
58
|
+
statuses: dict[str, CapabilityStatus] = {}
|
|
59
|
+
for declaration in self._providers.declared():
|
|
60
|
+
capability = declaration.capability
|
|
61
|
+
base = self._plan.capability_registry.get(capability)
|
|
62
|
+
if base is not None and base.status == "disabled":
|
|
63
|
+
statuses[capability] = "disabled"
|
|
64
|
+
continue
|
|
65
|
+
statuses[capability] = merged_capability_status(
|
|
66
|
+
self._plan,
|
|
67
|
+
self._providers,
|
|
68
|
+
capability,
|
|
69
|
+
)
|
|
70
|
+
return statuses
|
|
71
|
+
|
|
72
|
+
def liveness(self) -> HealthSnapshot:
|
|
73
|
+
started_at = time.perf_counter()
|
|
74
|
+
provider_started_at = started_at
|
|
75
|
+
extra_capability_statuses = self._provider_capability_statuses()
|
|
76
|
+
providers_ms = (time.perf_counter() - provider_started_at) * 1000.0
|
|
77
|
+
snapshot_started_at = time.perf_counter()
|
|
78
|
+
snapshot = build_health_snapshot(
|
|
79
|
+
self._plan,
|
|
80
|
+
self._state,
|
|
81
|
+
extra_capability_statuses=extra_capability_statuses,
|
|
82
|
+
)
|
|
83
|
+
snapshot_ms = (time.perf_counter() - snapshot_started_at) * 1000.0
|
|
84
|
+
total_ms = (time.perf_counter() - started_at) * 1000.0
|
|
85
|
+
log_http_timing("health.providers", total_ms=providers_ms)
|
|
86
|
+
log_http_timing("health.snapshot", total_ms=snapshot_ms)
|
|
87
|
+
log_http_timing("health.liveness", total_ms=total_ms)
|
|
88
|
+
return snapshot
|
|
89
|
+
|
|
90
|
+
def readiness(self) -> HealthSnapshot:
|
|
91
|
+
started_at = time.perf_counter()
|
|
92
|
+
snapshot = self.liveness()
|
|
93
|
+
if not bool(self._plan.infra.health.get("readiness_enabled", True)):
|
|
94
|
+
readiness_snapshot = replace(snapshot, status="disabled")
|
|
95
|
+
log_http_timing("health.readiness", total_ms=(time.perf_counter() - started_at) * 1000.0)
|
|
96
|
+
return readiness_snapshot
|
|
97
|
+
log_http_timing("health.readiness", total_ms=(time.perf_counter() - started_at) * 1000.0)
|
|
98
|
+
return snapshot
|
|
99
|
+
|
|
100
|
+
def capabilities(self) -> dict[str, CapabilityStatus]:
|
|
101
|
+
return dict(self.liveness().capability_statuses)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pz_core.runtime.errors import PzError
|
|
4
|
+
from pz_core.runtime.facades._capabilities import merged_capability_status
|
|
5
|
+
from pz_core.runtime.facades.providers import ProviderFacade
|
|
6
|
+
from pz_core.runtime.plan import RuntimePlan
|
|
7
|
+
from pz_core.runtime.registries import CapabilityStatus
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _require_mail_template(template: str, *, action: str) -> str:
|
|
11
|
+
if not isinstance(template, str) or not template.strip():
|
|
12
|
+
raise PzError(
|
|
13
|
+
code="PZ_MAIL_TEMPLATE_INVALID",
|
|
14
|
+
message="Mail template must be a non-empty string.",
|
|
15
|
+
stage="mail",
|
|
16
|
+
owner="runtime.mail",
|
|
17
|
+
action=action,
|
|
18
|
+
)
|
|
19
|
+
return template
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _require_mail_context(context: dict[str, object], *, action: str) -> dict[str, object]:
|
|
23
|
+
if not isinstance(context, dict):
|
|
24
|
+
raise PzError(
|
|
25
|
+
code="PZ_MAIL_CONTEXT_INVALID",
|
|
26
|
+
message="Mail context must be a mapping.",
|
|
27
|
+
stage="mail",
|
|
28
|
+
owner="runtime.mail",
|
|
29
|
+
action=action,
|
|
30
|
+
)
|
|
31
|
+
return context
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _read_optional_mail_locale(locale: str | None, *, action: str) -> str | None:
|
|
35
|
+
if locale is None:
|
|
36
|
+
return None
|
|
37
|
+
if not isinstance(locale, str) or not locale.strip():
|
|
38
|
+
raise PzError(
|
|
39
|
+
code="PZ_MAIL_LOCALE_INVALID",
|
|
40
|
+
message="Mail locale must be a non-empty string when configured.",
|
|
41
|
+
stage="mail",
|
|
42
|
+
owner="runtime.mail",
|
|
43
|
+
action=action,
|
|
44
|
+
)
|
|
45
|
+
return locale
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class MailFacade:
|
|
49
|
+
def __init__(self, plan: RuntimePlan, providers: ProviderFacade) -> None:
|
|
50
|
+
if not isinstance(plan, RuntimePlan):
|
|
51
|
+
raise PzError(
|
|
52
|
+
code="PZ_MAIL_PLAN_INVALID",
|
|
53
|
+
message="MailFacade requires a RuntimePlan instance.",
|
|
54
|
+
stage="mail",
|
|
55
|
+
owner="runtime.mail",
|
|
56
|
+
action="bind_mail_facade",
|
|
57
|
+
)
|
|
58
|
+
if not hasattr(providers, "require"):
|
|
59
|
+
raise PzError(
|
|
60
|
+
code="PZ_MAIL_PROVIDERS_INVALID",
|
|
61
|
+
message="MailFacade providers must expose require().",
|
|
62
|
+
stage="mail",
|
|
63
|
+
owner="runtime.mail",
|
|
64
|
+
action="bind_mail_facade",
|
|
65
|
+
)
|
|
66
|
+
self._plan = plan
|
|
67
|
+
self._providers = providers
|
|
68
|
+
|
|
69
|
+
def status(self) -> CapabilityStatus:
|
|
70
|
+
return merged_capability_status(self._plan, self._providers, "mail")
|
|
71
|
+
|
|
72
|
+
def _provider(self) -> object:
|
|
73
|
+
return self._providers.require("mail")
|
|
74
|
+
|
|
75
|
+
def render(
|
|
76
|
+
self,
|
|
77
|
+
template: str,
|
|
78
|
+
context: dict[str, object],
|
|
79
|
+
*,
|
|
80
|
+
locale: str | None = None,
|
|
81
|
+
) -> object:
|
|
82
|
+
template = _require_mail_template(template, action="render")
|
|
83
|
+
context = _require_mail_context(context, action="render")
|
|
84
|
+
locale = _read_optional_mail_locale(locale, action="render")
|
|
85
|
+
provider = self._provider()
|
|
86
|
+
renderer = getattr(provider, "render", None)
|
|
87
|
+
if not callable(renderer):
|
|
88
|
+
raise PzError(
|
|
89
|
+
code="PZ_MAIL_RENDER_UNSUPPORTED",
|
|
90
|
+
message="Registered mail provider does not expose render().",
|
|
91
|
+
stage="mail",
|
|
92
|
+
owner="infra.mail",
|
|
93
|
+
action="render",
|
|
94
|
+
)
|
|
95
|
+
return renderer(template, context, locale=locale)
|
|
96
|
+
|
|
97
|
+
def send(self, message: object) -> object:
|
|
98
|
+
provider = self._provider()
|
|
99
|
+
sender = getattr(provider, "send", None)
|
|
100
|
+
if not callable(sender):
|
|
101
|
+
raise PzError(
|
|
102
|
+
code="PZ_MAIL_SEND_UNSUPPORTED",
|
|
103
|
+
message="Registered mail provider does not expose send().",
|
|
104
|
+
stage="mail",
|
|
105
|
+
owner="infra.mail",
|
|
106
|
+
action="send",
|
|
107
|
+
)
|
|
108
|
+
return sender(message)
|
|
109
|
+
|
|
110
|
+
def enqueue(self, message: object) -> object:
|
|
111
|
+
provider = self._provider()
|
|
112
|
+
enqueue = getattr(provider, "enqueue", None)
|
|
113
|
+
if not callable(enqueue):
|
|
114
|
+
raise PzError(
|
|
115
|
+
code="PZ_MAIL_ENQUEUE_UNSUPPORTED",
|
|
116
|
+
message="Registered mail provider does not expose enqueue().",
|
|
117
|
+
stage="mail",
|
|
118
|
+
owner="infra.mail",
|
|
119
|
+
action="enqueue",
|
|
120
|
+
)
|
|
121
|
+
return enqueue(message)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
|
|
5
|
+
from pz_core.runtime.errors import PzError
|
|
6
|
+
from pz_core.infra.observability import NoopObservability, ObservabilityPort
|
|
7
|
+
from pz_core.runtime.facades._capabilities import merged_capability_status
|
|
8
|
+
from pz_core.runtime.facades.providers import ProviderFacade
|
|
9
|
+
from pz_core.runtime.plan import RuntimePlan
|
|
10
|
+
from pz_core.runtime.registries import CapabilityStatus
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _require_observability_name(name: str, *, action: str) -> str:
|
|
14
|
+
if not isinstance(name, str) or not name.strip():
|
|
15
|
+
raise PzError(
|
|
16
|
+
code="PZ_OBSERVABILITY_NAME_INVALID",
|
|
17
|
+
message="Observability name must be a non-empty string.",
|
|
18
|
+
stage="observability",
|
|
19
|
+
owner="runtime.observability",
|
|
20
|
+
action=action,
|
|
21
|
+
)
|
|
22
|
+
return name
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ObservabilityFacade:
|
|
26
|
+
def __init__(self, plan: RuntimePlan, providers: ProviderFacade) -> None:
|
|
27
|
+
if not isinstance(plan, RuntimePlan):
|
|
28
|
+
raise PzError(
|
|
29
|
+
code="PZ_OBSERVABILITY_PLAN_INVALID",
|
|
30
|
+
message="ObservabilityFacade requires a RuntimePlan instance.",
|
|
31
|
+
stage="observability",
|
|
32
|
+
owner="runtime.observability",
|
|
33
|
+
action="bind_observability_facade",
|
|
34
|
+
)
|
|
35
|
+
if not hasattr(providers, "require") or not hasattr(providers, "declared_for"):
|
|
36
|
+
raise PzError(
|
|
37
|
+
code="PZ_OBSERVABILITY_PROVIDERS_INVALID",
|
|
38
|
+
message="ObservabilityFacade providers must expose require() and declared_for().",
|
|
39
|
+
stage="observability",
|
|
40
|
+
owner="runtime.observability",
|
|
41
|
+
action="bind_observability_facade",
|
|
42
|
+
)
|
|
43
|
+
self._plan = plan
|
|
44
|
+
self._providers = providers
|
|
45
|
+
self._noop = NoopObservability()
|
|
46
|
+
|
|
47
|
+
def status(self) -> CapabilityStatus:
|
|
48
|
+
metrics = merged_capability_status(self._plan, self._providers, "metrics")
|
|
49
|
+
tracing = merged_capability_status(self._plan, self._providers, "tracing")
|
|
50
|
+
statuses = (metrics, tracing)
|
|
51
|
+
if any(status == "unavailable" for status in statuses):
|
|
52
|
+
return "unavailable"
|
|
53
|
+
if any(status == "ready" for status in statuses):
|
|
54
|
+
return "ready"
|
|
55
|
+
if any(status == "enabled" for status in statuses):
|
|
56
|
+
return "enabled"
|
|
57
|
+
return "disabled"
|
|
58
|
+
|
|
59
|
+
def _port(self) -> ObservabilityPort:
|
|
60
|
+
for capability in ("metrics", "tracing"):
|
|
61
|
+
if not self._providers.declared_for(capability):
|
|
62
|
+
continue
|
|
63
|
+
try:
|
|
64
|
+
provider = self._providers.require(capability)
|
|
65
|
+
except PzError:
|
|
66
|
+
continue
|
|
67
|
+
if isinstance(provider, ObservabilityPort):
|
|
68
|
+
return provider
|
|
69
|
+
return self._noop
|
|
70
|
+
|
|
71
|
+
def logger(self, name: str):
|
|
72
|
+
name = _require_observability_name(name, action="logger")
|
|
73
|
+
return self._port().logger(name)
|
|
74
|
+
|
|
75
|
+
def counter(
|
|
76
|
+
self,
|
|
77
|
+
name: str,
|
|
78
|
+
*,
|
|
79
|
+
unit: str | None = None,
|
|
80
|
+
description: str | None = None,
|
|
81
|
+
):
|
|
82
|
+
name = _require_observability_name(name, action="counter")
|
|
83
|
+
return self._port().counter(name, unit=unit, description=description)
|
|
84
|
+
|
|
85
|
+
def histogram(
|
|
86
|
+
self,
|
|
87
|
+
name: str,
|
|
88
|
+
*,
|
|
89
|
+
unit: str | None = None,
|
|
90
|
+
description: str | None = None,
|
|
91
|
+
):
|
|
92
|
+
name = _require_observability_name(name, action="histogram")
|
|
93
|
+
return self._port().histogram(name, unit=unit, description=description)
|
|
94
|
+
|
|
95
|
+
def span(
|
|
96
|
+
self,
|
|
97
|
+
name: str,
|
|
98
|
+
*,
|
|
99
|
+
attributes: Mapping[str, object] | None = None,
|
|
100
|
+
):
|
|
101
|
+
name = _require_observability_name(name, action="span")
|
|
102
|
+
return self._port().span(name, attributes=attributes)
|