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,664 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import importlib.util
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import re
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from pz_foundation.db.config import build_foundation_tortoise_config
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _require_aerich() -> None:
|
|
18
|
+
if importlib.util.find_spec("aerich") is None:
|
|
19
|
+
raise RuntimeError("Aerich is not installed. Install the project dependencies that provide the migration executor.")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _runnable_sources(migration_sources: tuple[Any, ...], *, action: str) -> tuple[Any, ...]:
|
|
23
|
+
runnable: list[Any] = []
|
|
24
|
+
for source in migration_sources:
|
|
25
|
+
if _is_shared_source_package(source):
|
|
26
|
+
continue
|
|
27
|
+
if not str(getattr(source, "models_module", "") or "").strip():
|
|
28
|
+
continue
|
|
29
|
+
files = tuple(str(item) for item in (getattr(source, "files", ()) or ()))
|
|
30
|
+
has_real_migration = any(
|
|
31
|
+
Path(item).suffix == ".py" and Path(item).name != "__init__.py"
|
|
32
|
+
for item in files
|
|
33
|
+
)
|
|
34
|
+
if has_real_migration or action == "makemigrations":
|
|
35
|
+
runnable.append(source)
|
|
36
|
+
return tuple(runnable)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _shared_init_sources(migration_sources: tuple[Any, ...]) -> tuple[Any, ...]:
|
|
40
|
+
return tuple(
|
|
41
|
+
source
|
|
42
|
+
for source in migration_sources
|
|
43
|
+
if _is_shared_source_package(source)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _is_shared_source_package(source: Any) -> bool:
|
|
48
|
+
raw_package_root = str(getattr(source, "package_root", "") or "").strip()
|
|
49
|
+
if not raw_package_root:
|
|
50
|
+
return False
|
|
51
|
+
try:
|
|
52
|
+
package_root = Path(raw_package_root).resolve()
|
|
53
|
+
except Exception:
|
|
54
|
+
return False
|
|
55
|
+
return ".sources" in package_root.parts
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _ensure_workspace_packages(*, workspace_dir: Path, migration_sources: tuple[Any, ...]) -> Path:
|
|
59
|
+
owners_dir = workspace_dir / "owners"
|
|
60
|
+
owners_dir.mkdir(parents=True, exist_ok=True)
|
|
61
|
+
(owners_dir / "__init__.py").touch(exist_ok=True)
|
|
62
|
+
for source in migration_sources:
|
|
63
|
+
owner_dir = owners_dir / str(getattr(source, "owner"))
|
|
64
|
+
owner_dir.mkdir(parents=True, exist_ok=True)
|
|
65
|
+
(owner_dir / "__init__.py").touch(exist_ok=True)
|
|
66
|
+
return owners_dir
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _build_tortoise_config(*, plan: Any, migration_sources: tuple[Any, ...]) -> dict[str, Any]:
|
|
70
|
+
installed_apps = [
|
|
71
|
+
str(getattr(source, "module_path", "")).split(":", 1)[0].strip()
|
|
72
|
+
for source in migration_sources
|
|
73
|
+
if str(getattr(source, "module_path", "")).strip()
|
|
74
|
+
]
|
|
75
|
+
return build_foundation_tortoise_config(
|
|
76
|
+
project_dir=str(plan.project.project_dir),
|
|
77
|
+
installed_apps=installed_apps,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _ensure_aerich_history_table(*, tortoise_config: dict[str, Any]) -> None:
|
|
82
|
+
if "aerich" not in (tortoise_config.get("apps", {}) or {}):
|
|
83
|
+
return
|
|
84
|
+
try:
|
|
85
|
+
from tortoise import Tortoise
|
|
86
|
+
except Exception:
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
async def _bootstrap() -> None:
|
|
90
|
+
try:
|
|
91
|
+
await Tortoise.init(
|
|
92
|
+
config={
|
|
93
|
+
"connections": tortoise_config.get("connections", {}),
|
|
94
|
+
"apps": {"aerich": tortoise_config["apps"]["aerich"]},
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
await Tortoise.generate_schemas(safe=True)
|
|
98
|
+
finally:
|
|
99
|
+
try:
|
|
100
|
+
await Tortoise.close_connections()
|
|
101
|
+
except Exception:
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
asyncio.run(_bootstrap())
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _migration_python_files(owner_dir: Path) -> tuple[Path, ...]:
|
|
108
|
+
if not owner_dir.is_dir():
|
|
109
|
+
return ()
|
|
110
|
+
return tuple(
|
|
111
|
+
path
|
|
112
|
+
for path in sorted(owner_dir.glob("*.py"), key=_migration_sort_key)
|
|
113
|
+
if path.name != "__init__.py"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _migration_sort_key(path: Path) -> tuple[int, str]:
|
|
118
|
+
match = re.match(r"^(?P<prefix>\d+)_", path.name)
|
|
119
|
+
if match is None:
|
|
120
|
+
return (sys.maxsize, path.name)
|
|
121
|
+
return (int(match.group("prefix")), path.name)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _migration_file_has_aerich_state(path: Path) -> bool:
|
|
125
|
+
try:
|
|
126
|
+
text = path.read_text(encoding="utf-8")
|
|
127
|
+
except Exception:
|
|
128
|
+
return False
|
|
129
|
+
return "MODELS_STATE" in text
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _owner_uses_legacy_sql_migrations(owner_dir: Path) -> bool:
|
|
133
|
+
return bool(_legacy_sql_migration_files(owner_dir))
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _legacy_sql_migration_files(owner_dir: Path) -> tuple[Path, ...]:
|
|
137
|
+
files = _migration_python_files(owner_dir)
|
|
138
|
+
if any(not _migration_file_has_aerich_state(path) for path in files):
|
|
139
|
+
return files
|
|
140
|
+
return tuple(path for path in files if not _migration_file_has_aerich_state(path))
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _coerce_query_row(row: Any) -> Any:
|
|
144
|
+
if isinstance(row, dict):
|
|
145
|
+
return row
|
|
146
|
+
keys = getattr(row, "keys", None)
|
|
147
|
+
if not callable(keys):
|
|
148
|
+
return row
|
|
149
|
+
try:
|
|
150
|
+
return {key: row[key] for key in keys()}
|
|
151
|
+
except Exception:
|
|
152
|
+
return row
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _coerce_execute_query_result(result: Any) -> Any:
|
|
156
|
+
if not isinstance(result, tuple) or len(result) < 2:
|
|
157
|
+
return result
|
|
158
|
+
rows = result[1]
|
|
159
|
+
if not isinstance(rows, list):
|
|
160
|
+
return result
|
|
161
|
+
coerced = [_coerce_query_row(row) for row in rows]
|
|
162
|
+
return (result[0], coerced, *result[2:])
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class _LegacySqlMigrationConnection:
|
|
166
|
+
def __init__(self, connection: Any) -> None:
|
|
167
|
+
self._connection = connection
|
|
168
|
+
|
|
169
|
+
def __getattr__(self, name: str) -> Any:
|
|
170
|
+
return getattr(self._connection, name)
|
|
171
|
+
|
|
172
|
+
async def execute_query(self, *args: Any, **kwargs: Any) -> Any:
|
|
173
|
+
result = await self._connection.execute_query(*args, **kwargs)
|
|
174
|
+
return _coerce_execute_query_result(result)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _connection_dialect(connection: Any) -> str:
|
|
178
|
+
try:
|
|
179
|
+
dialect = str(getattr(getattr(connection, "capabilities", None), "dialect", "") or "").lower()
|
|
180
|
+
if dialect:
|
|
181
|
+
return dialect
|
|
182
|
+
except Exception:
|
|
183
|
+
pass
|
|
184
|
+
try:
|
|
185
|
+
module_name = str(type(connection).__module__ or "").lower()
|
|
186
|
+
except Exception:
|
|
187
|
+
module_name = ""
|
|
188
|
+
if "sqlite" in module_name:
|
|
189
|
+
return "sqlite"
|
|
190
|
+
if "asyncpg" in module_name or "postgres" in module_name or "psycopg" in module_name:
|
|
191
|
+
return "postgres"
|
|
192
|
+
if "mysql" in module_name or "aiomysql" in module_name or "mariadb" in module_name:
|
|
193
|
+
return "mysql"
|
|
194
|
+
return ""
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _split_sql_statements(script: str) -> list[str]:
|
|
198
|
+
statements: list[str] = []
|
|
199
|
+
current: list[str] = []
|
|
200
|
+
in_single_quote = False
|
|
201
|
+
in_double_quote = False
|
|
202
|
+
idx = 0
|
|
203
|
+
while idx < len(script):
|
|
204
|
+
char = script[idx]
|
|
205
|
+
current.append(char)
|
|
206
|
+
if char == "'" and not in_double_quote:
|
|
207
|
+
next_char = script[idx + 1] if idx + 1 < len(script) else ""
|
|
208
|
+
if in_single_quote and next_char == "'":
|
|
209
|
+
current.append(next_char)
|
|
210
|
+
idx += 1
|
|
211
|
+
else:
|
|
212
|
+
in_single_quote = not in_single_quote
|
|
213
|
+
elif char == '"' and not in_single_quote:
|
|
214
|
+
in_double_quote = not in_double_quote
|
|
215
|
+
elif char == ";" and not in_single_quote and not in_double_quote:
|
|
216
|
+
statement = "".join(current).strip()
|
|
217
|
+
if statement:
|
|
218
|
+
statements.append(statement)
|
|
219
|
+
current = []
|
|
220
|
+
idx += 1
|
|
221
|
+
tail = "".join(current).strip()
|
|
222
|
+
if tail:
|
|
223
|
+
statements.append(tail)
|
|
224
|
+
return statements
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _strip_sqlite_unsupported_comment_statements(script: str) -> str:
|
|
228
|
+
statements = [
|
|
229
|
+
statement
|
|
230
|
+
for statement in _split_sql_statements(script)
|
|
231
|
+
if not statement.lstrip().upper().startswith("COMMENT ON ")
|
|
232
|
+
]
|
|
233
|
+
if not statements:
|
|
234
|
+
return ""
|
|
235
|
+
return "\n".join(statements)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _strip_sqlite_postgres_do_blocks(script: str) -> str:
|
|
239
|
+
return re.sub(
|
|
240
|
+
r"\bDO\s+\$\$.*?\$\$\s*;?",
|
|
241
|
+
"",
|
|
242
|
+
script,
|
|
243
|
+
flags=re.IGNORECASE | re.DOTALL,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _strip_sqlite_postgres_json_casts(script: str) -> str:
|
|
248
|
+
result: list[str] = []
|
|
249
|
+
in_single_quote = False
|
|
250
|
+
in_double_quote = False
|
|
251
|
+
idx = 0
|
|
252
|
+
while idx < len(script):
|
|
253
|
+
char = script[idx]
|
|
254
|
+
if char == "'" and not in_double_quote:
|
|
255
|
+
result.append(char)
|
|
256
|
+
next_char = script[idx + 1] if idx + 1 < len(script) else ""
|
|
257
|
+
if in_single_quote and next_char == "'":
|
|
258
|
+
result.append(next_char)
|
|
259
|
+
idx += 2
|
|
260
|
+
continue
|
|
261
|
+
in_single_quote = not in_single_quote
|
|
262
|
+
idx += 1
|
|
263
|
+
continue
|
|
264
|
+
if char == '"' and not in_single_quote:
|
|
265
|
+
in_double_quote = not in_double_quote
|
|
266
|
+
result.append(char)
|
|
267
|
+
idx += 1
|
|
268
|
+
continue
|
|
269
|
+
if not in_single_quote and not in_double_quote:
|
|
270
|
+
lower_tail = script[idx:].lower()
|
|
271
|
+
if lower_tail.startswith("::jsonb"):
|
|
272
|
+
idx += len("::jsonb")
|
|
273
|
+
continue
|
|
274
|
+
if lower_tail.startswith("::json"):
|
|
275
|
+
idx += len("::json")
|
|
276
|
+
continue
|
|
277
|
+
result.append(char)
|
|
278
|
+
idx += 1
|
|
279
|
+
return "".join(result)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _replace_sqlite_postgres_default_functions(script: str) -> str:
|
|
283
|
+
return re.sub(
|
|
284
|
+
r"\bDEFAULT\s+NOW\s*\(\s*\)",
|
|
285
|
+
"DEFAULT CURRENT_TIMESTAMP",
|
|
286
|
+
script,
|
|
287
|
+
flags=re.IGNORECASE,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _prepare_legacy_sql_script(script: str, *, connection: Any) -> str:
|
|
292
|
+
if _connection_dialect(connection) != "sqlite":
|
|
293
|
+
return script
|
|
294
|
+
script = _strip_sqlite_postgres_do_blocks(script)
|
|
295
|
+
script = _strip_sqlite_unsupported_comment_statements(script)
|
|
296
|
+
script = _strip_sqlite_postgres_json_casts(script)
|
|
297
|
+
return _replace_sqlite_postgres_default_functions(script)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _query_result_rows(result: Any) -> list[Any]:
|
|
301
|
+
if not isinstance(result, tuple) or len(result) < 2:
|
|
302
|
+
return []
|
|
303
|
+
rows = result[1]
|
|
304
|
+
if not isinstance(rows, list):
|
|
305
|
+
return []
|
|
306
|
+
return rows
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _row_value(row: Any, key: str) -> Any:
|
|
310
|
+
get = getattr(row, "get", None)
|
|
311
|
+
if callable(get):
|
|
312
|
+
return get(key)
|
|
313
|
+
try:
|
|
314
|
+
return row[key]
|
|
315
|
+
except Exception:
|
|
316
|
+
return None
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
async def _sqlite_column_exists(connection: Any, *, table_name: str, column_name: str) -> bool:
|
|
320
|
+
result = await connection.execute_query(f'PRAGMA table_info("{table_name}")')
|
|
321
|
+
return any(
|
|
322
|
+
str(_row_value(_coerce_query_row(row), "name") or "") == column_name
|
|
323
|
+
for row in _query_result_rows(result)
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
_SQLITE_ADD_COLUMN_IF_NOT_EXISTS_RE = re.compile(
|
|
328
|
+
r'^\s*ALTER\s+TABLE\s+"?(?P<table>[^"\s]+)"?\s+ADD\s+COLUMN\s+IF\s+NOT\s+EXISTS\s+"?(?P<column>[^"\s]+)"?\s+',
|
|
329
|
+
flags=re.IGNORECASE | re.DOTALL,
|
|
330
|
+
)
|
|
331
|
+
_SQLITE_ALTER_COLUMN_RE = re.compile(
|
|
332
|
+
r'^\s*ALTER\s+TABLE\s+"?[^"\s]+"?\s+ALTER\s+COLUMN\s+',
|
|
333
|
+
flags=re.IGNORECASE | re.DOTALL,
|
|
334
|
+
)
|
|
335
|
+
_SQLITE_ALTER_CONSTRAINT_RE = re.compile(
|
|
336
|
+
r'^\s*ALTER\s+TABLE\s+"?[^"\s]+"?\s+(?:DROP|ADD)\s+CONSTRAINT\s+',
|
|
337
|
+
flags=re.IGNORECASE | re.DOTALL,
|
|
338
|
+
)
|
|
339
|
+
_SQLITE_UPDATE_TABLE_RE = re.compile(
|
|
340
|
+
r'^\s*UPDATE\s+"?(?P<table>[^"\s]+)"?\s+',
|
|
341
|
+
flags=re.IGNORECASE | re.DOTALL,
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
async def _sqlite_table_has_rows(connection: Any, *, table_name: str) -> bool:
|
|
346
|
+
result = await connection.execute_query(f'SELECT 1 FROM "{table_name}" LIMIT 1')
|
|
347
|
+
return bool(_query_result_rows(result))
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
async def _execute_sqlite_legacy_statement(connection: Any, statement: str) -> None:
|
|
351
|
+
normalized = statement.strip().rstrip(";").strip()
|
|
352
|
+
if not normalized:
|
|
353
|
+
return
|
|
354
|
+
add_column_match = _SQLITE_ADD_COLUMN_IF_NOT_EXISTS_RE.match(normalized)
|
|
355
|
+
if add_column_match is not None:
|
|
356
|
+
table_name = add_column_match.group("table")
|
|
357
|
+
column_name = add_column_match.group("column")
|
|
358
|
+
if await _sqlite_column_exists(connection, table_name=table_name, column_name=column_name):
|
|
359
|
+
return
|
|
360
|
+
normalized = re.sub(
|
|
361
|
+
r"\bADD\s+COLUMN\s+IF\s+NOT\s+EXISTS\b",
|
|
362
|
+
"ADD COLUMN",
|
|
363
|
+
normalized,
|
|
364
|
+
count=1,
|
|
365
|
+
flags=re.IGNORECASE,
|
|
366
|
+
)
|
|
367
|
+
if _SQLITE_ALTER_COLUMN_RE.match(normalized) or _SQLITE_ALTER_CONSTRAINT_RE.match(normalized):
|
|
368
|
+
return
|
|
369
|
+
update_match = _SQLITE_UPDATE_TABLE_RE.match(normalized)
|
|
370
|
+
if update_match is not None and not await _sqlite_table_has_rows(connection, table_name=update_match.group("table")):
|
|
371
|
+
return
|
|
372
|
+
await connection.execute_script(normalized)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
async def _execute_legacy_sql_script(connection: Any, script: str) -> None:
|
|
376
|
+
if _connection_dialect(connection) != "sqlite":
|
|
377
|
+
await connection.execute_script(script)
|
|
378
|
+
return
|
|
379
|
+
for statement in _split_sql_statements(script):
|
|
380
|
+
await _execute_sqlite_legacy_statement(connection, statement)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def _apply_legacy_sql_migrations(
|
|
384
|
+
*,
|
|
385
|
+
owner: str,
|
|
386
|
+
owner_dir: Path,
|
|
387
|
+
tortoise_config: dict[str, Any],
|
|
388
|
+
) -> dict[str, Any]:
|
|
389
|
+
files = _legacy_sql_migration_files(owner_dir)
|
|
390
|
+
if not files:
|
|
391
|
+
return {"owner": owner, "command": "legacy-upgrade", "files": [], "returncode": 0}
|
|
392
|
+
try:
|
|
393
|
+
from tortoise import Tortoise
|
|
394
|
+
except Exception as exc:
|
|
395
|
+
raise RuntimeError(f"Tortoise is required to apply legacy SQL migrations for app '{owner}': {exc}") from exc
|
|
396
|
+
|
|
397
|
+
async def _upgrade() -> None:
|
|
398
|
+
try:
|
|
399
|
+
await Tortoise.init(config=tortoise_config)
|
|
400
|
+
connection = Tortoise.get_connection("default")
|
|
401
|
+
migration_connection = _LegacySqlMigrationConnection(connection)
|
|
402
|
+
for path in files:
|
|
403
|
+
spec = importlib.util.spec_from_file_location(f"_pz_legacy_migration_{owner}_{path.stem}", path)
|
|
404
|
+
if spec is None or spec.loader is None:
|
|
405
|
+
raise RuntimeError(f"Cannot load legacy migration file: {path}")
|
|
406
|
+
module = importlib.util.module_from_spec(spec)
|
|
407
|
+
spec.loader.exec_module(module)
|
|
408
|
+
upgrade = getattr(module, "upgrade", None)
|
|
409
|
+
if upgrade is None:
|
|
410
|
+
continue
|
|
411
|
+
sql = await upgrade(migration_connection)
|
|
412
|
+
sql_text = str(sql or "").strip()
|
|
413
|
+
sql_text = _prepare_legacy_sql_script(sql_text, connection=connection).strip()
|
|
414
|
+
if sql_text:
|
|
415
|
+
try:
|
|
416
|
+
await _execute_legacy_sql_script(connection, sql_text)
|
|
417
|
+
except Exception as exc:
|
|
418
|
+
raise RuntimeError(
|
|
419
|
+
f"Legacy SQL migration failed for app '{owner}' file '{path.name}': {exc}"
|
|
420
|
+
) from exc
|
|
421
|
+
finally:
|
|
422
|
+
try:
|
|
423
|
+
await Tortoise.close_connections()
|
|
424
|
+
except Exception:
|
|
425
|
+
pass
|
|
426
|
+
|
|
427
|
+
asyncio.run(_upgrade())
|
|
428
|
+
return {
|
|
429
|
+
"owner": owner,
|
|
430
|
+
"command": "legacy-upgrade",
|
|
431
|
+
"files": [path.name for path in files],
|
|
432
|
+
"returncode": 0,
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def _init_shared_source_schemas(
|
|
437
|
+
*,
|
|
438
|
+
plan: Any,
|
|
439
|
+
migration_sources: tuple[Any, ...],
|
|
440
|
+
owners_dir: Path | None = None,
|
|
441
|
+
) -> tuple[str, ...]:
|
|
442
|
+
sources = _shared_init_sources(migration_sources)
|
|
443
|
+
if not sources:
|
|
444
|
+
return ()
|
|
445
|
+
installed_apps = [
|
|
446
|
+
str(getattr(source, "module_path", "")).split(":", 1)[0].strip()
|
|
447
|
+
for source in sources
|
|
448
|
+
if str(getattr(source, "module_path", "")).strip()
|
|
449
|
+
]
|
|
450
|
+
if not installed_apps:
|
|
451
|
+
return ()
|
|
452
|
+
tortoise_config = build_foundation_tortoise_config(
|
|
453
|
+
project_dir=str(plan.project.project_dir),
|
|
454
|
+
installed_apps=installed_apps,
|
|
455
|
+
)
|
|
456
|
+
try:
|
|
457
|
+
from tortoise import Tortoise
|
|
458
|
+
except Exception as exc:
|
|
459
|
+
raise RuntimeError(f"Tortoise is required to initialize shared source database schemas: {exc}") from exc
|
|
460
|
+
|
|
461
|
+
async def _bootstrap() -> None:
|
|
462
|
+
try:
|
|
463
|
+
await Tortoise.init(config=tortoise_config)
|
|
464
|
+
await Tortoise.generate_schemas(safe=True)
|
|
465
|
+
finally:
|
|
466
|
+
try:
|
|
467
|
+
await Tortoise.close_connections()
|
|
468
|
+
except Exception:
|
|
469
|
+
pass
|
|
470
|
+
|
|
471
|
+
asyncio.run(_bootstrap())
|
|
472
|
+
if owners_dir is not None:
|
|
473
|
+
for source in sources:
|
|
474
|
+
owner = str(getattr(source, "owner"))
|
|
475
|
+
owner_dir = owners_dir / owner
|
|
476
|
+
if _owner_uses_legacy_sql_migrations(owner_dir):
|
|
477
|
+
_apply_legacy_sql_migrations(
|
|
478
|
+
owner=owner,
|
|
479
|
+
owner_dir=owner_dir,
|
|
480
|
+
tortoise_config=tortoise_config,
|
|
481
|
+
)
|
|
482
|
+
return tuple(str(getattr(source, "owner")) for source in sources)
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def _write_workspace_config(*, workspace_dir: Path, tortoise_config: dict[str, Any]) -> tuple[Path, Path]:
|
|
486
|
+
module_path = workspace_dir / "_pz_migration_config.py"
|
|
487
|
+
module_path.write_text(
|
|
488
|
+
"\n".join(
|
|
489
|
+
[
|
|
490
|
+
"from __future__ import annotations",
|
|
491
|
+
"",
|
|
492
|
+
f"TORTOISE_ORM = {json.dumps(tortoise_config, ensure_ascii=True, indent=2, sort_keys=True)}",
|
|
493
|
+
"TORTOISE_CONFIG = TORTOISE_ORM",
|
|
494
|
+
"",
|
|
495
|
+
]
|
|
496
|
+
),
|
|
497
|
+
encoding="utf-8",
|
|
498
|
+
)
|
|
499
|
+
config_path = workspace_dir / "aerich.ini"
|
|
500
|
+
config_path.write_text(
|
|
501
|
+
"\n".join(
|
|
502
|
+
[
|
|
503
|
+
"[tool.aerich]",
|
|
504
|
+
'tortoise_orm = "_pz_migration_config.TORTOISE_CONFIG"',
|
|
505
|
+
'location = "./owners"',
|
|
506
|
+
f'src_folder = "{workspace_dir}"',
|
|
507
|
+
"",
|
|
508
|
+
]
|
|
509
|
+
),
|
|
510
|
+
encoding="utf-8",
|
|
511
|
+
)
|
|
512
|
+
return module_path, config_path
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def _aerich_command() -> list[str]:
|
|
516
|
+
if importlib.util.find_spec("aerich.__main__") is not None:
|
|
517
|
+
return [sys.executable, "-m", "aerich"]
|
|
518
|
+
candidate = Path(sys.executable).with_name("aerich")
|
|
519
|
+
if candidate.exists():
|
|
520
|
+
return [str(candidate)]
|
|
521
|
+
found = shutil.which("aerich")
|
|
522
|
+
if found:
|
|
523
|
+
return [found]
|
|
524
|
+
return [sys.executable, "-m", "aerich"]
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def _runtime_import_roots(*, plan: Any) -> tuple[Path, ...]:
|
|
528
|
+
settings = getattr(plan, "settings", None)
|
|
529
|
+
values = tuple(getattr(settings, "import_roots", ()) or ())
|
|
530
|
+
roots: list[Path] = []
|
|
531
|
+
seen: set[Path] = set()
|
|
532
|
+
for item in values:
|
|
533
|
+
try:
|
|
534
|
+
root = Path(item).resolve()
|
|
535
|
+
except Exception:
|
|
536
|
+
continue
|
|
537
|
+
if root in seen:
|
|
538
|
+
continue
|
|
539
|
+
seen.add(root)
|
|
540
|
+
roots.append(root)
|
|
541
|
+
return tuple(roots)
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def _run_aerich(
|
|
545
|
+
*,
|
|
546
|
+
workspace_dir: Path,
|
|
547
|
+
owners_dir: Path,
|
|
548
|
+
config_path: Path,
|
|
549
|
+
owner: str,
|
|
550
|
+
command: str,
|
|
551
|
+
tortoise_config: dict[str, Any],
|
|
552
|
+
plan: Any,
|
|
553
|
+
) -> dict[str, Any]:
|
|
554
|
+
env = dict(os.environ)
|
|
555
|
+
pythonpath = env.get("PYTHONPATH", "")
|
|
556
|
+
import_entries = [str(workspace_dir), str(Path(getattr(getattr(plan, "project", None), "project_dir", workspace_dir)).resolve())]
|
|
557
|
+
import_entries.extend(str(path) for path in _runtime_import_roots(plan=plan))
|
|
558
|
+
if pythonpath:
|
|
559
|
+
import_entries.extend(item for item in pythonpath.split(os.pathsep) if item)
|
|
560
|
+
deduped_entries: list[str] = []
|
|
561
|
+
seen_entries: set[str] = set()
|
|
562
|
+
for entry in import_entries:
|
|
563
|
+
value = str(entry or "").strip()
|
|
564
|
+
if not value or value in seen_entries:
|
|
565
|
+
continue
|
|
566
|
+
seen_entries.add(value)
|
|
567
|
+
deduped_entries.append(value)
|
|
568
|
+
env["PYTHONPATH"] = os.pathsep.join(deduped_entries)
|
|
569
|
+
base_cmd = _aerich_command() + ["-c", str(config_path), "--app", owner]
|
|
570
|
+
owner_dir = owners_dir / owner
|
|
571
|
+
if command == "upgrade" and _owner_uses_legacy_sql_migrations(owner_dir):
|
|
572
|
+
return _apply_legacy_sql_migrations(owner=owner, owner_dir=owner_dir, tortoise_config=tortoise_config)
|
|
573
|
+
|
|
574
|
+
def _invoke(*args: str) -> subprocess.CompletedProcess[str]:
|
|
575
|
+
return subprocess.run(
|
|
576
|
+
base_cmd + list(args),
|
|
577
|
+
cwd=str(workspace_dir),
|
|
578
|
+
env=env,
|
|
579
|
+
capture_output=True,
|
|
580
|
+
text=True,
|
|
581
|
+
check=False,
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
proc = _invoke(command)
|
|
585
|
+
stderr_text = proc.stderr or ""
|
|
586
|
+
stdout_text = proc.stdout or ""
|
|
587
|
+
if proc.returncode != 0 and "Old format of migration file detected" in (stderr_text or stdout_text):
|
|
588
|
+
fix_proc = _invoke("fix-migrations")
|
|
589
|
+
if fix_proc.returncode == 0:
|
|
590
|
+
proc = _invoke(command)
|
|
591
|
+
stderr_text = proc.stderr or ""
|
|
592
|
+
stdout_text = proc.stdout or ""
|
|
593
|
+
if proc.returncode != 0:
|
|
594
|
+
raise RuntimeError(
|
|
595
|
+
f"Aerich {command} failed for app '{owner}' with exit code {proc.returncode}: "
|
|
596
|
+
f"{(stderr_text or stdout_text).strip()}"
|
|
597
|
+
)
|
|
598
|
+
return {
|
|
599
|
+
"owner": owner,
|
|
600
|
+
"command": command,
|
|
601
|
+
"stdout": stdout_text,
|
|
602
|
+
"stderr": stderr_text,
|
|
603
|
+
"returncode": proc.returncode,
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def execute(
|
|
608
|
+
*,
|
|
609
|
+
action: str,
|
|
610
|
+
plan: Any,
|
|
611
|
+
workspace_dir: Path,
|
|
612
|
+
manifest_path: Path,
|
|
613
|
+
orm_config_path: Path,
|
|
614
|
+
lock_path: Path,
|
|
615
|
+
migration_sources: tuple[Any, ...],
|
|
616
|
+
) -> dict[str, Any]:
|
|
617
|
+
del manifest_path, orm_config_path, lock_path
|
|
618
|
+
_require_aerich()
|
|
619
|
+
runnable = _runnable_sources(migration_sources, action=action)
|
|
620
|
+
owners_dir = _ensure_workspace_packages(workspace_dir=workspace_dir, migration_sources=migration_sources)
|
|
621
|
+
tortoise_config = _build_tortoise_config(plan=plan, migration_sources=migration_sources)
|
|
622
|
+
_ensure_aerich_history_table(tortoise_config=tortoise_config)
|
|
623
|
+
initialized_shared_sources = ()
|
|
624
|
+
if action in {"migrate", "upgrade"}:
|
|
625
|
+
initialized_shared_sources = _init_shared_source_schemas(
|
|
626
|
+
plan=plan,
|
|
627
|
+
migration_sources=migration_sources,
|
|
628
|
+
owners_dir=owners_dir,
|
|
629
|
+
)
|
|
630
|
+
_, config_path = _write_workspace_config(
|
|
631
|
+
workspace_dir=workspace_dir,
|
|
632
|
+
tortoise_config=tortoise_config,
|
|
633
|
+
)
|
|
634
|
+
command_by_action = {
|
|
635
|
+
"makemigrations": "migrate",
|
|
636
|
+
"migrate": "upgrade",
|
|
637
|
+
"upgrade": "upgrade",
|
|
638
|
+
}
|
|
639
|
+
command = command_by_action.get(action)
|
|
640
|
+
if command is None:
|
|
641
|
+
raise RuntimeError(f"Unsupported Aerich executor action: {action}")
|
|
642
|
+
command_results = [
|
|
643
|
+
_run_aerich(
|
|
644
|
+
workspace_dir=workspace_dir,
|
|
645
|
+
owners_dir=owners_dir,
|
|
646
|
+
config_path=config_path,
|
|
647
|
+
owner=str(getattr(source, "owner")),
|
|
648
|
+
command=command,
|
|
649
|
+
tortoise_config=tortoise_config,
|
|
650
|
+
plan=plan,
|
|
651
|
+
)
|
|
652
|
+
for source in runnable
|
|
653
|
+
]
|
|
654
|
+
return {
|
|
655
|
+
"action": action,
|
|
656
|
+
"owners": [str(getattr(source, "owner")) for source in runnable],
|
|
657
|
+
"initialized_shared_source_owners": list(initialized_shared_sources),
|
|
658
|
+
"results": command_results,
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
__all__ = [
|
|
663
|
+
"execute",
|
|
664
|
+
]
|