splent-cli 1.11.0__tar.gz → 1.12.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.11.0/src/splent_cli.egg-info → splent_cli-1.12.0}/PKG-INFO +1 -1
- {splent_cli-1.11.0 → splent_cli-1.12.0}/pyproject.toml +1 -1
- splent_cli-1.12.0/src/splent_cli/commands/database/db_reset.py +253 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_unlock.py +10 -2
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_add_feature.py +33 -36
- splent_cli-1.12.0/src/splent_cli/commands/spl/spl_compare.py +88 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_create.py +4 -1
- {splent_cli-1.11.0 → splent_cli-1.12.0/src/splent_cli.egg-info}/PKG-INFO +1 -1
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli.egg-info/SOURCES.txt +1 -0
- splent_cli-1.11.0/src/splent_cli/commands/database/db_reset.py +0 -159
- {splent_cli-1.11.0 → splent_cli-1.12.0}/LICENSE +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/README.md +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/setup.cfg +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/__init__.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/__main__.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/cli.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/__init__.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/__init__.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/__init__.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_deps.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_docker.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_env.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_features.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_github.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_infra.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_product.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/clear/__init__.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/command/__init__.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/command/command_create.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/coverage.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/database/db_console.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/database/db_dump.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/database/db_migrate.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/database/db_restore.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/database/db_rollback.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/database/db_seed.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/database/db_status.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/database/db_upgrade.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/doctor.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/env/env_list.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/env/env_set.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/env/env_show.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/export/__init__.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/export/export_puml.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_add.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_attach.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_clean.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_compat.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_compile.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_contract.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_create.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_detach.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_drift.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_env.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_impact.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_install.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_pin.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_refine.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_release.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_remove.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_rename.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_status.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_test.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_upgrade.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/lint.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/locust.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/__init__.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_auto_require.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_build.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_clean.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_commands.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_config.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_configure.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_console.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_containers.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_create.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_deploy.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_derive.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_down.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_drift.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_env.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_list.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_logs.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_missing.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_port.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_release.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_resolve.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_restart.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_routes.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_run.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_select.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_shell.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_signals.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_test.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_up.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_validate.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/release/__init__.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/release/release_core.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/selenium.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/__init__.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/tokens_setup.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/version.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/services/__init__.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/services/compose.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/services/context.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/services/preflight.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/services/release.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/__init__.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/cache_utils.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/command_loader.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/contract_freshness.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/db_utils.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/decorators.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/feature_installer.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/feature_utils.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/git_url.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/integrity.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/io_utils.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/lifecycle.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/manifest.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/path_utils.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/proc.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/template_drift.py +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli.egg-info/requires.txt +0 -0
- {splent_cli-1.11.0 → splent_cli-1.12.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.12.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
|
Project-URL: Homepage, https://github.com/diverso-lab/splent_cli
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import glob
|
|
2
|
+
import importlib
|
|
3
|
+
import os
|
|
4
|
+
import tomllib
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
from flask import current_app
|
|
8
|
+
from flask_migrate import upgrade as alembic_upgrade
|
|
9
|
+
from sqlalchemy import text, MetaData
|
|
10
|
+
|
|
11
|
+
from splent_cli.utils.decorators import requires_db
|
|
12
|
+
from splent_cli.services import context
|
|
13
|
+
from splent_cli.utils.lifecycle import advance_state, resolve_feature_key_from_entry
|
|
14
|
+
from splent_framework.db import db
|
|
15
|
+
from splent_framework.managers.migration_manager import (
|
|
16
|
+
MigrationManager,
|
|
17
|
+
SPLENT_MIGRATIONS_TABLE,
|
|
18
|
+
alembic_version_table,
|
|
19
|
+
)
|
|
20
|
+
from splent_framework.utils.feature_utils import get_features_from_pyproject
|
|
21
|
+
from splent_framework.utils.path_utils import PathUtils
|
|
22
|
+
from splent_cli.commands.clear.clear_uploads import clear_uploads
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# =====================================================================
|
|
26
|
+
# Helpers for the per-feature reset
|
|
27
|
+
# =====================================================================
|
|
28
|
+
def _normalize(feature_name: str) -> str:
|
|
29
|
+
"""Accept 'projects' or 'splent_feature_projects' -> full package name."""
|
|
30
|
+
short = feature_name.split("/")[-1].replace("splent_feature_", "")
|
|
31
|
+
return f"splent_feature_{short}"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _tables_owned_by(feature_full: str) -> list[str]:
|
|
35
|
+
"""Tables whose model classes live in the given feature's package."""
|
|
36
|
+
import_name = f"splent_io.{feature_full}"
|
|
37
|
+
try:
|
|
38
|
+
importlib.import_module(f"{import_name}.models")
|
|
39
|
+
except Exception:
|
|
40
|
+
pass
|
|
41
|
+
tables = []
|
|
42
|
+
for mapper in db.Model.registry.mappers:
|
|
43
|
+
cls = mapper.class_
|
|
44
|
+
if getattr(cls, "__module__", "").startswith(import_name):
|
|
45
|
+
tables.append(cls.__table__.name)
|
|
46
|
+
return sorted(set(tables))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _refiners_of(feature_full: str, product_path: str) -> list[str]:
|
|
50
|
+
"""Installed features whose [tool.splent.refinement].refines == feature."""
|
|
51
|
+
refiners = []
|
|
52
|
+
pattern = os.path.join(product_path, "features", "*", "*", "pyproject.toml")
|
|
53
|
+
for pp in glob.glob(pattern):
|
|
54
|
+
try:
|
|
55
|
+
with open(pp, "rb") as f:
|
|
56
|
+
data = tomllib.load(f)
|
|
57
|
+
except Exception:
|
|
58
|
+
continue
|
|
59
|
+
ref = data.get("tool", {}).get("splent", {}).get("refinement", {})
|
|
60
|
+
if ref.get("refines") == feature_full:
|
|
61
|
+
name = data.get("project", {}).get("name", "")
|
|
62
|
+
if name:
|
|
63
|
+
refiners.append(name)
|
|
64
|
+
return refiners
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _reapply(app, feats: list[str], entry_lookup: dict, product_path, product_name):
|
|
68
|
+
dirs = MigrationManager.get_all_feature_migration_dirs()
|
|
69
|
+
for feat in feats:
|
|
70
|
+
mdir = dirs.get(feat)
|
|
71
|
+
if not mdir:
|
|
72
|
+
continue
|
|
73
|
+
try:
|
|
74
|
+
alembic_upgrade(directory=mdir)
|
|
75
|
+
revision = MigrationManager.get_current_feature_revision(feat, db.engine)
|
|
76
|
+
MigrationManager.update_feature_status(app, feat, revision)
|
|
77
|
+
click.echo(click.style(f" ✅ {feat} → {revision or 'head'}", fg="green"))
|
|
78
|
+
info = entry_lookup.get(feat)
|
|
79
|
+
if info:
|
|
80
|
+
key, ns, name, version = info
|
|
81
|
+
advance_state(
|
|
82
|
+
product_path,
|
|
83
|
+
product_name,
|
|
84
|
+
key,
|
|
85
|
+
to="migrated",
|
|
86
|
+
namespace=ns,
|
|
87
|
+
name=name,
|
|
88
|
+
version=version,
|
|
89
|
+
)
|
|
90
|
+
except ImportError as e:
|
|
91
|
+
if "models" in str(e):
|
|
92
|
+
continue
|
|
93
|
+
click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
|
|
94
|
+
except Exception as e:
|
|
95
|
+
click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _entry_lookup() -> dict:
|
|
99
|
+
lookup = {}
|
|
100
|
+
for entry in get_features_from_pyproject() or []:
|
|
101
|
+
key, ns, name, version = resolve_feature_key_from_entry(entry)
|
|
102
|
+
lookup[name] = (key, ns, name, version)
|
|
103
|
+
return lookup
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# =====================================================================
|
|
107
|
+
# Command
|
|
108
|
+
# =====================================================================
|
|
109
|
+
@requires_db
|
|
110
|
+
@click.command(
|
|
111
|
+
"db:reset",
|
|
112
|
+
short_help="Drop all tables (or just one feature's) and re-apply migrations.",
|
|
113
|
+
)
|
|
114
|
+
@click.argument("feature_name", required=False)
|
|
115
|
+
@click.option("-y", "--yes", is_flag=True, help="Confirm without prompting.")
|
|
116
|
+
@context.requires_product
|
|
117
|
+
def db_reset(feature_name, yes):
|
|
118
|
+
"""Reset the database.
|
|
119
|
+
|
|
120
|
+
With no argument: drops ALL tables and re-applies every feature migration.
|
|
121
|
+
|
|
122
|
+
With FEATURE_NAME (e.g. ``db:reset projects``): drops only the tables that
|
|
123
|
+
feature owns, clears their migration tracking, and re-applies that
|
|
124
|
+
feature's migrations plus any feature that refines it — leaving every other
|
|
125
|
+
feature's data untouched. Useful after changing a model/refinement.
|
|
126
|
+
"""
|
|
127
|
+
app = current_app
|
|
128
|
+
product_path = PathUtils.get_app_base_dir()
|
|
129
|
+
product_name = os.getenv("SPLENT_APP", "")
|
|
130
|
+
|
|
131
|
+
if feature_name:
|
|
132
|
+
_reset_one_feature(app, feature_name, yes, product_path, product_name)
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
_reset_everything(app, yes, product_path, product_name)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _reset_one_feature(app, feature_name, yes, product_path, product_name):
|
|
139
|
+
feature_full = _normalize(feature_name)
|
|
140
|
+
tables = _tables_owned_by(feature_full)
|
|
141
|
+
refiners = _refiners_of(feature_full, product_path)
|
|
142
|
+
feats = [feature_full] + refiners
|
|
143
|
+
|
|
144
|
+
if not tables:
|
|
145
|
+
click.secho(
|
|
146
|
+
f"⚠️ '{feature_full}' owns no tables (nothing to drop). "
|
|
147
|
+
f"If it is a refinement, reset the base feature instead.",
|
|
148
|
+
fg="yellow",
|
|
149
|
+
)
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
click.echo()
|
|
153
|
+
click.secho(f"Reset feature: {feature_full}", fg="cyan", bold=True)
|
|
154
|
+
click.echo(f" tables to drop : {', '.join(tables)}")
|
|
155
|
+
if refiners:
|
|
156
|
+
click.echo(f" also re-applies: {', '.join(refiners)} (refiners)")
|
|
157
|
+
if not yes and not click.confirm(
|
|
158
|
+
"⚠️ This drops those tables and re-applies their migrations. Continue?",
|
|
159
|
+
abort=True,
|
|
160
|
+
):
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
# Drop the feature's tables
|
|
164
|
+
with db.engine.begin() as conn:
|
|
165
|
+
conn.execute(text("SET FOREIGN_KEY_CHECKS = 0"))
|
|
166
|
+
for t in tables:
|
|
167
|
+
conn.execute(text(f"DROP TABLE IF EXISTS `{t}`"))
|
|
168
|
+
click.echo(click.style(f" Dropped {t}", fg="bright_black"))
|
|
169
|
+
conn.execute(text("SET FOREIGN_KEY_CHECKS = 1"))
|
|
170
|
+
|
|
171
|
+
# Clear migration tracking for the feature and its refiners — both the
|
|
172
|
+
# central splent_migrations row AND each feature's alembic version table —
|
|
173
|
+
# so their migrations are re-applied from scratch onto the fresh tables.
|
|
174
|
+
with db.engine.begin() as conn:
|
|
175
|
+
conn.execute(text("SET FOREIGN_KEY_CHECKS = 0"))
|
|
176
|
+
for feat in feats:
|
|
177
|
+
conn.execute(
|
|
178
|
+
text(f"DELETE FROM `{SPLENT_MIGRATIONS_TABLE}` WHERE feature = :f"),
|
|
179
|
+
{"f": feat},
|
|
180
|
+
)
|
|
181
|
+
conn.execute(text(f"DROP TABLE IF EXISTS `{alembic_version_table(feat)}`"))
|
|
182
|
+
conn.execute(text("SET FOREIGN_KEY_CHECKS = 1"))
|
|
183
|
+
|
|
184
|
+
click.secho("⬆️ Re-applying migrations...", fg="cyan")
|
|
185
|
+
_reapply(app, feats, _entry_lookup(), product_path, product_name)
|
|
186
|
+
click.secho(f"\n🎉 Feature '{feature_full}' reset complete.", fg="green")
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _reset_everything(app, yes, product_path, product_name):
|
|
190
|
+
if not yes and not click.confirm(
|
|
191
|
+
"⚠️ WARNING: This will DROP all tables and clear uploads. Are you sure?",
|
|
192
|
+
abort=True,
|
|
193
|
+
):
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
click.echo(click.style("🗑️ Dropping all tables...", fg="yellow"))
|
|
197
|
+
try:
|
|
198
|
+
with db.engine.begin() as conn:
|
|
199
|
+
conn.execute(text("SET FOREIGN_KEY_CHECKS = 0"))
|
|
200
|
+
meta = MetaData()
|
|
201
|
+
meta.reflect(bind=db.engine)
|
|
202
|
+
for table in meta.sorted_tables:
|
|
203
|
+
conn.execute(text(f"DROP TABLE IF EXISTS `{table.name}`"))
|
|
204
|
+
click.echo(click.style(f" Dropped {table.name}", fg="bright_black"))
|
|
205
|
+
conn.execute(text("SET FOREIGN_KEY_CHECKS = 1"))
|
|
206
|
+
click.echo(click.style("✅ All tables dropped.", fg="yellow"))
|
|
207
|
+
except Exception as e:
|
|
208
|
+
click.echo(click.style(f"❌ Error dropping tables: {e}", fg="red"))
|
|
209
|
+
click.echo(
|
|
210
|
+
click.style(
|
|
211
|
+
"⚠️ The database may now be in a PARTIAL state: some tables "
|
|
212
|
+
"may have been dropped while others remain.\n"
|
|
213
|
+
" To recover, re-run this command once the database is "
|
|
214
|
+
"reachable again (it is safe to repeat — drops use "
|
|
215
|
+
"DROP TABLE IF EXISTS), or manually drop the remaining "
|
|
216
|
+
"tables / re-create the schema from migrations.",
|
|
217
|
+
fg="red",
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
raise SystemExit(1)
|
|
221
|
+
|
|
222
|
+
click.echo(click.style("📋 Recreating splent_migrations table...", fg="cyan"))
|
|
223
|
+
with db.engine.begin() as conn:
|
|
224
|
+
conn.execute(
|
|
225
|
+
text(
|
|
226
|
+
f"""
|
|
227
|
+
CREATE TABLE IF NOT EXISTS `{SPLENT_MIGRATIONS_TABLE}` (
|
|
228
|
+
`feature` VARCHAR(255) NOT NULL,
|
|
229
|
+
`last_migration` VARCHAR(255) DEFAULT NULL,
|
|
230
|
+
PRIMARY KEY (`feature`)
|
|
231
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
232
|
+
"""
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
ctx = click.get_current_context()
|
|
237
|
+
ctx.invoke(clear_uploads)
|
|
238
|
+
|
|
239
|
+
dirs = MigrationManager.get_all_feature_migration_dirs()
|
|
240
|
+
if not dirs:
|
|
241
|
+
click.echo(click.style("⚠️ No feature migrations found.", fg="yellow"))
|
|
242
|
+
else:
|
|
243
|
+
click.echo(
|
|
244
|
+
click.style(
|
|
245
|
+
f"⬆️ Applying migrations for {len(dirs)} features...", fg="cyan"
|
|
246
|
+
)
|
|
247
|
+
)
|
|
248
|
+
_reapply(app, list(dirs.keys()), _entry_lookup(), product_path, product_name)
|
|
249
|
+
|
|
250
|
+
click.echo(click.style("\n🎉 Database reset complete.", fg="green"))
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
cli_command = db_reset
|
|
@@ -4,7 +4,6 @@ import tomllib
|
|
|
4
4
|
import click
|
|
5
5
|
import requests
|
|
6
6
|
from splent_cli.services import context, compose
|
|
7
|
-
from splent_cli.utils.feature_utils import read_features_from_data
|
|
8
7
|
from splent_cli.utils.cache_utils import make_feature_writable
|
|
9
8
|
from splent_cli.utils.proc import run
|
|
10
9
|
from splent_cli.utils.io_utils import load_toml, atomic_write
|
|
@@ -343,7 +342,16 @@ def feature_edit(feature_name, edit_all, force):
|
|
|
343
342
|
with open(pyproject_path, "rb") as f:
|
|
344
343
|
data = tomllib.load(f)
|
|
345
344
|
|
|
346
|
-
features
|
|
345
|
+
# Look across all feature lists (features, features_dev, features_prod) so
|
|
346
|
+
# that dev/prod-only features (e.g. admin) can also be unlocked, not just
|
|
347
|
+
# the default `features` list.
|
|
348
|
+
splent_cfg = data.get("tool", {}).get("splent", {})
|
|
349
|
+
features = [
|
|
350
|
+
entry
|
|
351
|
+
for key in ("features", "features_dev", "features_prod")
|
|
352
|
+
for entry in (splent_cfg.get(key) or [])
|
|
353
|
+
if isinstance(entry, str)
|
|
354
|
+
]
|
|
347
355
|
|
|
348
356
|
# ── Single feature ────────────────────────────────────────────────
|
|
349
357
|
if feature_name:
|
|
@@ -219,45 +219,42 @@ def spl_add_feature(spl_name, feature_package, org):
|
|
|
219
219
|
with open(uvl_path, "r", encoding="utf-8") as f:
|
|
220
220
|
lines = f.read().splitlines()
|
|
221
221
|
|
|
222
|
-
#
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if line.strip() == "constraints"
|
|
227
|
-
constraints_idx = i
|
|
228
|
-
insert_idx = i
|
|
229
|
-
break
|
|
230
|
-
|
|
231
|
-
if insert_idx is None:
|
|
232
|
-
insert_idx = len(lines)
|
|
233
|
-
|
|
234
|
-
# The line-insertion below assumes "constraints" lives on its own line.
|
|
235
|
-
# If it doesn't (e.g. malformed or unexpected layout), bail out before
|
|
236
|
-
# touching the file rather than silently corrupting the model.
|
|
237
|
-
if detected_deps and constraints_idx is None:
|
|
238
|
-
click.secho(
|
|
239
|
-
f" ❌ No 'constraints' section found in {uvl_path}.\n"
|
|
240
|
-
f" Refusing to edit: the UVL layout is not what this command "
|
|
241
|
-
f"expects (a 'constraints' header on its own line). "
|
|
242
|
-
f"Add the feature and constraints manually.",
|
|
243
|
-
fg="red",
|
|
244
|
-
)
|
|
245
|
-
raise SystemExit(1)
|
|
246
|
-
|
|
247
|
-
# Insert feature declaration
|
|
248
|
-
feature_line = (
|
|
249
|
-
f"\t\toptional\n\t\t\t{short_name} {{org '{org}', package '{feature_package}'}}"
|
|
222
|
+
# Locate the (optional) "constraints" header and an existing "optional" group.
|
|
223
|
+
# A fresh SPL has neither. UVL rejects an EMPTY "constraints" section, so we
|
|
224
|
+
# only create that section when there is at least one constraint to add.
|
|
225
|
+
constraints_idx = next(
|
|
226
|
+
(i for i, line in enumerate(lines) if line.strip() == "constraints"), None
|
|
250
227
|
)
|
|
251
|
-
|
|
228
|
+
optional_idx = next(
|
|
229
|
+
(i for i, line in enumerate(lines) if line.strip() == "optional"), None
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
# Insert the feature under a SINGLE shared "optional" group, reusing it if it
|
|
233
|
+
# already exists (so repeated calls don't emit multiple 'optional' headers,
|
|
234
|
+
# which produced invalid UVL). The feature goes just before "constraints" if
|
|
235
|
+
# present, otherwise at the end of the features block. Indentation is tabs
|
|
236
|
+
# throughout, consistent with spl:create.
|
|
237
|
+
feat_at = constraints_idx if constraints_idx is not None else len(lines)
|
|
238
|
+
feature_decl = f"\t\t\t{short_name} {{org '{org}', package '{feature_package}'}}"
|
|
239
|
+
if optional_idx is not None:
|
|
240
|
+
lines.insert(feat_at, feature_decl)
|
|
241
|
+
if constraints_idx is not None:
|
|
242
|
+
constraints_idx += 1
|
|
243
|
+
else:
|
|
244
|
+
lines.insert(feat_at, "\t\toptional")
|
|
245
|
+
lines.insert(feat_at + 1, feature_decl)
|
|
246
|
+
if constraints_idx is not None:
|
|
247
|
+
constraints_idx += 2
|
|
252
248
|
|
|
253
|
-
#
|
|
254
|
-
if detected_deps
|
|
255
|
-
|
|
249
|
+
# Add constraints, creating the section on demand (never leave it empty).
|
|
250
|
+
if detected_deps:
|
|
251
|
+
if constraints_idx is None:
|
|
252
|
+
lines.append("constraints")
|
|
253
|
+
constraints_idx = len(lines) - 1
|
|
254
|
+
ci = constraints_idx
|
|
256
255
|
for dep in sorted(detected_deps):
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
)
|
|
260
|
-
lines.insert(constraints_idx, f"\t{short_name} => {dep}")
|
|
256
|
+
ci += 1
|
|
257
|
+
lines.insert(ci, f"\t{short_name} => {dep}")
|
|
261
258
|
|
|
262
259
|
# Back up the original UVL, then write atomically so a crash mid-write can
|
|
263
260
|
# never leave a half-written / truncated model.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from splent_cli.services import context
|
|
4
|
+
from splent_cli.utils.feature_utils import (
|
|
5
|
+
load_product_features,
|
|
6
|
+
parse_feature_entry,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _load_feature_map(product_name: str) -> dict[tuple[str, str], str]:
|
|
11
|
+
"""Return {(org, package): "org/package"} for a product in the workspace.
|
|
12
|
+
|
|
13
|
+
Features are keyed by (organisation, package) so that two features only
|
|
14
|
+
match when BOTH the org/user and the package coincide. The version is
|
|
15
|
+
ignored on purpose — a product on v1.2.0 and another on v1.3.0 of the same
|
|
16
|
+
feature still share that feature.
|
|
17
|
+
"""
|
|
18
|
+
product_dir = context.workspace() / product_name
|
|
19
|
+
if not (product_dir / "pyproject.toml").is_file():
|
|
20
|
+
raise click.ClickException(
|
|
21
|
+
f"Product not found: '{product_name}' (no pyproject.toml in {product_dir})"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
feature_map: dict[tuple[str, str], str] = {}
|
|
25
|
+
for entry in load_product_features(str(product_dir)):
|
|
26
|
+
org, package, _version = parse_feature_entry(entry)
|
|
27
|
+
feature_map[(org, package)] = f"{org}/{package}"
|
|
28
|
+
return feature_map
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _print_block(title: str, color: str, keys: list[tuple[str, str]]) -> None:
|
|
32
|
+
click.secho(f" {title} ({len(keys)})", fg=color, bold=True)
|
|
33
|
+
if not keys:
|
|
34
|
+
click.secho(" —", fg="bright_black")
|
|
35
|
+
for org, package in keys:
|
|
36
|
+
click.echo(f" {org}/{package}")
|
|
37
|
+
click.echo()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@click.command(
|
|
41
|
+
"spl:compare",
|
|
42
|
+
short_help="Compare the features of two products (shared vs product-specific)",
|
|
43
|
+
)
|
|
44
|
+
@click.argument("product_a")
|
|
45
|
+
@click.argument("product_b")
|
|
46
|
+
@context.requires_detached
|
|
47
|
+
def spl_compare(product_a, product_b):
|
|
48
|
+
"""Compare two products in the workspace by their feature sets.
|
|
49
|
+
|
|
50
|
+
Reports the features they SHARE and the features SPECIFIC to each one.
|
|
51
|
+
Matching is done by organisation/package — two features are "the same"
|
|
52
|
+
only when both the org (or user) and the package name coincide.
|
|
53
|
+
|
|
54
|
+
\b
|
|
55
|
+
Example:
|
|
56
|
+
splent spl:compare innosoft_app diversolab_app
|
|
57
|
+
"""
|
|
58
|
+
a = _load_feature_map(product_a)
|
|
59
|
+
b = _load_feature_map(product_b)
|
|
60
|
+
|
|
61
|
+
a_keys, b_keys = set(a), set(b)
|
|
62
|
+
shared = sorted(a_keys & b_keys)
|
|
63
|
+
only_a = sorted(a_keys - b_keys)
|
|
64
|
+
only_b = sorted(b_keys - a_keys)
|
|
65
|
+
|
|
66
|
+
union = a_keys | b_keys
|
|
67
|
+
reuse_pct = (len(shared) / len(union) * 100) if union else 0.0
|
|
68
|
+
|
|
69
|
+
click.echo()
|
|
70
|
+
click.secho(f" {product_a} ↔ {product_b}", fg="cyan", bold=True)
|
|
71
|
+
click.echo(
|
|
72
|
+
f" {len(a_keys)} feature(s) in {product_a}"
|
|
73
|
+
f" · {len(b_keys)} feature(s) in {product_b}"
|
|
74
|
+
)
|
|
75
|
+
reuse_color = "green" if reuse_pct >= 50 else "yellow"
|
|
76
|
+
click.secho(
|
|
77
|
+
f" Reuse: {len(shared)}/{len(union)} shared ({reuse_pct:.0f}%)",
|
|
78
|
+
fg=reuse_color,
|
|
79
|
+
bold=True,
|
|
80
|
+
)
|
|
81
|
+
click.echo()
|
|
82
|
+
|
|
83
|
+
_print_block("Shared", "green", shared)
|
|
84
|
+
_print_block(f"Only in {product_a}", "yellow", only_a)
|
|
85
|
+
_print_block(f"Only in {product_b}", "magenta", only_b)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
cli_command = spl_compare
|
|
@@ -35,7 +35,10 @@ def spl_create(name):
|
|
|
35
35
|
|
|
36
36
|
# {name}.uvl
|
|
37
37
|
uvl_path = os.path.join(spl_dir, f"{name}.uvl")
|
|
38
|
-
|
|
38
|
+
# A fresh SPL has no features yet — emit a minimal VALID model (just the
|
|
39
|
+
# root, no empty 'mandatory'/'optional' group, which UVL rejects). Tabs are
|
|
40
|
+
# used for indentation, consistent with spl:add-feature.
|
|
41
|
+
uvl_content = f"features\n\t{name}\n"
|
|
39
42
|
with open(uvl_path, "w") as f:
|
|
40
43
|
f.write(uvl_content)
|
|
41
44
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: splent_cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.12.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
|
Project-URL: Homepage, https://github.com/diverso-lab/splent_cli
|
|
@@ -134,6 +134,7 @@ src/splent_cli/commands/release/release_core.py
|
|
|
134
134
|
src/splent_cli/commands/spl/__init__.py
|
|
135
135
|
src/splent_cli/commands/spl/spl_add_constraints.py
|
|
136
136
|
src/splent_cli/commands/spl/spl_add_feature.py
|
|
137
|
+
src/splent_cli/commands/spl/spl_compare.py
|
|
137
138
|
src/splent_cli/commands/spl/spl_configurations.py
|
|
138
139
|
src/splent_cli/commands/spl/spl_create.py
|
|
139
140
|
src/splent_cli/commands/spl/spl_deps.py
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
|
|
3
|
-
import click
|
|
4
|
-
from flask import current_app
|
|
5
|
-
from flask_migrate import upgrade as alembic_upgrade
|
|
6
|
-
from sqlalchemy import text, MetaData
|
|
7
|
-
|
|
8
|
-
from splent_cli.utils.decorators import requires_db
|
|
9
|
-
from splent_cli.services import context
|
|
10
|
-
from splent_cli.utils.lifecycle import advance_state, resolve_feature_key_from_entry
|
|
11
|
-
from splent_framework.db import db
|
|
12
|
-
from splent_framework.managers.migration_manager import (
|
|
13
|
-
MigrationManager,
|
|
14
|
-
SPLENT_MIGRATIONS_TABLE,
|
|
15
|
-
)
|
|
16
|
-
from splent_framework.utils.feature_utils import get_features_from_pyproject
|
|
17
|
-
from splent_framework.utils.path_utils import PathUtils
|
|
18
|
-
from splent_cli.commands.clear.clear_uploads import clear_uploads
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@requires_db
|
|
22
|
-
@click.command(
|
|
23
|
-
"db:reset",
|
|
24
|
-
short_help="Drop all tables and re-apply migrations from scratch.",
|
|
25
|
-
)
|
|
26
|
-
@click.option("-y", "--yes", is_flag=True, help="Confirm without prompting.")
|
|
27
|
-
@context.requires_product
|
|
28
|
-
def db_reset(yes):
|
|
29
|
-
"""
|
|
30
|
-
Full database reset: drops ALL tables (data, alembic tracking,
|
|
31
|
-
splent_migrations), then re-applies existing feature migrations.
|
|
32
|
-
|
|
33
|
-
Feature migration files are NOT deleted — only the database is wiped.
|
|
34
|
-
"""
|
|
35
|
-
app = current_app
|
|
36
|
-
|
|
37
|
-
if not yes and not click.confirm(
|
|
38
|
-
"⚠️ WARNING: This will DROP all tables and clear uploads. Are you sure?",
|
|
39
|
-
abort=True,
|
|
40
|
-
):
|
|
41
|
-
return
|
|
42
|
-
|
|
43
|
-
# --- STEP 1: Drop ALL tables (including alembic_* and splent_migrations) ---
|
|
44
|
-
# Run every DROP inside a single transaction (engine.begin) so that a
|
|
45
|
-
# mid-operation connection failure rolls back as a unit rather than
|
|
46
|
-
# leaving a half-dropped schema. Note: MySQL/MariaDB issue an implicit
|
|
47
|
-
# COMMIT on DDL, so a full rollback is not guaranteed there; we therefore
|
|
48
|
-
# also surface explicit partial-state recovery guidance on failure.
|
|
49
|
-
click.echo(click.style("🗑️ Dropping all tables...", fg="yellow"))
|
|
50
|
-
try:
|
|
51
|
-
with db.engine.begin() as conn:
|
|
52
|
-
conn.execute(text("SET FOREIGN_KEY_CHECKS = 0"))
|
|
53
|
-
meta = MetaData()
|
|
54
|
-
meta.reflect(bind=db.engine)
|
|
55
|
-
for table in meta.sorted_tables:
|
|
56
|
-
conn.execute(text(f"DROP TABLE IF EXISTS `{table.name}`"))
|
|
57
|
-
click.echo(click.style(f" Dropped {table.name}", fg="bright_black"))
|
|
58
|
-
conn.execute(text("SET FOREIGN_KEY_CHECKS = 1"))
|
|
59
|
-
click.echo(click.style("✅ All tables dropped.", fg="yellow"))
|
|
60
|
-
except Exception as e:
|
|
61
|
-
click.echo(click.style(f"❌ Error dropping tables: {e}", fg="red"))
|
|
62
|
-
click.echo(
|
|
63
|
-
click.style(
|
|
64
|
-
"⚠️ The database may now be in a PARTIAL state: some tables "
|
|
65
|
-
"may have been dropped while others remain.\n"
|
|
66
|
-
" To recover, re-run this command once the database is "
|
|
67
|
-
"reachable again (it is safe to repeat — drops use "
|
|
68
|
-
"DROP TABLE IF EXISTS), or manually drop the remaining "
|
|
69
|
-
"tables / re-create the schema from migrations.",
|
|
70
|
-
fg="red",
|
|
71
|
-
)
|
|
72
|
-
)
|
|
73
|
-
raise SystemExit(1)
|
|
74
|
-
|
|
75
|
-
# --- STEP 2: Recreate splent_migrations tracking table ---
|
|
76
|
-
click.echo(click.style("📋 Recreating splent_migrations table...", fg="cyan"))
|
|
77
|
-
try:
|
|
78
|
-
with db.engine.begin() as conn:
|
|
79
|
-
conn.execute(
|
|
80
|
-
text(
|
|
81
|
-
f"""
|
|
82
|
-
CREATE TABLE IF NOT EXISTS `{SPLENT_MIGRATIONS_TABLE}` (
|
|
83
|
-
`feature` VARCHAR(255) NOT NULL,
|
|
84
|
-
`last_migration` VARCHAR(255) DEFAULT NULL,
|
|
85
|
-
PRIMARY KEY (`feature`)
|
|
86
|
-
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
87
|
-
"""
|
|
88
|
-
)
|
|
89
|
-
)
|
|
90
|
-
except Exception as e:
|
|
91
|
-
click.echo(click.style(f"❌ Error creating tracking table: {e}", fg="red"))
|
|
92
|
-
click.echo(
|
|
93
|
-
click.style(
|
|
94
|
-
"⚠️ All tables were dropped but the tracking table could not "
|
|
95
|
-
"be recreated. Re-run this command once the database is "
|
|
96
|
-
"reachable to finish the reset.",
|
|
97
|
-
fg="red",
|
|
98
|
-
)
|
|
99
|
-
)
|
|
100
|
-
raise SystemExit(1)
|
|
101
|
-
|
|
102
|
-
# --- STEP 3: Clear uploads ---
|
|
103
|
-
ctx = click.get_current_context()
|
|
104
|
-
ctx.invoke(clear_uploads)
|
|
105
|
-
|
|
106
|
-
# --- STEP 4: Re-apply all existing feature migrations ---
|
|
107
|
-
dirs = MigrationManager.get_all_feature_migration_dirs()
|
|
108
|
-
|
|
109
|
-
# Build entry→key lookup for manifest updates
|
|
110
|
-
product_path = PathUtils.get_app_base_dir()
|
|
111
|
-
product_name = os.getenv("SPLENT_APP", "")
|
|
112
|
-
entry_lookup = {}
|
|
113
|
-
for entry in get_features_from_pyproject() or []:
|
|
114
|
-
key, ns, name, version = resolve_feature_key_from_entry(entry)
|
|
115
|
-
entry_lookup[name] = (key, ns, name, version)
|
|
116
|
-
|
|
117
|
-
if not dirs:
|
|
118
|
-
click.echo(click.style("⚠️ No feature migrations found.", fg="yellow"))
|
|
119
|
-
else:
|
|
120
|
-
click.echo(
|
|
121
|
-
click.style(
|
|
122
|
-
f"⬆️ Applying migrations for {len(dirs)} features...", fg="cyan"
|
|
123
|
-
)
|
|
124
|
-
)
|
|
125
|
-
for feat, mdir in dirs.items():
|
|
126
|
-
try:
|
|
127
|
-
alembic_upgrade(directory=mdir)
|
|
128
|
-
revision = MigrationManager.get_current_feature_revision(
|
|
129
|
-
feat, db.engine
|
|
130
|
-
)
|
|
131
|
-
MigrationManager.update_feature_status(app, feat, revision)
|
|
132
|
-
click.echo(
|
|
133
|
-
click.style(f" ✅ {feat} → {revision or 'head'}", fg="green")
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
# Advance lifecycle state to "migrated"
|
|
137
|
-
info = entry_lookup.get(feat)
|
|
138
|
-
if info:
|
|
139
|
-
key, ns, name, version = info
|
|
140
|
-
advance_state(
|
|
141
|
-
product_path,
|
|
142
|
-
product_name,
|
|
143
|
-
key,
|
|
144
|
-
to="migrated",
|
|
145
|
-
namespace=ns,
|
|
146
|
-
name=name,
|
|
147
|
-
version=version,
|
|
148
|
-
)
|
|
149
|
-
except ImportError as e:
|
|
150
|
-
if "models" in str(e):
|
|
151
|
-
continue
|
|
152
|
-
click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
|
|
153
|
-
except Exception as e:
|
|
154
|
-
click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
|
|
155
|
-
|
|
156
|
-
click.echo(click.style("\n🎉 Database reset complete.", fg="green"))
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
cli_command = db_reset
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|