splent-cli 1.14.0__tar.gz → 1.15.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.14.0/src/splent_cli.egg-info → splent_cli-1.15.0}/PKG-INFO +1 -1
- {splent_cli-1.14.0 → splent_cli-1.15.0}/pyproject.toml +1 -1
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/cli.py +5 -2
- splent_cli-1.15.0/src/splent_cli/commands/spl/spl_publish.py +434 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/uvl/uvl_utils.py +13 -2
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/services/marketplace.py +16 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0/src/splent_cli.egg-info}/PKG-INFO +1 -1
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli.egg-info/SOURCES.txt +1 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/LICENSE +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/README.md +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/setup.cfg +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/__init__.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/__main__.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/__init__.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/cache/__init__.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/check/__init__.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/check/check_contracts.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/check/check_deps.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/check/check_docker.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/check/check_env.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/check/check_features.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/check/check_github.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/check/check_infra.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/check/check_product.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/clear/__init__.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/command/__init__.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/command/command_create.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/coverage.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/database/db_console.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/database/db_dump.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/database/db_migrate.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/database/db_reset.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/database/db_restore.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/database/db_rollback.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/database/db_seed.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/database/db_status.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/database/db_upgrade.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/doctor.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/env/env_list.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/env/env_set.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/env/env_show.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/export/__init__.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/export/export_puml.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_add.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_attach.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_clean.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_compat.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_compile.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_contract.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_create.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_detach.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_drift.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_env.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_impact.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_info.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_install.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_pin.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_refine.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_release.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_remove.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_rename.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_status.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_test.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_unlock.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_upgrade.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/lint.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/locust.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/marketplace/marketplace_index.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/__init__.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_auto_require.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_build.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_clean.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_commands.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_config.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_configure.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_console.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_containers.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_create.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_deploy.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_derive.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_down.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_drift.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_env.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_list.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_logs.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_missing.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_port.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_release.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_resolve.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_restart.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_routes.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_run.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_select.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_shell.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_signals.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_test.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_up.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_validate.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/release/__init__.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/release/release_core.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/selenium.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/spl/__init__.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/spl/spl_compare.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/tokens_setup.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/commands/version.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/services/__init__.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/services/compose.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/services/context.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/services/preflight.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/services/registry.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/services/release.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/utils/__init__.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/utils/archetype.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/utils/cache_utils.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/utils/command_loader.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/utils/contract_freshness.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/utils/db_utils.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/utils/decorators.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/utils/feature_installer.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/utils/feature_utils.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/utils/git_url.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/utils/integrity.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/utils/io_utils.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/utils/lifecycle.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/utils/manifest.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/utils/path_utils.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/utils/proc.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli/utils/template_drift.py +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.0}/src/splent_cli.egg-info/requires.txt +0 -0
- {splent_cli-1.14.0 → splent_cli-1.15.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.15.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
|
|
@@ -3,9 +3,7 @@ import os
|
|
|
3
3
|
import click
|
|
4
4
|
from dotenv import load_dotenv
|
|
5
5
|
|
|
6
|
-
from splent_cli.utils.dynamic_imports import get_app
|
|
7
6
|
from splent_cli.utils.command_loader import load_commands
|
|
8
|
-
from splent_cli.utils.db_utils import check_db_connection
|
|
9
7
|
|
|
10
8
|
load_dotenv()
|
|
11
9
|
|
|
@@ -40,6 +38,8 @@ class SPLENTCLI(click.Group):
|
|
|
40
38
|
|
|
41
39
|
self._feature_cmds_cache: dict[str, click.BaseCommand] = {}
|
|
42
40
|
try:
|
|
41
|
+
from splent_cli.utils.dynamic_imports import get_app
|
|
42
|
+
|
|
43
43
|
app = get_app()
|
|
44
44
|
with app.app_context():
|
|
45
45
|
registry = app.extensions.get("splent_feature_commands", {})
|
|
@@ -84,6 +84,9 @@ class SPLENTCLI(click.Group):
|
|
|
84
84
|
command = self.get_command(ctx, cmd_name)
|
|
85
85
|
|
|
86
86
|
if command and getattr(command, "requires_app", False):
|
|
87
|
+
from splent_cli.utils.dynamic_imports import get_app
|
|
88
|
+
from splent_cli.utils.db_utils import check_db_connection
|
|
89
|
+
|
|
87
90
|
app = get_app()
|
|
88
91
|
if getattr(command, "requires_db", False):
|
|
89
92
|
if not check_db_connection(app):
|
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
"""
|
|
2
|
+
spl:publish — Publish the local UVL model of an SPL to UVLHub.
|
|
3
|
+
|
|
4
|
+
Closes the UVL → UVLHub → spl:fetch cycle: the .uvl files are not tracked in
|
|
5
|
+
git (UVLHub is the remote source of truth), so after editing a model locally
|
|
6
|
+
this command uploads it, publishes it (new dataset or new version of the
|
|
7
|
+
existing DOI), verifies the published raw file matches the local one
|
|
8
|
+
byte-for-byte, and only then records the new doi/file in metadata.toml.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
import tomllib
|
|
14
|
+
|
|
15
|
+
import click
|
|
16
|
+
import requests
|
|
17
|
+
|
|
18
|
+
from splent_cli.commands.spl.spl_utils import _resolve_spl_metadata
|
|
19
|
+
from splent_cli.commands.uvl.uvl_utils import (
|
|
20
|
+
resolve_uvlhub_raw_url,
|
|
21
|
+
uvlhub_base_url,
|
|
22
|
+
)
|
|
23
|
+
from splent_cli.services import context
|
|
24
|
+
from splent_cli.utils.io_utils import atomic_write, backup_file
|
|
25
|
+
|
|
26
|
+
_TIMEOUT = 60
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
# UVLHub API boundary (all network I/O goes through these helpers)
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _api_base() -> str:
|
|
35
|
+
"""Base URL of the UVLHub instance (UVLHUB_URL env, default uvlhub.io).
|
|
36
|
+
|
|
37
|
+
Shared with resolve_uvlhub_raw_url so upload/publish and verification
|
|
38
|
+
always target the SAME instance.
|
|
39
|
+
"""
|
|
40
|
+
return uvlhub_base_url()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _server_message(response) -> str:
|
|
44
|
+
"""Extract a human-readable error message from an API response."""
|
|
45
|
+
try:
|
|
46
|
+
data = response.json()
|
|
47
|
+
except Exception: # noqa: BLE001 - any body that is not JSON
|
|
48
|
+
data = None
|
|
49
|
+
if isinstance(data, dict):
|
|
50
|
+
for key in ("error", "message", "detail"):
|
|
51
|
+
if data.get(key):
|
|
52
|
+
return str(data[key])
|
|
53
|
+
text = (getattr(response, "text", "") or "").strip()
|
|
54
|
+
return text[:300] if text else f"HTTP {response.status_code}"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _check_response(response, action: str) -> dict:
|
|
58
|
+
"""Fail with the server's message on non-2xx; return the JSON body."""
|
|
59
|
+
if response.status_code not in (200, 201):
|
|
60
|
+
raise click.ClickException(
|
|
61
|
+
f"UVLHub rejected the {action} ({response.status_code}): "
|
|
62
|
+
f"{_server_message(response)}"
|
|
63
|
+
)
|
|
64
|
+
try:
|
|
65
|
+
data = response.json()
|
|
66
|
+
except Exception: # noqa: BLE001
|
|
67
|
+
raise click.ClickException(
|
|
68
|
+
f"UVLHub returned an invalid response for the {action} (not JSON)"
|
|
69
|
+
)
|
|
70
|
+
if not isinstance(data, dict):
|
|
71
|
+
raise click.ClickException(
|
|
72
|
+
f"UVLHub returned an unexpected response for the {action}"
|
|
73
|
+
)
|
|
74
|
+
return data
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _api_upload(base: str, api_key: str, uvl_path: str, title: str, description: str):
|
|
78
|
+
"""POST /api/v1/datasets/upload — create a draft dataset with the UVL."""
|
|
79
|
+
try:
|
|
80
|
+
with open(uvl_path, "rb") as fh:
|
|
81
|
+
r = requests.post(
|
|
82
|
+
f"{base}/api/v1/datasets/upload",
|
|
83
|
+
headers={"X-API-Key": api_key},
|
|
84
|
+
data={"title": title, "description": description},
|
|
85
|
+
files={"uvl_file": (os.path.basename(uvl_path), fh)},
|
|
86
|
+
timeout=_TIMEOUT,
|
|
87
|
+
)
|
|
88
|
+
except requests.RequestException as e:
|
|
89
|
+
raise click.ClickException(f"Could not reach UVLHub at {base}: {e}")
|
|
90
|
+
return _check_response(r, "upload")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _api_publish(base: str, api_key: str, dataset_id) -> dict:
|
|
94
|
+
"""POST /api/v1/datasets/<id>/publish — publish the draft (mints a DOI)."""
|
|
95
|
+
try:
|
|
96
|
+
r = requests.post(
|
|
97
|
+
f"{base}/api/v1/datasets/{dataset_id}/publish",
|
|
98
|
+
headers={"X-API-Key": api_key},
|
|
99
|
+
timeout=_TIMEOUT,
|
|
100
|
+
)
|
|
101
|
+
except requests.RequestException as e:
|
|
102
|
+
raise click.ClickException(f"Could not reach UVLHub at {base}: {e}")
|
|
103
|
+
return _check_response(r, "publish")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _api_lookup_doi(base: str, api_key: str, doi: str) -> dict:
|
|
107
|
+
"""GET /api/v1/datasets/doi/<doi> — resolve a DOI to its dataset."""
|
|
108
|
+
try:
|
|
109
|
+
r = requests.get(
|
|
110
|
+
f"{base}/api/v1/datasets/doi/{doi}",
|
|
111
|
+
headers={"X-API-Key": api_key},
|
|
112
|
+
timeout=_TIMEOUT,
|
|
113
|
+
)
|
|
114
|
+
except requests.RequestException as e:
|
|
115
|
+
raise click.ClickException(f"Could not reach UVLHub at {base}: {e}")
|
|
116
|
+
return _check_response(r, "DOI lookup")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _api_new_version(base: str, api_key: str, dataset_id, uvl_path: str) -> dict:
|
|
120
|
+
"""POST /api/v1/datasets/<id>/new-version — publish a new version."""
|
|
121
|
+
try:
|
|
122
|
+
with open(uvl_path, "rb") as fh:
|
|
123
|
+
r = requests.post(
|
|
124
|
+
f"{base}/api/v1/datasets/{dataset_id}/new-version",
|
|
125
|
+
headers={"X-API-Key": api_key},
|
|
126
|
+
files={"file": (os.path.basename(uvl_path), fh)},
|
|
127
|
+
timeout=_TIMEOUT,
|
|
128
|
+
)
|
|
129
|
+
except requests.RequestException as e:
|
|
130
|
+
raise click.ClickException(f"Could not reach UVLHub at {base}: {e}")
|
|
131
|
+
return _check_response(r, "new-version")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# ---------------------------------------------------------------------------
|
|
135
|
+
# Local helpers
|
|
136
|
+
# ---------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _pick_remote_file(files, local_name: str) -> tuple[str, str | None]:
|
|
140
|
+
"""Return (name, raw_url) of the REAL remote file from the API's files list.
|
|
141
|
+
|
|
142
|
+
UVLHub may rename the upload (secure_filename / collision suffixes), so
|
|
143
|
+
the name the server reports is the one metadata.toml must point at. The
|
|
144
|
+
raw_url the server reports (built with the host that actually served the
|
|
145
|
+
publish) is the authoritative download location for verification.
|
|
146
|
+
"""
|
|
147
|
+
if not isinstance(files, list) or not files:
|
|
148
|
+
raise click.ClickException(
|
|
149
|
+
"UVLHub response did not include the published files"
|
|
150
|
+
)
|
|
151
|
+
entries = [f for f in files if isinstance(f, dict) and f.get("name")]
|
|
152
|
+
if not entries:
|
|
153
|
+
raise click.ClickException(
|
|
154
|
+
"UVLHub response did not include the published file name"
|
|
155
|
+
)
|
|
156
|
+
chosen = next(
|
|
157
|
+
(f for f in entries if f["name"] == local_name),
|
|
158
|
+
next((f for f in entries if f["name"].endswith(".uvl")), entries[0]),
|
|
159
|
+
)
|
|
160
|
+
raw_url = chosen.get("raw_url")
|
|
161
|
+
return chosen["name"], raw_url if isinstance(raw_url, str) else None
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _toml_str(value: str) -> str:
|
|
165
|
+
"""Render a string as a quoted TOML value."""
|
|
166
|
+
return '"' + value.replace("\\", "\\\\").replace('"', '\\"') + '"'
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _update_metadata_uvl(metadata_path: str, *, doi: str, file: str) -> None:
|
|
170
|
+
"""Rewrite doi/file inside [spl.uvl] preserving the rest of metadata.toml.
|
|
171
|
+
|
|
172
|
+
Text-level edit (comments/formatting survive), backed by a .bak copy and
|
|
173
|
+
an atomic write, then re-parsed to confirm the values landed. On any
|
|
174
|
+
failure the original file is restored — metadata.toml is never left
|
|
175
|
+
corrupted or half-written.
|
|
176
|
+
"""
|
|
177
|
+
with open(metadata_path, encoding="utf-8") as f:
|
|
178
|
+
lines = f.read().splitlines()
|
|
179
|
+
|
|
180
|
+
section = None
|
|
181
|
+
uvl_header_idx = None
|
|
182
|
+
doi_done = file_done = False
|
|
183
|
+
for i, line in enumerate(lines):
|
|
184
|
+
stripped = line.strip()
|
|
185
|
+
if stripped.startswith("[") and stripped.endswith("]"):
|
|
186
|
+
section = stripped
|
|
187
|
+
if section == "[spl.uvl]":
|
|
188
|
+
uvl_header_idx = i
|
|
189
|
+
continue
|
|
190
|
+
if section != "[spl.uvl]":
|
|
191
|
+
continue
|
|
192
|
+
m = re.match(r"^(\s*)(doi|file)\s*=", line)
|
|
193
|
+
if not m:
|
|
194
|
+
continue
|
|
195
|
+
key = m.group(2)
|
|
196
|
+
value = doi if key == "doi" else file
|
|
197
|
+
lines[i] = f"{m.group(1)}{key} = {_toml_str(value)}"
|
|
198
|
+
if key == "doi":
|
|
199
|
+
doi_done = True
|
|
200
|
+
else:
|
|
201
|
+
file_done = True
|
|
202
|
+
|
|
203
|
+
if uvl_header_idx is None:
|
|
204
|
+
if lines and lines[-1].strip():
|
|
205
|
+
lines.append("")
|
|
206
|
+
lines.append("[spl.uvl]")
|
|
207
|
+
lines.append('mirror = "uvlhub.io"')
|
|
208
|
+
lines.append(f"doi = {_toml_str(doi)}")
|
|
209
|
+
lines.append(f"file = {_toml_str(file)}")
|
|
210
|
+
else:
|
|
211
|
+
missing = []
|
|
212
|
+
if not doi_done:
|
|
213
|
+
missing.append(f"doi = {_toml_str(doi)}")
|
|
214
|
+
if not file_done:
|
|
215
|
+
missing.append(f"file = {_toml_str(file)}")
|
|
216
|
+
for offset, entry in enumerate(missing):
|
|
217
|
+
lines.insert(uvl_header_idx + 1 + offset, entry)
|
|
218
|
+
|
|
219
|
+
bak = backup_file(metadata_path, ".bak")
|
|
220
|
+
try:
|
|
221
|
+
atomic_write(metadata_path, "\n".join(lines) + "\n")
|
|
222
|
+
with open(metadata_path, "rb") as f:
|
|
223
|
+
written = tomllib.load(f)
|
|
224
|
+
uvl_cfg = written.get("spl", {}).get("uvl", {})
|
|
225
|
+
if uvl_cfg.get("doi") != doi or uvl_cfg.get("file") != file:
|
|
226
|
+
raise click.ClickException(
|
|
227
|
+
"metadata.toml did not validate after editing "
|
|
228
|
+
"(doi/file not found on re-read)"
|
|
229
|
+
)
|
|
230
|
+
except BaseException:
|
|
231
|
+
if bak is not None:
|
|
232
|
+
atomic_write(metadata_path, bak.read_text(encoding="utf-8"))
|
|
233
|
+
raise
|
|
234
|
+
finally:
|
|
235
|
+
if bak is not None and bak.exists():
|
|
236
|
+
bak.unlink()
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _verify_published(
|
|
240
|
+
mirror: str,
|
|
241
|
+
doi: str,
|
|
242
|
+
remote_file: str,
|
|
243
|
+
local_bytes: bytes,
|
|
244
|
+
raw_url: str | None = None,
|
|
245
|
+
):
|
|
246
|
+
"""Download the published raw file and compare it byte-for-byte.
|
|
247
|
+
|
|
248
|
+
Prefers the absolute raw_url reported by the server (it carries the host
|
|
249
|
+
that actually served the publish); only falls back to the URL built from
|
|
250
|
+
mirror/doi/file. Follows redirects (the DOI mapping may 302 to the
|
|
251
|
+
versioned dataset). Returns (matches: bool, url: str).
|
|
252
|
+
"""
|
|
253
|
+
if raw_url and raw_url.startswith(("http://", "https://")):
|
|
254
|
+
url = raw_url
|
|
255
|
+
else:
|
|
256
|
+
url = resolve_uvlhub_raw_url(mirror, doi, remote_file)
|
|
257
|
+
try:
|
|
258
|
+
r = requests.get(url, timeout=_TIMEOUT, allow_redirects=True)
|
|
259
|
+
except requests.RequestException as e:
|
|
260
|
+
raise click.ClickException(f"Verification download failed: {e}")
|
|
261
|
+
if r.status_code != 200:
|
|
262
|
+
raise click.ClickException(
|
|
263
|
+
f"Verification failed: UVLHub returned {r.status_code} for {url}"
|
|
264
|
+
)
|
|
265
|
+
return r.content == local_bytes, url
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
# ---------------------------------------------------------------------------
|
|
269
|
+
# Command
|
|
270
|
+
# ---------------------------------------------------------------------------
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@click.command(
|
|
274
|
+
"spl:publish",
|
|
275
|
+
short_help="Publish the local UVL model to UVLHub and update metadata.toml",
|
|
276
|
+
)
|
|
277
|
+
@click.argument("spl_name")
|
|
278
|
+
@click.option(
|
|
279
|
+
"--dry-run",
|
|
280
|
+
is_flag=True,
|
|
281
|
+
help="Show what would be done without touching the network or metadata.",
|
|
282
|
+
)
|
|
283
|
+
@context.requires_detached
|
|
284
|
+
def spl_publish(spl_name, dry_run):
|
|
285
|
+
"""Publish the local UVL model of the SPL to UVLHub.
|
|
286
|
+
|
|
287
|
+
\b
|
|
288
|
+
- Empty doi in metadata.toml → uploads a NEW dataset and publishes it.
|
|
289
|
+
- Existing doi → publishes a NEW VERSION of that dataset.
|
|
290
|
+
|
|
291
|
+
After publishing, the raw file on UVLHub is downloaded and compared
|
|
292
|
+
byte-for-byte with the local UVL; only if they match are doi/file
|
|
293
|
+
written to splent_catalog/{spl}/metadata.toml.
|
|
294
|
+
|
|
295
|
+
Requires UVLHUB_API_KEY (generate one at {UVLHUB_URL}/developer/api-keys).
|
|
296
|
+
"""
|
|
297
|
+
workspace = str(context.workspace())
|
|
298
|
+
base = _api_base()
|
|
299
|
+
|
|
300
|
+
metadata = _resolve_spl_metadata(spl_name)
|
|
301
|
+
spl_cfg = metadata.get("spl", {})
|
|
302
|
+
uvl_cfg = spl_cfg.get("uvl", {})
|
|
303
|
+
mirror = uvl_cfg.get("mirror") or "uvlhub.io"
|
|
304
|
+
doi = (uvl_cfg.get("doi") or "").strip()
|
|
305
|
+
|
|
306
|
+
spl_dir = os.path.join(workspace, "splent_catalog", spl_name)
|
|
307
|
+
metadata_path = os.path.join(spl_dir, "metadata.toml")
|
|
308
|
+
uvl_path = os.path.join(spl_dir, f"{spl_name}.uvl")
|
|
309
|
+
|
|
310
|
+
if mirror != "uvlhub.io":
|
|
311
|
+
click.secho(
|
|
312
|
+
f" ❌ Unsupported mirror '{mirror}' in metadata.toml "
|
|
313
|
+
f"(only 'uvlhub.io' can be published to).",
|
|
314
|
+
fg="red",
|
|
315
|
+
)
|
|
316
|
+
raise SystemExit(1)
|
|
317
|
+
|
|
318
|
+
if not os.path.isfile(uvl_path):
|
|
319
|
+
click.secho(f" ❌ Local UVL not found: {uvl_path}", fg="red")
|
|
320
|
+
click.echo(
|
|
321
|
+
" Nothing to publish. Build the model first "
|
|
322
|
+
"(spl:create / spl:add-feature)"
|
|
323
|
+
)
|
|
324
|
+
click.echo(
|
|
325
|
+
f" or download the current version with: splent spl:fetch {spl_name}"
|
|
326
|
+
)
|
|
327
|
+
raise SystemExit(1)
|
|
328
|
+
|
|
329
|
+
title = spl_cfg.get("name") or spl_name
|
|
330
|
+
description = (spl_cfg.get("description") or "").strip() or (
|
|
331
|
+
f"UVL variability model for the '{spl_name}' SPLENT product line."
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
if dry_run:
|
|
335
|
+
click.echo()
|
|
336
|
+
click.echo(click.style(f" spl:publish {spl_name} (dry run)", bold=True))
|
|
337
|
+
click.echo()
|
|
338
|
+
click.echo(f" Local UVL : {uvl_path}")
|
|
339
|
+
click.echo(f" API base : {base}")
|
|
340
|
+
click.echo()
|
|
341
|
+
if doi:
|
|
342
|
+
click.echo(f" Would publish a NEW VERSION of the existing DOI {doi}:")
|
|
343
|
+
click.echo(" 1. GET /api/v1/datasets/doi/… (resolve dataset id)")
|
|
344
|
+
click.echo(" 2. POST /api/v1/datasets/<id>/new-version (upload UVL)")
|
|
345
|
+
else:
|
|
346
|
+
click.echo(" Would upload a NEW dataset and publish it:")
|
|
347
|
+
click.echo(f" 1. POST /api/v1/datasets/upload (title: {title})")
|
|
348
|
+
click.echo(" 2. POST /api/v1/datasets/<id>/publish (mints the DOI)")
|
|
349
|
+
click.echo(
|
|
350
|
+
" 3. Verify the published raw file matches the local UVL "
|
|
351
|
+
"byte-for-byte"
|
|
352
|
+
)
|
|
353
|
+
click.echo(f" 4. Write doi/file into {metadata_path}")
|
|
354
|
+
if not os.getenv("UVLHUB_API_KEY"):
|
|
355
|
+
click.echo()
|
|
356
|
+
click.secho(
|
|
357
|
+
" ⚠ UVLHUB_API_KEY is not set — a real run would fail.",
|
|
358
|
+
fg="yellow",
|
|
359
|
+
)
|
|
360
|
+
click.echo()
|
|
361
|
+
click.secho(" ✅ Dry run: nothing was changed.", fg="green")
|
|
362
|
+
return
|
|
363
|
+
|
|
364
|
+
api_key = os.getenv("UVLHUB_API_KEY")
|
|
365
|
+
if not api_key:
|
|
366
|
+
click.secho(" ❌ UVLHUB_API_KEY is not set.", fg="red")
|
|
367
|
+
click.echo(f" Generate an API key at {base}/developer/api-keys")
|
|
368
|
+
click.echo(" and export it before publishing:")
|
|
369
|
+
click.echo(" export UVLHUB_API_KEY=<your-key>")
|
|
370
|
+
raise SystemExit(1)
|
|
371
|
+
|
|
372
|
+
with open(uvl_path, "rb") as f:
|
|
373
|
+
local_bytes = f.read()
|
|
374
|
+
local_name = os.path.basename(uvl_path)
|
|
375
|
+
|
|
376
|
+
click.echo()
|
|
377
|
+
click.echo(click.style(f" Publishing {spl_name} to {base}", bold=True))
|
|
378
|
+
click.echo()
|
|
379
|
+
|
|
380
|
+
if doi:
|
|
381
|
+
click.echo(f" Existing DOI {doi} — creating a new version...")
|
|
382
|
+
dataset = _api_lookup_doi(base, api_key, doi)
|
|
383
|
+
dataset_id = dataset.get("dataset_id")
|
|
384
|
+
if not dataset_id:
|
|
385
|
+
raise click.ClickException(
|
|
386
|
+
f"UVLHub DOI lookup for {doi} did not return a dataset_id"
|
|
387
|
+
)
|
|
388
|
+
result = _api_new_version(base, api_key, dataset_id, uvl_path)
|
|
389
|
+
else:
|
|
390
|
+
click.echo(" No DOI in metadata.toml — uploading a new dataset...")
|
|
391
|
+
uploaded = _api_upload(base, api_key, uvl_path, title, description)
|
|
392
|
+
dataset_id = uploaded.get("dataset_id")
|
|
393
|
+
if not dataset_id:
|
|
394
|
+
raise click.ClickException(
|
|
395
|
+
"UVLHub upload did not return a dataset_id"
|
|
396
|
+
)
|
|
397
|
+
click.echo(f" 📤 Draft dataset created (id {dataset_id}) — publishing...")
|
|
398
|
+
result = _api_publish(base, api_key, dataset_id)
|
|
399
|
+
|
|
400
|
+
new_doi = (result.get("doi") or "").strip()
|
|
401
|
+
if not new_doi:
|
|
402
|
+
raise click.ClickException("UVLHub did not return a DOI after publishing")
|
|
403
|
+
remote_file, raw_url = _pick_remote_file(result.get("files"), local_name)
|
|
404
|
+
click.echo(f" 🌐 Published as DOI {new_doi} (file: {remote_file})")
|
|
405
|
+
|
|
406
|
+
click.echo(" Verifying published content against the local UVL...")
|
|
407
|
+
matches, url = _verify_published(
|
|
408
|
+
mirror, new_doi, remote_file, local_bytes, raw_url
|
|
409
|
+
)
|
|
410
|
+
if not matches:
|
|
411
|
+
click.secho(
|
|
412
|
+
" ❌ Verification failed: the published content differs from "
|
|
413
|
+
"the local UVL.",
|
|
414
|
+
fg="red",
|
|
415
|
+
)
|
|
416
|
+
click.echo(f" URL: {url}")
|
|
417
|
+
click.echo(
|
|
418
|
+
" metadata.toml was NOT modified — it still points to the "
|
|
419
|
+
"previous version."
|
|
420
|
+
)
|
|
421
|
+
raise SystemExit(1)
|
|
422
|
+
|
|
423
|
+
_update_metadata_uvl(metadata_path, doi=new_doi, file=remote_file)
|
|
424
|
+
|
|
425
|
+
click.echo()
|
|
426
|
+
click.secho(
|
|
427
|
+
f" ✅ '{spl_name}' published to UVLHub (DOI {new_doi}).", fg="green"
|
|
428
|
+
)
|
|
429
|
+
if doi and new_doi != doi:
|
|
430
|
+
click.echo(f" metadata.toml updated: doi {doi} → {new_doi}")
|
|
431
|
+
click.echo()
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
cli_command = spl_publish
|
|
@@ -13,6 +13,13 @@ import tomllib
|
|
|
13
13
|
|
|
14
14
|
from splent_cli.utils.feature_utils import read_features_from_data
|
|
15
15
|
|
|
16
|
+
DEFAULT_UVLHUB_URL = "https://www.uvlhub.io"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def uvlhub_base_url() -> str:
|
|
20
|
+
"""Base URL of the UVLHub instance (UVLHUB_URL env, default uvlhub.io)."""
|
|
21
|
+
return os.getenv("UVLHUB_URL", DEFAULT_UVLHUB_URL).rstrip("/")
|
|
22
|
+
|
|
16
23
|
|
|
17
24
|
def _require_flamapy():
|
|
18
25
|
try:
|
|
@@ -131,12 +138,16 @@ def normalize_feature_name(dep: str) -> str:
|
|
|
131
138
|
|
|
132
139
|
|
|
133
140
|
def resolve_uvlhub_raw_url(mirror: str, doi: str, file: str) -> str:
|
|
134
|
-
"""Resolve a uvlhub.io DOI + filename to a raw download URL.
|
|
141
|
+
"""Resolve a uvlhub.io DOI + filename to a raw download URL.
|
|
142
|
+
|
|
143
|
+
The base honors UVLHUB_URL so fetch/info/publish all target the same
|
|
144
|
+
instance (custom deployments included), defaulting to production.
|
|
145
|
+
"""
|
|
135
146
|
if mirror != "uvlhub.io":
|
|
136
147
|
raise click.ClickException(
|
|
137
148
|
f"Unsupported mirror '{mirror}' (only 'uvlhub.io' implemented)"
|
|
138
149
|
)
|
|
139
|
-
return f"
|
|
150
|
+
return f"{uvlhub_base_url()}/doi/{doi}/files/raw/{file}/"
|
|
140
151
|
|
|
141
152
|
|
|
142
153
|
def iter_children(node):
|
|
@@ -30,6 +30,8 @@ import tomllib
|
|
|
30
30
|
from datetime import datetime, timezone
|
|
31
31
|
from pathlib import Path
|
|
32
32
|
|
|
33
|
+
import click
|
|
34
|
+
|
|
33
35
|
INDEX_SCHEMA = 1
|
|
34
36
|
FEATURE_PREFIX = "splent_feature_"
|
|
35
37
|
|
|
@@ -370,6 +372,20 @@ def build_spls(workspace: str) -> list[dict]:
|
|
|
370
372
|
"model": None,
|
|
371
373
|
}
|
|
372
374
|
uvl_path = spl_dir / f"{spl_dir.name}.uvl"
|
|
375
|
+
if not uvl_path.is_file():
|
|
376
|
+
# UVL files are not tracked in git — fetch from UVLHub (spl:fetch
|
|
377
|
+
# logic) when the metadata has a DOI. Best-effort: an SPL without
|
|
378
|
+
# a published model is indexed without one, never fails the build
|
|
379
|
+
# — but the failure is warned about, never swallowed silently.
|
|
380
|
+
try:
|
|
381
|
+
from splent_cli.commands.spl.spl_utils import _fetch_uvl
|
|
382
|
+
|
|
383
|
+
_fetch_uvl(spl_dir.name, metadata, str(uvl_path))
|
|
384
|
+
except Exception as e: # noqa: BLE001 - best-effort indexing
|
|
385
|
+
click.secho(
|
|
386
|
+
f" ⚠ could not fetch UVL for {spl_dir.name}: {e}",
|
|
387
|
+
fg="yellow",
|
|
388
|
+
)
|
|
373
389
|
if uvl_path.is_file():
|
|
374
390
|
try:
|
|
375
391
|
entry["model"] = parse_uvl_structure(uvl_path.read_text())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: splent_cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.15.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
|
|
@@ -145,6 +145,7 @@ src/splent_cli/commands/spl/spl_features.py
|
|
|
145
145
|
src/splent_cli/commands/spl/spl_fetch.py
|
|
146
146
|
src/splent_cli/commands/spl/spl_info.py
|
|
147
147
|
src/splent_cli/commands/spl/spl_list.py
|
|
148
|
+
src/splent_cli/commands/spl/spl_publish.py
|
|
148
149
|
src/splent_cli/commands/spl/spl_utils.py
|
|
149
150
|
src/splent_cli/commands/uvl/uvl_utils.py
|
|
150
151
|
src/splent_cli/services/__init__.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|