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,724 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import importlib
|
|
5
|
+
import traceback
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from types import SimpleNamespace
|
|
8
|
+
from typing import Any, Callable, Optional
|
|
9
|
+
|
|
10
|
+
from ..observability import get_logger
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from tortoise import Tortoise, connections
|
|
14
|
+
except Exception: # pragma: no cover
|
|
15
|
+
Tortoise = None # type: ignore[assignment]
|
|
16
|
+
connections = None # type: ignore[assignment]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
logger = get_logger(__name__)
|
|
20
|
+
|
|
21
|
+
ConfigBuilder = Callable[[], dict[str, Any]]
|
|
22
|
+
ClusterFactory = Callable[[], Any]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True, slots=True)
|
|
26
|
+
class _RuntimeStateInspection:
|
|
27
|
+
connections_compatible: bool
|
|
28
|
+
apps_compatible: bool
|
|
29
|
+
reasons: tuple[str, ...]
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def compatible(self) -> bool:
|
|
33
|
+
return self.connections_compatible and self.apps_compatible
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def can_rebuild_aliases(self) -> bool:
|
|
37
|
+
return (not self.connections_compatible) and self.apps_compatible
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class _FallbackConnection:
|
|
41
|
+
def __init__(self, alias: str, payload: Any) -> None:
|
|
42
|
+
self.alias = alias
|
|
43
|
+
self.payload = payload
|
|
44
|
+
|
|
45
|
+
async def execute_query(self, query: str, params: list[Any] | None = None):
|
|
46
|
+
return {"alias": self.alias, "query": query, "params": list(params or []), "payload": self.payload}
|
|
47
|
+
|
|
48
|
+
def in_transaction(self):
|
|
49
|
+
class _Tx:
|
|
50
|
+
async def __aenter__(self_inner):
|
|
51
|
+
return self_inner
|
|
52
|
+
|
|
53
|
+
async def __aexit__(self_inner, exc_type, exc, tb):
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
return _Tx()
|
|
57
|
+
|
|
58
|
+
async def close(self) -> None:
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class _TortoiseManager:
|
|
63
|
+
def __init__(self) -> None:
|
|
64
|
+
self._init_lock = asyncio.Lock()
|
|
65
|
+
self._initialized: bool = False
|
|
66
|
+
self._last_config: Optional[dict[str, Any]] = None
|
|
67
|
+
self._cluster = None
|
|
68
|
+
self._ctx = None
|
|
69
|
+
self._pool_close_patched = False
|
|
70
|
+
self._fallback_connections: dict[str, _FallbackConnection] = {}
|
|
71
|
+
self._last_reconnect_at: dict[str, float] = {}
|
|
72
|
+
self._reconnect_min_interval = 0.1
|
|
73
|
+
self._loop_id: int | None = None
|
|
74
|
+
|
|
75
|
+
async def _close_tortoise_connections_quietly(self, *, reason: str) -> None:
|
|
76
|
+
if Tortoise is None:
|
|
77
|
+
return
|
|
78
|
+
try:
|
|
79
|
+
await Tortoise.close_connections()
|
|
80
|
+
except asyncio.CancelledError:
|
|
81
|
+
# Safe to ignore here because this path only runs while tearing down or
|
|
82
|
+
# replacing an old Tortoise connection set. Treating it as cleanup noise
|
|
83
|
+
# avoids surfacing a transient shutdown cancellation as a startup failure.
|
|
84
|
+
try:
|
|
85
|
+
logger.debug("Ignored CancelledError from Tortoise.close_connections during %s", reason)
|
|
86
|
+
except Exception:
|
|
87
|
+
pass
|
|
88
|
+
except Exception:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
def _install_pool_close_probe(self) -> None:
|
|
92
|
+
if self._pool_close_patched:
|
|
93
|
+
return
|
|
94
|
+
try:
|
|
95
|
+
import asyncpg.pool as _ap # type: ignore
|
|
96
|
+
except Exception:
|
|
97
|
+
return
|
|
98
|
+
original_close = _ap.Pool.close
|
|
99
|
+
|
|
100
|
+
async def _logged_close(pool, *args, **kwargs):
|
|
101
|
+
try:
|
|
102
|
+
logger.error("asyncpg pool.close called\n%s", "".join(traceback.format_stack(limit=12)))
|
|
103
|
+
except Exception:
|
|
104
|
+
pass
|
|
105
|
+
return await original_close(pool, *args, **kwargs)
|
|
106
|
+
|
|
107
|
+
_ap.Pool.close = _logged_close
|
|
108
|
+
self._pool_close_patched = True
|
|
109
|
+
|
|
110
|
+
def _configured_aliases(self) -> tuple[str, ...]:
|
|
111
|
+
return tuple((self._last_config or {}).get("connections", {}).keys())
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
def _current_loop_id() -> int | None:
|
|
115
|
+
try:
|
|
116
|
+
return id(asyncio.get_running_loop())
|
|
117
|
+
except RuntimeError:
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
def _get_active_context() -> Any | None:
|
|
122
|
+
try:
|
|
123
|
+
if connections is None:
|
|
124
|
+
return None
|
|
125
|
+
if not bool(getattr(connections, "_db_config", None)) and not connections._get_storage():
|
|
126
|
+
return None
|
|
127
|
+
return SimpleNamespace(
|
|
128
|
+
connections=connections,
|
|
129
|
+
inited=bool(getattr(connections, "_db_config", None)),
|
|
130
|
+
)
|
|
131
|
+
except Exception:
|
|
132
|
+
return None
|
|
133
|
+
|
|
134
|
+
def _adopt_active_context(self, cfg: dict[str, Any], *, current_loop_id: int | None = None) -> bool:
|
|
135
|
+
ctx = self._get_active_context()
|
|
136
|
+
if ctx is None or not bool(getattr(ctx, "inited", False)):
|
|
137
|
+
return False
|
|
138
|
+
try:
|
|
139
|
+
available_aliases = set(getattr(ctx.connections, "_db_config", {}) or {})
|
|
140
|
+
except Exception:
|
|
141
|
+
return False
|
|
142
|
+
expected_aliases = set((cfg or {}).get("connections", {}).keys())
|
|
143
|
+
if not available_aliases:
|
|
144
|
+
return False
|
|
145
|
+
if expected_aliases and not expected_aliases.intersection(available_aliases) and "default" not in available_aliases:
|
|
146
|
+
return False
|
|
147
|
+
inspection = self._inspect_runtime_state(cfg, current_loop_id=current_loop_id)
|
|
148
|
+
if not inspection.compatible:
|
|
149
|
+
return False
|
|
150
|
+
self._ctx = ctx
|
|
151
|
+
self._initialized = True
|
|
152
|
+
self._install_pool_close_probe()
|
|
153
|
+
return True
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def _configs_equivalent(left: dict[str, Any] | None, right: dict[str, Any] | None) -> bool:
|
|
157
|
+
return dict(left or {}) == dict(right or {})
|
|
158
|
+
|
|
159
|
+
def _expected_model_names(self, cfg: dict[str, Any]) -> dict[str, set[str]]:
|
|
160
|
+
if Tortoise is None:
|
|
161
|
+
return {}
|
|
162
|
+
expected: dict[str, set[str]] = {}
|
|
163
|
+
for app_label, app_cfg in (cfg or {}).get("apps", {}).items():
|
|
164
|
+
discovered: set[str] = set()
|
|
165
|
+
for module_path in app_cfg.get("models", []):
|
|
166
|
+
try:
|
|
167
|
+
models = Tortoise._discover_models(module_path, app_label)
|
|
168
|
+
except Exception:
|
|
169
|
+
return {}
|
|
170
|
+
for model in models:
|
|
171
|
+
discovered.add(f"{model.__module__}.{model.__name__}")
|
|
172
|
+
expected[app_label] = discovered
|
|
173
|
+
return expected
|
|
174
|
+
|
|
175
|
+
@staticmethod
|
|
176
|
+
def _expected_aliases(cfg: dict[str, Any]) -> tuple[str, ...]:
|
|
177
|
+
return tuple(dict((cfg or {}).get("connections", {}) or {}).keys())
|
|
178
|
+
|
|
179
|
+
def _inspect_runtime_state(
|
|
180
|
+
self,
|
|
181
|
+
cfg: dict[str, Any],
|
|
182
|
+
*,
|
|
183
|
+
current_loop_id: int | None = None,
|
|
184
|
+
) -> _RuntimeStateInspection:
|
|
185
|
+
connection_reasons = self._inspect_connection_state(cfg, current_loop_id=current_loop_id)
|
|
186
|
+
app_reasons = self._inspect_app_state(cfg)
|
|
187
|
+
return _RuntimeStateInspection(
|
|
188
|
+
connections_compatible=not connection_reasons,
|
|
189
|
+
apps_compatible=not app_reasons,
|
|
190
|
+
reasons=tuple((*connection_reasons, *app_reasons)),
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
def _inspect_connection_state(
|
|
194
|
+
self,
|
|
195
|
+
cfg: dict[str, Any],
|
|
196
|
+
*,
|
|
197
|
+
current_loop_id: int | None = None,
|
|
198
|
+
) -> tuple[str, ...]:
|
|
199
|
+
expected_aliases = set(self._expected_aliases(cfg))
|
|
200
|
+
if Tortoise is None or connections is None:
|
|
201
|
+
if expected_aliases.issubset(set(self._fallback_connections.keys())):
|
|
202
|
+
return ()
|
|
203
|
+
missing = sorted(expected_aliases.difference(self._fallback_connections.keys()))
|
|
204
|
+
return tuple(f"missing_fallback_alias:{alias}" for alias in missing)
|
|
205
|
+
|
|
206
|
+
reasons: list[str] = []
|
|
207
|
+
try:
|
|
208
|
+
current_aliases = set(getattr(connections, "db_config", {}).keys())
|
|
209
|
+
except Exception:
|
|
210
|
+
return ("db_config_unavailable",)
|
|
211
|
+
missing_config_aliases = sorted(expected_aliases.difference(current_aliases))
|
|
212
|
+
reasons.extend(f"missing_db_config_alias:{alias}" for alias in missing_config_aliases)
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
storage = connections._get_storage()
|
|
216
|
+
except Exception:
|
|
217
|
+
return (*reasons, "connection_storage_unavailable")
|
|
218
|
+
|
|
219
|
+
live_aliases = set(storage.keys())
|
|
220
|
+
missing_live_aliases = sorted(expected_aliases.difference(live_aliases))
|
|
221
|
+
reasons.extend(f"missing_live_alias:{alias}" for alias in missing_live_aliases)
|
|
222
|
+
|
|
223
|
+
for alias in expected_aliases.intersection(live_aliases):
|
|
224
|
+
conn = storage.get(alias)
|
|
225
|
+
pool = getattr(conn, "_pool", None)
|
|
226
|
+
if pool is not None and bool(getattr(pool, "closed", False) or getattr(pool, "_closed", False)):
|
|
227
|
+
reasons.append(f"closed_pool:{alias}")
|
|
228
|
+
continue
|
|
229
|
+
if not self._connection_pools_match_loop(
|
|
230
|
+
storage,
|
|
231
|
+
{alias},
|
|
232
|
+
current_loop_id=current_loop_id,
|
|
233
|
+
):
|
|
234
|
+
reasons.append(f"loop_mismatch:{alias}")
|
|
235
|
+
return tuple(reasons)
|
|
236
|
+
|
|
237
|
+
def _inspect_app_state(self, cfg: dict[str, Any]) -> tuple[str, ...]:
|
|
238
|
+
if Tortoise is None:
|
|
239
|
+
return ()
|
|
240
|
+
|
|
241
|
+
reasons: list[str] = []
|
|
242
|
+
try:
|
|
243
|
+
expected_apps = set((cfg or {}).get("apps", {}).keys())
|
|
244
|
+
current_apps = getattr(Tortoise, "apps", {}) or {}
|
|
245
|
+
missing_apps = sorted(expected_apps.difference(set(current_apps.keys())))
|
|
246
|
+
reasons.extend(f"missing_app_registry:{app_label}" for app_label in missing_apps)
|
|
247
|
+
|
|
248
|
+
expected_model_names = self._expected_model_names(cfg)
|
|
249
|
+
if expected_apps and not expected_model_names:
|
|
250
|
+
reasons.append("expected_models_unavailable")
|
|
251
|
+
return tuple(reasons)
|
|
252
|
+
|
|
253
|
+
for app_label in sorted(expected_apps):
|
|
254
|
+
app_models = current_apps.get(app_label)
|
|
255
|
+
if not app_models:
|
|
256
|
+
if f"missing_app_registry:{app_label}" not in reasons:
|
|
257
|
+
reasons.append(f"missing_models:{app_label}")
|
|
258
|
+
continue
|
|
259
|
+
current_model_names = {f"{model.__module__}.{model.__name__}" for model in app_models.values()}
|
|
260
|
+
expected_names = expected_model_names.get(app_label, set())
|
|
261
|
+
if current_model_names != expected_names:
|
|
262
|
+
reasons.append(f"model_registry_mismatch:{app_label}")
|
|
263
|
+
for model in app_models.values():
|
|
264
|
+
meta = getattr(model, "_meta", None)
|
|
265
|
+
if meta is None:
|
|
266
|
+
continue
|
|
267
|
+
if getattr(meta, "default_connection", None) is None:
|
|
268
|
+
reasons.append(f"missing_default_connection:{model.__module__}.{model.__name__}")
|
|
269
|
+
basequery = getattr(meta, "basequery", None)
|
|
270
|
+
if basequery is None or not hasattr(basequery, "get_parameterized_sql"):
|
|
271
|
+
reasons.append(f"missing_basequery:{model.__module__}.{model.__name__}")
|
|
272
|
+
except Exception:
|
|
273
|
+
return ("app_registry_inspection_failed",)
|
|
274
|
+
return tuple(reasons)
|
|
275
|
+
|
|
276
|
+
def _is_current_state_compatible(self, cfg: dict[str, Any], *, current_loop_id: int | None = None) -> bool:
|
|
277
|
+
inspection = self._inspect_runtime_state(cfg, current_loop_id=current_loop_id)
|
|
278
|
+
return inspection.compatible
|
|
279
|
+
|
|
280
|
+
@staticmethod
|
|
281
|
+
def _pool_matches_loop(pool: Any, *, current_loop_id: int | None) -> bool:
|
|
282
|
+
if current_loop_id is None or pool is None:
|
|
283
|
+
return True
|
|
284
|
+
pool_loop = getattr(pool, "_loop", None)
|
|
285
|
+
if pool_loop is not None and id(pool_loop) != current_loop_id:
|
|
286
|
+
return False
|
|
287
|
+
return True
|
|
288
|
+
|
|
289
|
+
@staticmethod
|
|
290
|
+
def _connection_pools_match_loop(
|
|
291
|
+
storage: dict[str, Any],
|
|
292
|
+
expected_aliases: set[str],
|
|
293
|
+
*,
|
|
294
|
+
current_loop_id: int | None,
|
|
295
|
+
) -> bool:
|
|
296
|
+
if current_loop_id is None:
|
|
297
|
+
return True
|
|
298
|
+
for alias in expected_aliases:
|
|
299
|
+
conn = storage.get(alias)
|
|
300
|
+
pool = getattr(conn, "_pool", None)
|
|
301
|
+
if pool is not None and not _TortoiseManager._pool_matches_loop(pool, current_loop_id=current_loop_id):
|
|
302
|
+
return False
|
|
303
|
+
if pool is None:
|
|
304
|
+
continue
|
|
305
|
+
workers = tuple(getattr(pool, "_workers", ()) or ())
|
|
306
|
+
scheduler = getattr(pool, "_sched_runner", None)
|
|
307
|
+
maintenance_tasks = tuple(task for task in (*workers, scheduler) if task is not None)
|
|
308
|
+
if not maintenance_tasks and hasattr(pool, "_tasks"):
|
|
309
|
+
return False
|
|
310
|
+
for task in maintenance_tasks:
|
|
311
|
+
done = getattr(task, "done", None)
|
|
312
|
+
if callable(done):
|
|
313
|
+
try:
|
|
314
|
+
if bool(done()):
|
|
315
|
+
return False
|
|
316
|
+
except Exception:
|
|
317
|
+
return False
|
|
318
|
+
get_loop = getattr(task, "get_loop", None)
|
|
319
|
+
if not callable(get_loop):
|
|
320
|
+
continue
|
|
321
|
+
try:
|
|
322
|
+
task_loop = get_loop()
|
|
323
|
+
except Exception:
|
|
324
|
+
return False
|
|
325
|
+
if id(task_loop) != current_loop_id:
|
|
326
|
+
return False
|
|
327
|
+
return True
|
|
328
|
+
|
|
329
|
+
def _connection_pool_ready(self, alias: str) -> bool:
|
|
330
|
+
if connections is None:
|
|
331
|
+
return alias in self._fallback_connections
|
|
332
|
+
try:
|
|
333
|
+
storage = connections._get_storage()
|
|
334
|
+
conn = storage.get(alias)
|
|
335
|
+
except Exception:
|
|
336
|
+
return False
|
|
337
|
+
if conn is None:
|
|
338
|
+
return False
|
|
339
|
+
pool = getattr(conn, "_pool", None)
|
|
340
|
+
if pool is not None and bool(getattr(pool, "closed", False) or getattr(pool, "_closed", False)):
|
|
341
|
+
return False
|
|
342
|
+
return self._connection_pools_match_loop(
|
|
343
|
+
storage,
|
|
344
|
+
{alias},
|
|
345
|
+
current_loop_id=self._current_loop_id(),
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
@staticmethod
|
|
349
|
+
def _has_real_tortoise_config(cfg: dict[str, Any]) -> bool:
|
|
350
|
+
connections_cfg = dict((cfg or {}).get("connections", {}))
|
|
351
|
+
if not connections_cfg:
|
|
352
|
+
return False
|
|
353
|
+
for payload in connections_cfg.values():
|
|
354
|
+
if not isinstance(payload, dict):
|
|
355
|
+
return False
|
|
356
|
+
if payload.get("db_url"):
|
|
357
|
+
continue
|
|
358
|
+
engine = payload.get("engine")
|
|
359
|
+
if isinstance(engine, str) and engine.strip():
|
|
360
|
+
try:
|
|
361
|
+
importlib.import_module(engine.strip())
|
|
362
|
+
except Exception:
|
|
363
|
+
return False
|
|
364
|
+
continue
|
|
365
|
+
return False
|
|
366
|
+
return True
|
|
367
|
+
|
|
368
|
+
@staticmethod
|
|
369
|
+
def _bind_configured_models(cfg: dict[str, Any]) -> None:
|
|
370
|
+
if Tortoise is None:
|
|
371
|
+
return
|
|
372
|
+
current_apps = getattr(Tortoise, "apps", None)
|
|
373
|
+
if not isinstance(current_apps, dict):
|
|
374
|
+
return
|
|
375
|
+
for app_label, app_cfg in dict((cfg or {}).get("apps", {}) or {}).items():
|
|
376
|
+
modules = tuple(app_cfg.get("models", ()) or ())
|
|
377
|
+
if not modules:
|
|
378
|
+
continue
|
|
379
|
+
default_connection = str(app_cfg.get("default_connection") or "default").strip() or "default"
|
|
380
|
+
registry = current_apps.setdefault(app_label, {})
|
|
381
|
+
for module_path in modules:
|
|
382
|
+
try:
|
|
383
|
+
models = Tortoise._discover_models(module_path, app_label)
|
|
384
|
+
except Exception:
|
|
385
|
+
continue
|
|
386
|
+
for model in models:
|
|
387
|
+
meta = getattr(model, "_meta", None)
|
|
388
|
+
if meta is None:
|
|
389
|
+
continue
|
|
390
|
+
if getattr(meta, "default_connection", None) is None:
|
|
391
|
+
meta.default_connection = default_connection
|
|
392
|
+
registry.setdefault(getattr(model, "__name__", str(id(model))), model)
|
|
393
|
+
|
|
394
|
+
@staticmethod
|
|
395
|
+
def _clear_global_context_fallback() -> None:
|
|
396
|
+
return None
|
|
397
|
+
|
|
398
|
+
async def _init_with_config(self, cfg: dict[str, Any]) -> None:
|
|
399
|
+
if Tortoise is None or not self._has_real_tortoise_config(cfg):
|
|
400
|
+
if Tortoise is not None:
|
|
401
|
+
await self._close_tortoise_connections_quietly(reason="fallback init")
|
|
402
|
+
self._clear_global_context_fallback()
|
|
403
|
+
try:
|
|
404
|
+
await Tortoise._reset_apps()
|
|
405
|
+
except Exception:
|
|
406
|
+
pass
|
|
407
|
+
self._ctx = None
|
|
408
|
+
self._fallback_connections = {
|
|
409
|
+
alias: _FallbackConnection(alias, payload)
|
|
410
|
+
for alias, payload in dict((cfg or {}).get("connections", {})).items()
|
|
411
|
+
}
|
|
412
|
+
return
|
|
413
|
+
|
|
414
|
+
self._fallback_connections.clear()
|
|
415
|
+
await self._close_tortoise_connections_quietly(reason="real init")
|
|
416
|
+
self._clear_global_context_fallback()
|
|
417
|
+
try:
|
|
418
|
+
await Tortoise._reset_apps()
|
|
419
|
+
except Exception:
|
|
420
|
+
pass
|
|
421
|
+
|
|
422
|
+
try:
|
|
423
|
+
self._ctx = await Tortoise.init(config=cfg, _enable_global_fallback=True) # type: ignore[call-arg]
|
|
424
|
+
except TypeError:
|
|
425
|
+
self._ctx = await Tortoise.init(config=cfg)
|
|
426
|
+
except Exception as exc:
|
|
427
|
+
if "Global context fallback is already enabled" in str(exc):
|
|
428
|
+
self._ctx = await Tortoise.init(config=cfg, _enable_global_fallback=False) # type: ignore[call-arg]
|
|
429
|
+
else:
|
|
430
|
+
raise
|
|
431
|
+
|
|
432
|
+
async def _close_connection(self, conn: Any) -> None:
|
|
433
|
+
if conn is None:
|
|
434
|
+
return
|
|
435
|
+
close = getattr(conn, "close", None)
|
|
436
|
+
if close is None:
|
|
437
|
+
return
|
|
438
|
+
try:
|
|
439
|
+
result = close()
|
|
440
|
+
if asyncio.iscoroutine(result):
|
|
441
|
+
await result
|
|
442
|
+
except asyncio.CancelledError:
|
|
443
|
+
# Alias rebuild publishes the replacement connection before retiring the
|
|
444
|
+
# previous pool. Cross-loop or already-tearing-down pools can surface a
|
|
445
|
+
# cancellation during close; once the new alias is live this is cleanup
|
|
446
|
+
# noise, not a fatal runtime initialization failure.
|
|
447
|
+
try:
|
|
448
|
+
logger.debug("Ignored CancelledError while retiring rebuilt DB connection")
|
|
449
|
+
except Exception:
|
|
450
|
+
pass
|
|
451
|
+
except Exception:
|
|
452
|
+
pass
|
|
453
|
+
|
|
454
|
+
async def _close_cluster(self, cluster: Any) -> None:
|
|
455
|
+
if cluster is None:
|
|
456
|
+
return
|
|
457
|
+
for attr_name in ("aclose", "close", "shutdown", "disconnect"):
|
|
458
|
+
closer = getattr(cluster, attr_name, None)
|
|
459
|
+
if closer is None:
|
|
460
|
+
continue
|
|
461
|
+
try:
|
|
462
|
+
result = closer()
|
|
463
|
+
if asyncio.iscoroutine(result):
|
|
464
|
+
await result
|
|
465
|
+
except Exception:
|
|
466
|
+
pass
|
|
467
|
+
return
|
|
468
|
+
|
|
469
|
+
def _rebuild_connection(self, alias: str):
|
|
470
|
+
if connections is None:
|
|
471
|
+
old_conn = self._fallback_connections.get(alias)
|
|
472
|
+
payload = ((self._last_config or {}).get("connections", {}) or {}).get(alias)
|
|
473
|
+
if payload is not None:
|
|
474
|
+
self._fallback_connections[alias] = _FallbackConnection(alias, payload)
|
|
475
|
+
return old_conn, self._fallback_connections.get(alias)
|
|
476
|
+
|
|
477
|
+
old_conn = connections.discard(alias)
|
|
478
|
+
new_conn = connections._create_connection(alias)
|
|
479
|
+
connections._get_storage()[alias] = new_conn
|
|
480
|
+
return old_conn, new_conn
|
|
481
|
+
|
|
482
|
+
def _rebuild_aliases(self, aliases: tuple[str, ...]) -> list[Any]:
|
|
483
|
+
rebuilt_old_connections: list[Any] = []
|
|
484
|
+
for name in aliases:
|
|
485
|
+
old_conn, _new_conn = self._rebuild_connection(name)
|
|
486
|
+
rebuilt_old_connections.append(old_conn)
|
|
487
|
+
self._install_pool_close_probe()
|
|
488
|
+
scope_key = ",".join(aliases) if aliases else "*"
|
|
489
|
+
self._last_reconnect_at[scope_key] = asyncio.get_running_loop().time()
|
|
490
|
+
return rebuilt_old_connections
|
|
491
|
+
|
|
492
|
+
def has_connection(self, alias: str) -> bool:
|
|
493
|
+
if self._fallback_connections:
|
|
494
|
+
return alias in self._fallback_connections
|
|
495
|
+
if connections is None:
|
|
496
|
+
return False
|
|
497
|
+
try:
|
|
498
|
+
return alias in connections._get_storage()
|
|
499
|
+
except Exception:
|
|
500
|
+
return False
|
|
501
|
+
|
|
502
|
+
def get_connection(self, alias: str):
|
|
503
|
+
if self._fallback_connections:
|
|
504
|
+
if alias not in self._fallback_connections:
|
|
505
|
+
raise KeyError(alias)
|
|
506
|
+
return self._fallback_connections[alias]
|
|
507
|
+
if Tortoise is None:
|
|
508
|
+
raise KeyError(alias)
|
|
509
|
+
return Tortoise.get_connection(alias)
|
|
510
|
+
|
|
511
|
+
async def generate_schemas(self, *, safe: bool = True) -> None:
|
|
512
|
+
if self._fallback_connections:
|
|
513
|
+
raise RuntimeError("schema generation is unavailable for fallback-only connections")
|
|
514
|
+
if Tortoise is None:
|
|
515
|
+
raise RuntimeError("tortoise is unavailable")
|
|
516
|
+
await Tortoise.generate_schemas(safe=safe)
|
|
517
|
+
|
|
518
|
+
async def ensure_initialized(
|
|
519
|
+
self,
|
|
520
|
+
config_builder: Optional[ConfigBuilder] = None,
|
|
521
|
+
cluster_factory: Optional[ClusterFactory] = None,
|
|
522
|
+
) -> None:
|
|
523
|
+
cfg_builder = config_builder or self._default_config_builder()
|
|
524
|
+
cfg = cfg_builder() if cfg_builder is not None else {"connections": {"default": {}}, "apps": {}}
|
|
525
|
+
wants_fallback_only = not self._has_real_tortoise_config(cfg)
|
|
526
|
+
current_loop_id = self._current_loop_id()
|
|
527
|
+
loop_changed = (
|
|
528
|
+
current_loop_id is not None
|
|
529
|
+
and self._loop_id is not None
|
|
530
|
+
and self._loop_id != current_loop_id
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
if wants_fallback_only:
|
|
534
|
+
if self._initialized and self._configs_equivalent(self._last_config, cfg) and (
|
|
535
|
+
self._fallback_connections or self._is_current_state_compatible(cfg)
|
|
536
|
+
):
|
|
537
|
+
self._last_config = cfg
|
|
538
|
+
self._loop_id = current_loop_id
|
|
539
|
+
return
|
|
540
|
+
async with self._init_lock:
|
|
541
|
+
if self._initialized and self._configs_equivalent(self._last_config, cfg) and (
|
|
542
|
+
self._fallback_connections or self._is_current_state_compatible(cfg)
|
|
543
|
+
):
|
|
544
|
+
self._last_config = cfg
|
|
545
|
+
self._loop_id = current_loop_id
|
|
546
|
+
return
|
|
547
|
+
|
|
548
|
+
old_cluster = self._cluster
|
|
549
|
+
self._last_config = cfg
|
|
550
|
+
await self._init_with_config(cfg)
|
|
551
|
+
new_cluster = None
|
|
552
|
+
try:
|
|
553
|
+
if cluster_factory is not None:
|
|
554
|
+
new_cluster = cluster_factory()
|
|
555
|
+
except Exception:
|
|
556
|
+
new_cluster = None
|
|
557
|
+
self._cluster = new_cluster
|
|
558
|
+
if old_cluster is not None and old_cluster is not new_cluster:
|
|
559
|
+
await self._close_cluster(old_cluster)
|
|
560
|
+
self._initialized = True
|
|
561
|
+
self._loop_id = current_loop_id
|
|
562
|
+
return
|
|
563
|
+
|
|
564
|
+
if self._adopt_active_context(cfg, current_loop_id=current_loop_id):
|
|
565
|
+
self._last_config = cfg
|
|
566
|
+
self._loop_id = current_loop_id
|
|
567
|
+
return
|
|
568
|
+
|
|
569
|
+
if Tortoise is not None and bool(getattr(Tortoise, "_inited", False)):
|
|
570
|
+
if (
|
|
571
|
+
not loop_changed
|
|
572
|
+
and self._is_current_state_compatible(cfg, current_loop_id=current_loop_id)
|
|
573
|
+
and self._configs_equivalent(self._last_config, cfg)
|
|
574
|
+
):
|
|
575
|
+
try:
|
|
576
|
+
self._ctx = self._ctx or self._get_active_context()
|
|
577
|
+
except Exception:
|
|
578
|
+
pass
|
|
579
|
+
self._initialized = True
|
|
580
|
+
self._last_config = cfg
|
|
581
|
+
self._loop_id = current_loop_id
|
|
582
|
+
self._install_pool_close_probe()
|
|
583
|
+
return
|
|
584
|
+
|
|
585
|
+
if (
|
|
586
|
+
not loop_changed
|
|
587
|
+
and self._initialized
|
|
588
|
+
and self._is_current_state_compatible(cfg, current_loop_id=current_loop_id)
|
|
589
|
+
and self._configs_equivalent(self._last_config, cfg)
|
|
590
|
+
):
|
|
591
|
+
self._last_config = cfg
|
|
592
|
+
self._loop_id = current_loop_id
|
|
593
|
+
return
|
|
594
|
+
|
|
595
|
+
rebuilt_old_connections: list[Any] = []
|
|
596
|
+
should_close_rebuilt_connections = False
|
|
597
|
+
handled_rebuild = False
|
|
598
|
+
async with self._init_lock:
|
|
599
|
+
inspection = self._inspect_runtime_state(cfg, current_loop_id=current_loop_id)
|
|
600
|
+
if (
|
|
601
|
+
self._initialized
|
|
602
|
+
and inspection.compatible
|
|
603
|
+
and self._configs_equivalent(self._last_config, cfg)
|
|
604
|
+
):
|
|
605
|
+
if loop_changed and not wants_fallback_only:
|
|
606
|
+
aliases = self._configured_aliases()
|
|
607
|
+
rebuilt_old_connections = self._rebuild_aliases(aliases)
|
|
608
|
+
should_close_rebuilt_connections = True
|
|
609
|
+
self._last_config = cfg
|
|
610
|
+
self._loop_id = current_loop_id
|
|
611
|
+
handled_rebuild = True
|
|
612
|
+
if not should_close_rebuilt_connections:
|
|
613
|
+
return
|
|
614
|
+
|
|
615
|
+
elif (
|
|
616
|
+
self._initialized
|
|
617
|
+
and not wants_fallback_only
|
|
618
|
+
and inspection.can_rebuild_aliases
|
|
619
|
+
and self._configs_equivalent(self._last_config, cfg)
|
|
620
|
+
):
|
|
621
|
+
aliases = self._expected_aliases(cfg)
|
|
622
|
+
logger.warning(
|
|
623
|
+
"Tortoise runtime degraded; rebuilding configured aliases without full re-init: %s",
|
|
624
|
+
", ".join(inspection.reasons) or "unknown",
|
|
625
|
+
)
|
|
626
|
+
rebuilt_old_connections = self._rebuild_aliases(aliases)
|
|
627
|
+
should_close_rebuilt_connections = True
|
|
628
|
+
self._last_config = cfg
|
|
629
|
+
self._loop_id = current_loop_id
|
|
630
|
+
handled_rebuild = True
|
|
631
|
+
if not should_close_rebuilt_connections:
|
|
632
|
+
return
|
|
633
|
+
|
|
634
|
+
else:
|
|
635
|
+
if self._initialized and not inspection.compatible:
|
|
636
|
+
logger.warning(
|
|
637
|
+
"Tortoise runtime requires full re-init: %s",
|
|
638
|
+
", ".join(inspection.reasons) or "unknown",
|
|
639
|
+
)
|
|
640
|
+
old_cluster = self._cluster
|
|
641
|
+
self._last_config = cfg
|
|
642
|
+
await self._init_with_config(cfg)
|
|
643
|
+
self._bind_configured_models(cfg)
|
|
644
|
+
self._install_pool_close_probe()
|
|
645
|
+
new_cluster = None
|
|
646
|
+
try:
|
|
647
|
+
if cluster_factory is not None:
|
|
648
|
+
new_cluster = cluster_factory()
|
|
649
|
+
except Exception:
|
|
650
|
+
new_cluster = None
|
|
651
|
+
self._cluster = new_cluster
|
|
652
|
+
if old_cluster is not None and old_cluster is not new_cluster:
|
|
653
|
+
await self._close_cluster(old_cluster)
|
|
654
|
+
self._initialized = True
|
|
655
|
+
self._loop_id = current_loop_id
|
|
656
|
+
|
|
657
|
+
if handled_rebuild and should_close_rebuilt_connections:
|
|
658
|
+
for old_conn in rebuilt_old_connections:
|
|
659
|
+
await self._close_connection(old_conn)
|
|
660
|
+
return
|
|
661
|
+
|
|
662
|
+
@staticmethod
|
|
663
|
+
def _default_config_builder() -> Optional[ConfigBuilder]:
|
|
664
|
+
try:
|
|
665
|
+
from pz_foundation.host.db.config import get_tortoise_config
|
|
666
|
+
|
|
667
|
+
return get_tortoise_config
|
|
668
|
+
except Exception:
|
|
669
|
+
return None
|
|
670
|
+
|
|
671
|
+
async def reconnect(self, alias: str | None = None) -> None:
|
|
672
|
+
if self._last_config is None:
|
|
673
|
+
await self.ensure_initialized()
|
|
674
|
+
return
|
|
675
|
+
|
|
676
|
+
async with self._init_lock:
|
|
677
|
+
aliases = self._configured_aliases()
|
|
678
|
+
if alias is not None:
|
|
679
|
+
aliases = tuple(name for name in aliases if name == alias)
|
|
680
|
+
if not aliases:
|
|
681
|
+
raise KeyError(alias)
|
|
682
|
+
scope_key = ",".join(aliases) if aliases else "*"
|
|
683
|
+
now = asyncio.get_running_loop().time()
|
|
684
|
+
last_reconnect_at = self._last_reconnect_at.get(scope_key, 0.0)
|
|
685
|
+
if last_reconnect_at and now - last_reconnect_at < self._reconnect_min_interval:
|
|
686
|
+
return
|
|
687
|
+
rebuilt_old_connections = self._rebuild_aliases(aliases)
|
|
688
|
+
|
|
689
|
+
for old_conn in rebuilt_old_connections:
|
|
690
|
+
await self._close_connection(old_conn)
|
|
691
|
+
|
|
692
|
+
async def close_connections(self) -> None:
|
|
693
|
+
old_cluster = self._cluster
|
|
694
|
+
if self._fallback_connections:
|
|
695
|
+
self._fallback_connections.clear()
|
|
696
|
+
if Tortoise is not None:
|
|
697
|
+
await self._close_tortoise_connections_quietly(reason="manager close")
|
|
698
|
+
self._clear_global_context_fallback()
|
|
699
|
+
self._last_config = None
|
|
700
|
+
self._cluster = None
|
|
701
|
+
self._ctx = None
|
|
702
|
+
self._initialized = False
|
|
703
|
+
self._last_reconnect_at.clear()
|
|
704
|
+
self._loop_id = None
|
|
705
|
+
await self._close_cluster(old_cluster)
|
|
706
|
+
|
|
707
|
+
async def get_connection_status(self) -> dict[str, Any]:
|
|
708
|
+
from .lifecycle_status import connection_lifecycle_status
|
|
709
|
+
|
|
710
|
+
lifecycle = connection_lifecycle_status(self)
|
|
711
|
+
return {
|
|
712
|
+
"default": lifecycle.alias_status("default"),
|
|
713
|
+
"read_only": lifecycle.alias_status("read_only"),
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
tortoise_manager = _TortoiseManager()
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
async def get_connection_status() -> dict[str, Any]:
|
|
721
|
+
return await tortoise_manager.get_connection_status()
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
__all__ = ["ConfigBuilder", "ClusterFactory", "_TortoiseManager", "get_connection_status", "tortoise_manager"]
|