splent-cli 1.38.0__tar.gz → 1.40.0__tar.gz
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.
- {splent_cli-1.38.0/src/splent_cli.egg-info → splent_cli-1.40.0}/PKG-INFO +2 -2
- {splent_cli-1.38.0 → splent_cli-1.40.0}/pyproject.toml +9 -6
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_contract.py +45 -1
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_release.py +98 -6
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_build.py +24 -2
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_config.py +37 -9
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_env.py +39 -5
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_validate.py +179 -41
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/uvl/uvl_utils.py +51 -27
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/services/preflight.py +31 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/services/release_dry_run.py +27 -0
- splent_cli-1.40.0/src/splent_cli/utils/feature_params.py +551 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/redact.py +18 -6
- splent_cli-1.40.0/src/splent_cli/utils/uvl_params.py +254 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0/src/splent_cli.egg-info}/PKG-INFO +2 -2
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli.egg-info/SOURCES.txt +3 -1
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli.egg-info/requires.txt +1 -1
- {splent_cli-1.38.0 → splent_cli-1.40.0}/COPYING +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/COPYING.LESSER +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/README.md +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/setup.cfg +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/__init__.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/__main__.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/cli.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/__init__.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/cache/__init__.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/check/__init__.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/check/check_contracts.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/check/check_deps.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/check/check_docker.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/check/check_env.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/check/check_features.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/check/check_github.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/check/check_infra.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/check/check_product.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/check/check_releases.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/check/check_vendor.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/clear/__init__.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/command/__init__.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/command/command_create.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/coverage.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/database/db_console.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/database/db_dump.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/database/db_migrate.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/database/db_reset.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/database/db_restore.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/database/db_rollback.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/database/db_seed.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/database/db_status.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/database/db_upgrade.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/doctor.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/env/env_list.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/env/env_set.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/env/env_show.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/export/__init__.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/export/export_puml.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_add.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_attach.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_clean.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_compat.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_compile.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_create.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_detach.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_drift.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_env.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_impact.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_info.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_install.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_pin.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_refine.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_remove.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_rename.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_status.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_test.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_unlock.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_upgrade.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/lint.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/locust.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/marketplace/login.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/marketplace/logout.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/marketplace/marketplace_auth_utils.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/marketplace/marketplace_index.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/marketplace/whoami.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/__init__.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_auto_require.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_clean.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_commands.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_configure.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_console.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_containers.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_create.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_deploy.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_derive.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_down.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_drift.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_list.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_logs.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_missing.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_port.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_release.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_resolve.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_restart.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_routes.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_run.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_select.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_shell.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_signals.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_test.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/product/product_up.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/release/__init__.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/release/release_core.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/release/release_resume.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/selenium.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/spl/__init__.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/spl/spl_compare.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/spl/spl_outdated.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/spl/spl_pin.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/spl/spl_publish.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/spl/spl_remove_feature.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/tokens_setup.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/commands/version.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/services/__init__.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/services/compose.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/services/context.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/services/credentials.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/services/deploy_bundle.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/services/image_registry.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/services/index_refresh.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/services/marketplace.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/services/marketplace_api.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/services/marketplace_url.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/services/neutral_app.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/services/pypi_quota.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/services/registry.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/services/release.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/services/release_gate.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/services/spl_store.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/services/web_container.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_.gitignore.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_MANIFEST.in.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_README.md.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_commands.py.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_config.py.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_docker-compose.dev.yml.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_docker-compose.prod.yml.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_docker.env.example.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_forms.py.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_hooks.py.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_init.py.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_init_config.py.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_init_light.py.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_init_service.py.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_migrations_alembic.ini.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_migrations_env.py.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_migrations_script.py.mako.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_models.py.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_pyproject.toml.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_pyproject_minimal.toml.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_repositories.py.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_routes.py.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_scripts.js.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_seeders.py.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_services.py.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_signals.py.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_templates_index.html.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_tests_conftest.py.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_tests_e2e.py.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_tests_functional.py.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_tests_integration.py.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_tests_locustfile.py.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_tests_unit.py.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/feature/feature_webpack.config.js.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/licenses/COPYING +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/licenses/COPYING.LESSER +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_.env.dev.example.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_.env.prod.example.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_.gitignore.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_00_core_requirements_dev.sh.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_00_install_features.sh.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_01_compile_assets.sh.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_02_0_db_wait_connection.sh.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_02_1_db_create_db_test.sh.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_02_2_db_create_splent_migrations.sh.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_03_initialize_migrations.sh.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_04_handle_migrations.sh.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_05_0_start_app_dev.sh.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_05_1_start_app_prod.sh.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_400.html +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_401.html +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_404.html +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_500.html +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_Dockerfile.dev.dockerignore.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_Dockerfile.dev.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_Dockerfile.prod.dockerignore.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_Dockerfile.prod.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_README.md.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_app.css +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_app.js +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_base_template.html +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_ci_upgrade.yml.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_ci_validate.yml.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_config.py.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_docker-compose.dev.yml.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_docker-compose.prod.yml.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_dropzone.css +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_entrypoint.dev.sh.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_entrypoint.prod.sh.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_errors.py.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_favicon.svg +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_init.py.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_logging.py.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_own.css +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_package.json.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_pyproject.toml.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/templates/product/product_uvl.j2 +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/__init__.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/archetype.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/cache_utils.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/ci_services.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/ci_workflow.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/command_loader.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/contract_freshness.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/db_client.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/db_utils.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/decorators.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/feature_installer.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/feature_naming.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/feature_requirements.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/feature_resolution.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/feature_utils.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/forge.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/git_url.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/integrity.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/io_utils.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/lifecycle.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/manifest.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/menu_keys.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/migration_lock.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/path_utils.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/proc.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/refinement_facts.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/running_version.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/template_drift.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/toml_edit.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli/utils/upgrade_plan.py +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
- {splent_cli-1.38.0 → splent_cli-1.40.0}/src/splent_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: splent_cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.40.0
|
|
4
4
|
Summary: SPLENT-CLI is a CLI to be able to work on your development more easily.
|
|
5
5
|
Author-email: DiversoLab <diversolab@us.es>
|
|
6
6
|
License-Expression: LGPL-3.0-or-later
|
|
@@ -16,7 +16,7 @@ Requires-Dist: packaging>=24.0
|
|
|
16
16
|
Requires-Dist: python-dotenv>=1.0
|
|
17
17
|
Requires-Dist: pyyaml>=6.0
|
|
18
18
|
Requires-Dist: requests>=2.31.0
|
|
19
|
-
Requires-Dist: splent_framework<2,>=1.
|
|
19
|
+
Requires-Dist: splent_framework<2,>=1.22.0
|
|
20
20
|
Requires-Dist: tomli_w>=1.0.0
|
|
21
21
|
Provides-Extra: dev
|
|
22
22
|
Requires-Dist: setuptools==80.3.1; extra == "dev"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "splent_cli"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.40.0"
|
|
8
8
|
description = "SPLENT-CLI is a CLI to be able to work on your development more easily."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13"
|
|
@@ -24,11 +24,14 @@ dependencies = [
|
|
|
24
24
|
"pyyaml>=6.0",
|
|
25
25
|
"requests>=2.31.0",
|
|
26
26
|
# The CLI drives the framework (paths, feature order, migrations, the
|
|
27
|
-
# app factory) and only works with the same major version of it. 1.
|
|
28
|
-
#
|
|
29
|
-
# and
|
|
30
|
-
#
|
|
31
|
-
|
|
27
|
+
# app factory) and only works with the same major version of it. 1.22.0
|
|
28
|
+
# brings splent_framework.params, the [tool.splent.config_schema] reader
|
|
29
|
+
# product:validate, product:env and product:build share with the boot;
|
|
30
|
+
# 1.18.0 resolves a feature to the copy feature:test names
|
|
31
|
+
# (SPLENT_FEATURE_SOURCES) and brings the production defaults the deploy
|
|
32
|
+
# templates explain; 1.17.0 the refinement rules product:validate shares
|
|
33
|
+
# with the boot.
|
|
34
|
+
"splent_framework>=1.22.0,<2",
|
|
32
35
|
"tomli_w>=1.0.0",
|
|
33
36
|
]
|
|
34
37
|
|
|
@@ -456,6 +456,7 @@ def feature_contract(feature_ref, write):
|
|
|
456
456
|
# nobody would think to look.
|
|
457
457
|
_check_declared_config(cache_path, inferred)
|
|
458
458
|
_report_config_keys(inferred)
|
|
459
|
+
_report_params(cache_path, name)
|
|
459
460
|
click.echo()
|
|
460
461
|
return
|
|
461
462
|
|
|
@@ -468,6 +469,7 @@ def feature_contract(feature_ref, write):
|
|
|
468
469
|
)
|
|
469
470
|
_check_declared_config(cache_path, inferred)
|
|
470
471
|
_report_config_keys(inferred)
|
|
472
|
+
_report_params(cache_path, name)
|
|
471
473
|
click.echo()
|
|
472
474
|
return
|
|
473
475
|
|
|
@@ -485,6 +487,7 @@ def feature_contract(feature_ref, write):
|
|
|
485
487
|
# And whether the settings it reads are declared where a product looks
|
|
486
488
|
_check_declared_config(cache_path, inferred)
|
|
487
489
|
_report_config_keys(inferred)
|
|
490
|
+
_report_params(cache_path, name)
|
|
488
491
|
|
|
489
492
|
click.echo()
|
|
490
493
|
|
|
@@ -535,7 +538,8 @@ def _check_declared_config(feature_path, inferred):
|
|
|
535
538
|
except (OSError, tomllib.TOMLDecodeError):
|
|
536
539
|
return
|
|
537
540
|
|
|
538
|
-
|
|
541
|
+
splent = data.get("tool", {}).get("splent", {})
|
|
542
|
+
declared = set(splent.get("config", {})) | set(splent.get("config_schema", {}))
|
|
539
543
|
# A feature that ships a service may state a default next to its compose
|
|
540
544
|
# file instead, which is equally visible to product:env --merge.
|
|
541
545
|
env_example = feature_path / "docker" / ".env.example"
|
|
@@ -593,6 +597,46 @@ def _report_config_keys(inferred):
|
|
|
593
597
|
)
|
|
594
598
|
|
|
595
599
|
|
|
600
|
+
def _report_params(feature_path, name):
|
|
601
|
+
"""Say what [tool.splent.config_schema] declares, and what of it changed
|
|
602
|
+
since the latest release in a way that breaks a product: feature:release
|
|
603
|
+
refuses that change without a major bump (a minor one below 1.0.0)."""
|
|
604
|
+
from splent_framework.params.schema import read_pyproject
|
|
605
|
+
|
|
606
|
+
from splent_cli.utils.feature_params import release_compatibility
|
|
607
|
+
|
|
608
|
+
pyproject = feature_path / "pyproject.toml"
|
|
609
|
+
try:
|
|
610
|
+
with open(pyproject, "rb") as handle:
|
|
611
|
+
declared = read_pyproject(name, tomllib.load(handle))
|
|
612
|
+
except (OSError, tomllib.TOMLDecodeError):
|
|
613
|
+
return
|
|
614
|
+
if not declared.declared:
|
|
615
|
+
return
|
|
616
|
+
click.echo()
|
|
617
|
+
count = len(declared.params)
|
|
618
|
+
click.secho(
|
|
619
|
+
f" ℹ {count} parameter{'s' if count != 1 else ''} declared in "
|
|
620
|
+
"[tool.splent.config_schema]",
|
|
621
|
+
fg="cyan",
|
|
622
|
+
)
|
|
623
|
+
for error in declared.errors:
|
|
624
|
+
click.secho(f" ❌ [tool.splent.config_schema] {error}", fg="red")
|
|
625
|
+
compat = release_compatibility(str(feature_path), name)
|
|
626
|
+
if compat.changes:
|
|
627
|
+
click.secho(
|
|
628
|
+
f" ⚠ Since {compat.previous}, changes that break a product that "
|
|
629
|
+
"sets them:",
|
|
630
|
+
fg="yellow",
|
|
631
|
+
)
|
|
632
|
+
for change in compat.changes:
|
|
633
|
+
click.secho(f" {change}", fg="yellow")
|
|
634
|
+
click.secho(
|
|
635
|
+
f" The next release must be a {compat.required_bump()} version.",
|
|
636
|
+
dim=True,
|
|
637
|
+
)
|
|
638
|
+
|
|
639
|
+
|
|
596
640
|
def _check_config_py(feature_path, ns, name, inferred):
|
|
597
641
|
"""Warn if config.py is missing or stale relative to the inferred contract."""
|
|
598
642
|
from splent_cli.commands.feature.feature_inject_config import (
|
|
@@ -488,17 +488,33 @@ def _spelled_string(node, constants: dict[str, str]) -> str | None:
|
|
|
488
488
|
return None
|
|
489
489
|
|
|
490
490
|
|
|
491
|
+
def _is_param_call(node) -> bool:
|
|
492
|
+
"""Whether ``node`` is ``param("feature", "KEY")``, the reader of the
|
|
493
|
+
parameters a feature declares (splent_framework.params)."""
|
|
494
|
+
if not isinstance(node, ast.Call) or len(node.args) < 2:
|
|
495
|
+
return False
|
|
496
|
+
func = node.func
|
|
497
|
+
name = func.id if isinstance(func, ast.Name) else getattr(func, "attr", None)
|
|
498
|
+
return name == "param"
|
|
499
|
+
|
|
500
|
+
|
|
491
501
|
def _config_keys(tree) -> tuple[set[str], set[str]]:
|
|
492
502
|
"""``(read, written)``: the app.config keys a module reads and sets.
|
|
493
503
|
|
|
494
|
-
Read: ``.config.get("KEY")
|
|
495
|
-
|
|
496
|
-
|
|
504
|
+
Read: ``.config.get("KEY")``, ``.config["KEY"]`` and
|
|
505
|
+
``param("feature", "KEY")``. Set: ``.config["KEY"] = ...``,
|
|
506
|
+
``.config.setdefault("KEY", ...)`` and the literal keys of
|
|
507
|
+
``.config.update(...)`` or ``.config.from_mapping(...)``.
|
|
497
508
|
"""
|
|
498
509
|
constants = _module_string_constants(tree)
|
|
499
510
|
read: set[str] = set()
|
|
500
511
|
written: set[str] = set()
|
|
501
512
|
for node in ast.walk(tree):
|
|
513
|
+
if _is_param_call(node):
|
|
514
|
+
key = _spelled_string(node.args[1], constants)
|
|
515
|
+
if key:
|
|
516
|
+
read.add(key)
|
|
517
|
+
continue
|
|
502
518
|
if isinstance(node, ast.Subscript) and _is_app_config(node.value):
|
|
503
519
|
key = _spelled_string(node.slice, constants)
|
|
504
520
|
if key and isinstance(node.ctx, ast.Store):
|
|
@@ -737,7 +753,7 @@ def _config_keys_of(py_files) -> tuple[set[str], set[str]]:
|
|
|
737
753
|
try:
|
|
738
754
|
text = py_file.read_text()
|
|
739
755
|
# Most modules never touch the config, and parsing is the cost.
|
|
740
|
-
if ".config" not in text:
|
|
756
|
+
if ".config" not in text and "param(" not in text:
|
|
741
757
|
continue
|
|
742
758
|
tree = ast.parse(text)
|
|
743
759
|
except (SyntaxError, OSError, UnicodeDecodeError):
|
|
@@ -768,12 +784,45 @@ def config_reads(
|
|
|
768
784
|
variable holds, can only come from a product's config.py.
|
|
769
785
|
"""
|
|
770
786
|
read, written = _config_keys_of(_feature_sources(src_dir))
|
|
787
|
+
read |= _template_param_reads(src_dir)
|
|
771
788
|
own_short = src_dir.name.removeprefix("splent_feature_")
|
|
772
789
|
taken = {key for key, shorts in (others or {}).items() if shorts - {own_short}}
|
|
773
790
|
left = read - written - taken - _framework_config_keys()
|
|
774
791
|
return {key for key in left if not _is_extension_key(key)}
|
|
775
792
|
|
|
776
793
|
|
|
794
|
+
# {{ param('media', 'MEDIA_PUBLIC_GALLERY') }} in a template.
|
|
795
|
+
_TEMPLATE_PARAM = re.compile(
|
|
796
|
+
r"""\bparam\(\s*['"][\w.]+['"]\s*,\s*['"]([A-Z][A-Z0-9_]+)['"]"""
|
|
797
|
+
)
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
def _template_param_reads(src_dir: Path) -> set[str]:
|
|
801
|
+
"""The parameters the feature's templates read with param()."""
|
|
802
|
+
keys: set[str] = set()
|
|
803
|
+
for path in sorted(src_dir.rglob("*.html")):
|
|
804
|
+
if "tests" in path.relative_to(src_dir).parts[:-1]:
|
|
805
|
+
continue
|
|
806
|
+
try:
|
|
807
|
+
text = path.read_text(errors="replace")
|
|
808
|
+
except OSError:
|
|
809
|
+
continue
|
|
810
|
+
if "param(" in text:
|
|
811
|
+
keys.update(_TEMPLATE_PARAM.findall(text))
|
|
812
|
+
return keys
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
def declared_param_keys(feature_root: Path) -> set[str]:
|
|
816
|
+
"""The keys the feature declares in [tool.splent.config_schema]. The
|
|
817
|
+
framework sets them on its behalf, so it reads them as its own."""
|
|
818
|
+
try:
|
|
819
|
+
data = tomllib.loads((Path(feature_root) / "pyproject.toml").read_text())
|
|
820
|
+
except (OSError, UnicodeDecodeError, tomllib.TOMLDecodeError):
|
|
821
|
+
return set()
|
|
822
|
+
schema = data.get("tool", {}).get("splent", {}).get("config_schema", {})
|
|
823
|
+
return set(schema) if isinstance(schema, dict) else set()
|
|
824
|
+
|
|
825
|
+
|
|
777
826
|
def default_config_map() -> dict[str, frozenset[str]]:
|
|
778
827
|
""":func:`build_config_map` over the workspace in reach, built once per
|
|
779
828
|
process, or empty without one."""
|
|
@@ -788,7 +837,10 @@ def build_config_map(workspace: str) -> dict[str, frozenset[str]]:
|
|
|
788
837
|
of every feature in reach took half a second, once per contract.
|
|
789
838
|
"""
|
|
790
839
|
owners: dict[str, set[str]] = {}
|
|
791
|
-
for short, package,
|
|
840
|
+
for short, package, feature_dir, _ in _feature_dirs_in_reach(workspace):
|
|
841
|
+
# A parameter the feature declares is set by the framework for it.
|
|
842
|
+
for key in declared_param_keys(feature_dir):
|
|
843
|
+
owners.setdefault(key, set()).add(short)
|
|
792
844
|
if package is None:
|
|
793
845
|
continue
|
|
794
846
|
files = (package / "config.py", package / "__init__.py")
|
|
@@ -1383,7 +1435,11 @@ def infer_contract(
|
|
|
1383
1435
|
# is not one a product must put in its .env.
|
|
1384
1436
|
if config_map is None:
|
|
1385
1437
|
config_map = default_config_map()
|
|
1386
|
-
config_keys = sorted(
|
|
1438
|
+
config_keys = sorted(
|
|
1439
|
+
config_reads(src_dir, config_map)
|
|
1440
|
+
- set(env_vars)
|
|
1441
|
+
- declared_param_keys(feature_root)
|
|
1442
|
+
)
|
|
1387
1443
|
signals_provided, signals_required = _extract_signals(src_dir / "signals.py")
|
|
1388
1444
|
translations = _extract_translations(src_dir / "translations")
|
|
1389
1445
|
|
|
@@ -2111,6 +2167,10 @@ def feature_release(
|
|
|
2111
2167
|
normalized = version.lstrip("v")
|
|
2112
2168
|
tag = f"v{normalized}"
|
|
2113
2169
|
|
|
2170
|
+
# A parameter change that breaks a product needs the version to say so,
|
|
2171
|
+
# before anything is bumped, committed or tagged.
|
|
2172
|
+
_refuse_breaking_params(feature_path, feature_name, tag)
|
|
2173
|
+
|
|
2114
2174
|
# Why the forge is off decides the advice: with --no-forge, release:resume
|
|
2115
2175
|
# pushes the tag later; a package that does not publish to the forge
|
|
2116
2176
|
# never gets its tag there, release:resume included.
|
|
@@ -2225,6 +2285,38 @@ def feature_release(
|
|
|
2225
2285
|
ctx.invoke(feature_attach, feature_identifier=feature_ref, version=tag)
|
|
2226
2286
|
|
|
2227
2287
|
|
|
2288
|
+
def _refuse_breaking_params(feature_path, feature_name, tag) -> None:
|
|
2289
|
+
"""Stop the release when the parameters the feature declares changed in
|
|
2290
|
+
a way that breaks a product (a key removed, a type changed, choices or a
|
|
2291
|
+
range narrowed, a key made required) and ``tag`` does not bump the
|
|
2292
|
+
version that says so: the major, or the minor below 1.0.0.
|
|
2293
|
+
|
|
2294
|
+
Compared with the pyproject.toml of the latest release tag in the
|
|
2295
|
+
feature's repository. A feature never released, or without a schema
|
|
2296
|
+
then and now, has nothing to compare.
|
|
2297
|
+
"""
|
|
2298
|
+
from splent_cli.utils.feature_params import release_compatibility
|
|
2299
|
+
|
|
2300
|
+
compat = release_compatibility(feature_path, feature_name)
|
|
2301
|
+
if compat.allows(tag):
|
|
2302
|
+
return
|
|
2303
|
+
click.secho(
|
|
2304
|
+
f" error: since {compat.previous} the parameters of {feature_name} "
|
|
2305
|
+
"changed in a way that breaks a product that sets them:",
|
|
2306
|
+
fg="red",
|
|
2307
|
+
)
|
|
2308
|
+
for change in compat.changes:
|
|
2309
|
+
click.secho(f" {change}", fg="red")
|
|
2310
|
+
click.echo(
|
|
2311
|
+
f" {tag} keeps the {compat.required_bump()} version of "
|
|
2312
|
+
f"{compat.previous}. Release it with --bump {compat.required_bump()}"
|
|
2313
|
+
+ (" (or major)" if compat.required_bump() == "minor" else "")
|
|
2314
|
+
+ ", or keep the old declaration compatible."
|
|
2315
|
+
)
|
|
2316
|
+
click.echo(" Nothing was changed. No version bump, no commit, no tag, no release.")
|
|
2317
|
+
raise SystemExit(1)
|
|
2318
|
+
|
|
2319
|
+
|
|
2228
2320
|
def _refuse_invalid_requirements(feature_path, namespace, feature_name) -> None:
|
|
2229
2321
|
"""Stop the release, before it changes anything, when the contract it
|
|
2230
2322
|
would write has a requirement that does not parse (``auth>=soon``).
|
|
@@ -1132,7 +1132,15 @@ def _contracts_snapshot(workspace: str, declared_features: Sequence[str]) -> dic
|
|
|
1132
1132
|
A feature that declares neither is still listed, so the framework knows
|
|
1133
1133
|
it was looked at. One whose pyproject cannot be found is left out with a
|
|
1134
1134
|
warning, and the framework then reports it as missing.
|
|
1135
|
+
|
|
1136
|
+
A feature's [tool.splent.config_schema] goes in too, as
|
|
1137
|
+
``config_schema``: the image resolves and checks its parameters from it.
|
|
1138
|
+
Each default is filled in from [tool.splent.config], except a secret's,
|
|
1139
|
+
which never goes to a file baked into the image. A schema that cannot be
|
|
1140
|
+
used is left for the pre-flight to report; only its usable keys go in.
|
|
1135
1141
|
"""
|
|
1142
|
+
from splent_framework.params.schema import read_pyproject
|
|
1143
|
+
|
|
1136
1144
|
features: dict[str, dict] = {}
|
|
1137
1145
|
missing: list[str] = []
|
|
1138
1146
|
for feat in declared_features:
|
|
@@ -1144,7 +1152,8 @@ def _contracts_snapshot(workspace: str, declared_features: Sequence[str]) -> dic
|
|
|
1144
1152
|
if not os.path.isfile(pyproject):
|
|
1145
1153
|
missing.append(name)
|
|
1146
1154
|
continue
|
|
1147
|
-
|
|
1155
|
+
data = load_toml(pyproject)
|
|
1156
|
+
splent = data.get("tool", {}).get("splent", {})
|
|
1148
1157
|
item: dict = {"entry": clean}
|
|
1149
1158
|
if isinstance(splent, dict):
|
|
1150
1159
|
if "refinement" in splent:
|
|
@@ -1152,6 +1161,11 @@ def _contracts_snapshot(workspace: str, declared_features: Sequence[str]) -> dic
|
|
|
1152
1161
|
contract = splent.get("contract")
|
|
1153
1162
|
if isinstance(contract, dict) and "extensible" in contract:
|
|
1154
1163
|
item["extensible"] = contract["extensible"]
|
|
1164
|
+
declared = read_pyproject(name, data)
|
|
1165
|
+
if declared.declared:
|
|
1166
|
+
item["config_schema"] = {
|
|
1167
|
+
key: param.as_dict() for key, param in declared.params.items()
|
|
1168
|
+
}
|
|
1155
1169
|
features[name] = item
|
|
1156
1170
|
if missing:
|
|
1157
1171
|
click.secho(
|
|
@@ -1501,7 +1515,15 @@ def write_deploy_artifacts(
|
|
|
1501
1515
|
# defaults and loses to the product's own env file, exactly as in
|
|
1502
1516
|
# product:env --merge. The two paths have to agree: a deploy template
|
|
1503
1517
|
# that disagreed with dev would only be found in production.
|
|
1504
|
-
|
|
1518
|
+
from splent_cli.utils.feature_params import product_params
|
|
1519
|
+
|
|
1520
|
+
env_result = merge_env_dicts(
|
|
1521
|
+
feature_defaults,
|
|
1522
|
+
_declared_config(
|
|
1523
|
+
pyproject_path,
|
|
1524
|
+
product_params(workspace, load_toml(pyproject_path), "prod"),
|
|
1525
|
+
),
|
|
1526
|
+
)
|
|
1505
1527
|
env_result = merge_env_dicts(env_result, product_env)
|
|
1506
1528
|
|
|
1507
1529
|
# Resolve the product placeholders. A feature that talks to the product's
|
|
@@ -50,14 +50,16 @@ def _short_value(value, max_len: int = 50) -> str:
|
|
|
50
50
|
return _truncate(repr(value), max_len)
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
def _redacted(key: str, value) -> tuple[str, bool]:
|
|
53
|
+
def _redacted(key: str, value, secrets=()) -> tuple[str, bool]:
|
|
54
54
|
"""repr(*value*) as it may be printed, and whether anything was hidden.
|
|
55
55
|
|
|
56
|
-
A secret-looking key's string is masked, and
|
|
57
|
-
|
|
56
|
+
A secret-looking key's string is masked, and so is one a feature's
|
|
57
|
+
[tool.splent.config_schema] marks as a secret (``secrets``), and the
|
|
58
|
+
password of any URI is dropped, also inside a dict or a list such as
|
|
59
|
+
SQLALCHEMY_BINDS.
|
|
58
60
|
"""
|
|
59
61
|
masked = False
|
|
60
|
-
if is_secret_key(key) and isinstance(value, (str, bytes)) and value:
|
|
62
|
+
if is_secret_key(key, secrets) and isinstance(value, (str, bytes)) and value:
|
|
61
63
|
value = mask_value(value) if isinstance(value, str) else MASK.encode()
|
|
62
64
|
masked = True
|
|
63
65
|
text = repr(value)
|
|
@@ -68,10 +70,13 @@ def _redacted(key: str, value) -> tuple[str, bool]:
|
|
|
68
70
|
def _source_label(info: dict) -> str:
|
|
69
71
|
"""Human-readable label for who set this key."""
|
|
70
72
|
source = info.get("source", "unknown")
|
|
71
|
-
# Prettify feature import names: "splent_io.splent_feature_redis" -> "feature (splent_feature_redis)"
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
# Prettify feature import names: "splent_io.splent_feature_redis" -> "feature (splent_feature_redis)",
|
|
74
|
+
# and a feature's default parameter: "... (default)" -> "feature (...) default".
|
|
75
|
+
# Only an import name: "product pyproject ([tool.splent.config])" has dots too.
|
|
76
|
+
name, default, _ = source.partition(" (default)")
|
|
77
|
+
if "." in name and " " not in name:
|
|
78
|
+
label = f"feature ({name.rsplit('.', 1)[1]})"
|
|
79
|
+
return f"{label} default" if default else label
|
|
75
80
|
return source
|
|
76
81
|
|
|
77
82
|
|
|
@@ -120,11 +125,13 @@ def product_config(show_all, filter_feature, filter_key, show_secrets):
|
|
|
120
125
|
"""
|
|
121
126
|
masked_any = False
|
|
122
127
|
|
|
128
|
+
secrets: set = set()
|
|
129
|
+
|
|
123
130
|
def shown(key, value, max_len=50):
|
|
124
131
|
nonlocal masked_any
|
|
125
132
|
if show_secrets:
|
|
126
133
|
return _short_value(value, max_len)
|
|
127
|
-
text, changed = _redacted(key, value)
|
|
134
|
+
text, changed = _redacted(key, value, secrets)
|
|
128
135
|
masked_any = masked_any or changed
|
|
129
136
|
return _truncate(text, max_len)
|
|
130
137
|
|
|
@@ -140,6 +147,9 @@ def product_config(show_all, filter_feature, filter_key, show_secrets):
|
|
|
140
147
|
raise click.ClickException(f"could not boot app: {exc}")
|
|
141
148
|
|
|
142
149
|
trace: dict = app.extensions.get("splent_config_trace", {})
|
|
150
|
+
# The keys the features' schemas mark as secrets, masked like the ones
|
|
151
|
+
# whose name says so.
|
|
152
|
+
secrets |= {key for key, info in trace.items() if info.get("secret")}
|
|
143
153
|
|
|
144
154
|
# Separate overwritten keys
|
|
145
155
|
overwrites = {k: v for k, v in trace.items() if v.get("action") == "overwritten"}
|
|
@@ -214,6 +224,24 @@ def product_config(show_all, filter_feature, filter_key, show_secrets):
|
|
|
214
224
|
f" {key}: {prev_val} ({prev_src}) \u2192 {new_val} ({new_src})"
|
|
215
225
|
)
|
|
216
226
|
|
|
227
|
+
# Drift: the environment replaced what the product decided (for a
|
|
228
|
+
# declared parameter, one bound at derivation). Accepted, and said.
|
|
229
|
+
drifted = {k: v for k, v in trace.items() if v.get("drift")}
|
|
230
|
+
if drifted:
|
|
231
|
+
click.echo()
|
|
232
|
+
click.secho(
|
|
233
|
+
" \u26a0 Drift, the environment overrides what the product decided:",
|
|
234
|
+
fg="yellow",
|
|
235
|
+
bold=True,
|
|
236
|
+
)
|
|
237
|
+
for key, info in sorted(drifted.items()):
|
|
238
|
+
before = shown(key, info.get("overruled_value"), 30)
|
|
239
|
+
after = shown(key, info.get("value"), 30)
|
|
240
|
+
click.echo(
|
|
241
|
+
f" {key}: {before} ({info.get('overruled_source', '?')}) "
|
|
242
|
+
f"\u2192 {after} (operator environment)"
|
|
243
|
+
)
|
|
244
|
+
|
|
217
245
|
if masked_any:
|
|
218
246
|
click.echo()
|
|
219
247
|
click.secho(
|
|
@@ -31,17 +31,35 @@ def _scalar(value) -> str | None:
|
|
|
31
31
|
return None
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
def _env_value(pyproject_path: str, key: str, value) -> str:
|
|
34
|
+
def _env_value(pyproject_path: str, key: str, value, param=None) -> str:
|
|
35
35
|
"""One declared value as the string the environment will carry.
|
|
36
36
|
|
|
37
37
|
Raises ClickException for what an environment variable cannot hold
|
|
38
38
|
without losing it: a table, a list of tables or of lists, and a list
|
|
39
39
|
item with a comma in it, which the reader would split in two.
|
|
40
|
+
|
|
41
|
+
A key some feature declares in [tool.splent.config_schema] (``param``)
|
|
42
|
+
is written the way the framework reads it back at boot: a list item
|
|
43
|
+
with a comma, or a map, as JSON (splent_framework.params.schema
|
|
44
|
+
.env_text). A value the schema refuses raises ClickException too, so
|
|
45
|
+
the environment never carries what the app would refuse to start with.
|
|
40
46
|
"""
|
|
47
|
+
where = f"{key} in [tool.splent.config] of {pyproject_path}"
|
|
48
|
+
if param is not None:
|
|
49
|
+
from splent_framework.params.schema import PLACEHOLDER, ParamError, env_text
|
|
50
|
+
|
|
51
|
+
if value == PLACEHOLDER:
|
|
52
|
+
return value
|
|
53
|
+
try:
|
|
54
|
+
return env_text(param, value)
|
|
55
|
+
except ParamError as e:
|
|
56
|
+
raise click.ClickException(
|
|
57
|
+
f"{where}: {e} ({param.short} declares it in "
|
|
58
|
+
"[tool.splent.config_schema])"
|
|
59
|
+
) from None
|
|
41
60
|
text = _scalar(value)
|
|
42
61
|
if text is not None:
|
|
43
62
|
return text
|
|
44
|
-
where = f"{key} in [tool.splent.config] of {pyproject_path}"
|
|
45
63
|
if isinstance(value, list):
|
|
46
64
|
items = [_scalar(item) for item in value]
|
|
47
65
|
if any(item is None for item in items):
|
|
@@ -68,7 +86,7 @@ def _env_value(pyproject_path: str, key: str, value) -> str:
|
|
|
68
86
|
)
|
|
69
87
|
|
|
70
88
|
|
|
71
|
-
def _declared_config(pyproject_path: str) -> dict:
|
|
89
|
+
def _declared_config(pyproject_path: str, params: dict | None = None) -> dict:
|
|
72
90
|
"""The ``[tool.splent.config]`` block of a pyproject, as strings.
|
|
73
91
|
|
|
74
92
|
A feature declares the variables it reads and what they default to; a
|
|
@@ -83,6 +101,11 @@ def _declared_config(pyproject_path: str) -> dict:
|
|
|
83
101
|
can carry (a table, a list of tables) raises ClickException naming the
|
|
84
102
|
key and the file: it used to be dropped without a word, and the feature
|
|
85
103
|
then ran on its default while the pyproject said otherwise.
|
|
104
|
+
|
|
105
|
+
``params`` are the declared parameters the values are written for, by
|
|
106
|
+
key (feature_params.product_params for a product's pyproject). Left
|
|
107
|
+
out, they are the file's own [tool.splent.config_schema], which is the
|
|
108
|
+
case of a feature's pyproject. A list or a map is then carried, typed.
|
|
86
109
|
"""
|
|
87
110
|
try:
|
|
88
111
|
with open(pyproject_path, "rb") as handle:
|
|
@@ -94,8 +117,15 @@ def _declared_config(pyproject_path: str) -> dict:
|
|
|
94
117
|
if not isinstance(declared, dict):
|
|
95
118
|
return {}
|
|
96
119
|
|
|
120
|
+
if params is None:
|
|
121
|
+
from splent_framework.params.schema import read_pyproject
|
|
122
|
+
|
|
123
|
+
feature = os.path.basename(os.path.dirname(pyproject_path)).split("@")[0]
|
|
124
|
+
params = read_pyproject(feature, data).params
|
|
125
|
+
|
|
97
126
|
return {
|
|
98
|
-
key: _env_value(pyproject_path, key, value
|
|
127
|
+
key: _env_value(pyproject_path, key, value, params.get(key))
|
|
128
|
+
for key, value in declared.items()
|
|
99
129
|
}
|
|
100
130
|
|
|
101
131
|
|
|
@@ -398,7 +428,11 @@ def product_env(generate, merge, env_name, process_all):
|
|
|
398
428
|
k for k in defaults.declared if k not in stated_by_the_product
|
|
399
429
|
]
|
|
400
430
|
|
|
401
|
-
|
|
431
|
+
from splent_cli.utils.feature_params import product_params
|
|
432
|
+
|
|
433
|
+
product_declared = _declared_config(
|
|
434
|
+
py_path, product_params(workspace, data, env_name)
|
|
435
|
+
)
|
|
402
436
|
for key, value in product_declared.items():
|
|
403
437
|
# Beats the feature's default, which is the point of writing it
|
|
404
438
|
# here, and loses to the product's own env file, which is the
|