nextmv 1.7.1__tar.gz → 1.7.2.dev0__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.
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/PKG-INFO +1 -1
- nextmv-1.7.2.dev0/nextmv/__about__.py +1 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/main.py +57 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/local/executor.py +22 -14
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/local/runner.py +14 -3
- nextmv-1.7.2.dev0/nextmv/local/uv_handler.py +58 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/pyproject.toml +0 -1
- nextmv-1.7.2.dev0/tests/cli/test_main.py +176 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/local/test_runner.py +34 -2
- nextmv-1.7.2.dev0/tests/local/test_uv_handler.py +164 -0
- nextmv-1.7.1/nextmv/__about__.py +0 -1
- nextmv-1.7.1/tests/cli/test_main.py +0 -68
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/.gitignore +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/.python-version +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/CONTRIBUTING.md +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/LICENSE +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/README.md +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/__entrypoint__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/_serialization.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/account.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/base_model.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/acceptance/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/acceptance/create.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/acceptance/delete.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/acceptance/get.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/acceptance/list.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/acceptance/update.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/account/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/account/create.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/account/delete.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/account/get.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/account/update.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/app/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/app/create.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/app/delete.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/app/exists.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/app/get.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/app/list.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/app/push.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/app/update.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/batch/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/batch/create.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/batch/delete.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/batch/get.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/batch/list.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/batch/metadata.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/batch/update.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/data/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/data/upload.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/ensemble/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/ensemble/create.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/ensemble/delete.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/ensemble/get.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/ensemble/list.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/ensemble/update.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/input_set/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/input_set/create.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/input_set/delete.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/input_set/get.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/input_set/list.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/input_set/update.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/instance/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/instance/create.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/instance/delete.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/instance/exists.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/instance/get.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/instance/list.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/instance/update.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/managed_input/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/managed_input/create.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/managed_input/delete.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/managed_input/get.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/managed_input/list.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/managed_input/update.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/marketplace/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/marketplace/app/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/marketplace/app/create.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/marketplace/app/get.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/marketplace/app/list.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/marketplace/app/update.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/marketplace/subscription/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/marketplace/subscription/create.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/marketplace/subscription/delete.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/marketplace/subscription/get.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/marketplace/subscription/list.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/marketplace/version/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/marketplace/version/create.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/marketplace/version/get.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/marketplace/version/list.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/marketplace/version/update.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/run/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/run/cancel.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/run/create.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/run/delete.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/run/get.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/run/input.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/run/list.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/run/logs.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/run/metadata.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/run/track.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/scenario/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/scenario/create.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/scenario/delete.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/scenario/get.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/scenario/list.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/scenario/metadata.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/scenario/update.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/secrets/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/secrets/create.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/secrets/delete.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/secrets/get.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/secrets/list.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/secrets/update.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/shadow/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/shadow/create.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/shadow/delete.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/shadow/get.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/shadow/list.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/shadow/metadata.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/shadow/start.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/shadow/stop.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/shadow/update.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/sso/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/sso/create.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/sso/delete.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/sso/disable.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/sso/domain/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/sso/domain/delete.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/sso/enable.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/sso/get.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/sso/update.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/switchback/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/switchback/create.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/switchback/delete.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/switchback/get.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/switchback/list.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/switchback/metadata.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/switchback/start.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/switchback/stop.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/switchback/update.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/upload/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/upload/create.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/version/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/version/create.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/version/delete.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/version/exists.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/version/get.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/version/list.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/cloud/version/update.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/community/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/community/clone.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/community/list.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/configuration/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/configuration/config.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/configuration/create.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/configuration/delete.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/configuration/list.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/init.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/local/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/local/app/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/local/app/delete.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/local/app/get.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/local/app/list.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/local/app/register.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/local/app/registered.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/local/app/sync.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/local/app/update.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/local/run/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/local/run/create.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/local/run/get.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/local/run/input.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/local/run/list.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/local/run/logs.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/local/run/metadata.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/local/run/visuals.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/manifest/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/manifest/init.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/manifest/validate.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/mcp/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/mcp/serve.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/mcp/server.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/mcp/tools/WORKFLOW.md +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/mcp/tools/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/mcp/tools/_helpers.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/mcp/tools/acceptance.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/mcp/tools/account.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/mcp/tools/app.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/mcp/tools/batch.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/mcp/tools/community.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/mcp/tools/ensemble.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/mcp/tools/guide.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/mcp/tools/input_set.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/mcp/tools/instance.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/mcp/tools/local.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/mcp/tools/managed_input.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/mcp/tools/profile.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/mcp/tools/run.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/mcp/tools/scenario.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/mcp/tools/secrets.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/mcp/tools/shadow.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/mcp/tools/sso.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/mcp/tools/switchback.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/mcp/tools/version.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/message.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/options.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cli/version.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/acceptance_test.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/account.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/application/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/application/_acceptance.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/application/_batch_scenario.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/application/_ensemble.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/application/_input_set.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/application/_instance.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/application/_managed_input.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/application/_run.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/application/_secrets.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/application/_shadow.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/application/_switchback.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/application/_utils.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/application/_version.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/assets.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/batch_experiment.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/client.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/community.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/ensemble.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/input_set.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/instance.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/integration.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/marketplace.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/package.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/scenario.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/secrets.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/shadow.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/sso.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/switchback.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/url.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/cloud/version.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/content_format.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/deprecated.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/input.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/local/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/local/application.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/local/geojson_handler.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/local/local.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/local/plotly_handler.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/local/registry.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/logger.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/manifest.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/model.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/options.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/output.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/polling.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/run.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/safe.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/status.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/binary_json_app.yaml +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/binary_multi-file_app.yaml +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/go_json_app.yaml +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/go_json_hello-world/.gitignore +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/go_json_hello-world/README.md +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/go_json_hello-world/app.yaml +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/go_json_hello-world/go.mod +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/go_json_hello-world/input.json +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/go_json_hello-world/main.go +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/go_multi-file_app.yaml +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/go_multi-file_hello-world/.gitignore +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/go_multi-file_hello-world/README.md +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/go_multi-file_hello-world/app.yaml +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/go_multi-file_hello-world/go.mod +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/go_multi-file_hello-world/inputs/input.json +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/go_multi-file_hello-world/main.go +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/java_json_app.yaml +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/java_json_hello-world/.gitignore +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/java_json_hello-world/Main.java +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/java_json_hello-world/README.md +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/java_json_hello-world/app.yaml +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/java_json_hello-world/input.json +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/java_json_hello-world/pom.xml +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/java_multi-file_app.yaml +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/java_multi-file_hello-world/.gitignore +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/java_multi-file_hello-world/Main.java +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/java_multi-file_hello-world/README.md +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/java_multi-file_hello-world/app.yaml +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/java_multi-file_hello-world/inputs/input.json +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/java_multi-file_hello-world/pom.xml +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_app.yaml +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_class-assign/.gitignore +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_class-assign/README.md +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_class-assign/app.yaml +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_class-assign/input.json +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_class-assign/main.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_class-assign/requirements.txt +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_class-assign/visualizations.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_demand-alloc/.gitignore +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_demand-alloc/README.md +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_demand-alloc/allocation/README.md +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_demand-alloc/allocation/app.yaml +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_demand-alloc/allocation/input.json +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_demand-alloc/allocation/main.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_demand-alloc/allocation/requirements.txt +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_demand-alloc/allocation/visuals.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_demand-alloc/workflow/README.md +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_demand-alloc/workflow/app.yaml +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_demand-alloc/workflow/input.json +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_demand-alloc/workflow/main.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_demand-alloc/workflow/requirements.txt +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_demand-alloc/workflow/visuals.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_hello-world/.gitignore +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_hello-world/README.md +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_hello-world/app.yaml +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_hello-world/input.json +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_hello-world/main.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_json_hello-world/requirements.txt +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_app.yaml +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_class-assign/.gitignore +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_class-assign/README.md +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_class-assign/app.yaml +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_class-assign/inputs/input.json +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_class-assign/main.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_class-assign/requirements.txt +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_class-assign/visualizations.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_demand-alloc/README.md +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_demand-alloc/allocation/README.md +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_demand-alloc/allocation/app.yaml +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_demand-alloc/allocation/input.json +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_demand-alloc/allocation/main.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_demand-alloc/allocation/requirements.txt +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_demand-alloc/allocation/visuals.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_demand-alloc/workflow/.gitignore +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_demand-alloc/workflow/README.md +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_demand-alloc/workflow/app.yaml +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_demand-alloc/workflow/inputs/input.json +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_demand-alloc/workflow/main.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_demand-alloc/workflow/requirements.txt +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_demand-alloc/workflow/visuals.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_hello-world/.gitignore +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_hello-world/README.md +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_hello-world/app.yaml +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_hello-world/inputs/input.json +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_hello-world/main.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/nextmv/templates/python_multi-file_hello-world/requirements.txt +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/cli/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/cli/test_configuration.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/cli/test_mcp.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/cli/test_version.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/cloud/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/cloud/app.yaml +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/cloud/test_client.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/cloud/test_instance.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/cloud/test_package.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/cloud/test_scenario.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/integration/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/integration/cloud/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/integration/cloud/test_integration_cloud.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/integration/cloud/test_integration_marketplace.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/local/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/local/test_application.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/local/test_executor.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/local/test_registry.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/scripts/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/scripts/options1.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/scripts/options2.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/scripts/options3.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/scripts/options4.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/scripts/options5.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/scripts/options6.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/scripts/options7.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/test_base_model.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/test_entrypoint/__init__.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/test_entrypoint/test_entrypoint.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/test_input.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/test_inputs/test_data.csv +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/test_inputs/test_data.json +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/test_inputs/test_data.txt +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/test_logger.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/test_manifest.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/test_model.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/test_options.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/test_output.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/test_polling.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/test_run.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/test_safe.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/test_serialization.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/tests/test_version.py +0 -0
- {nextmv-1.7.1 → nextmv-1.7.2.dev0}/uv.lock +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "v1.7.2.dev0"
|
|
@@ -13,7 +13,9 @@ about the features used here. An example of Rich markup can be found in the
|
|
|
13
13
|
epilog of the Typer application defined below.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
+
import io
|
|
16
17
|
import os
|
|
18
|
+
import runpy
|
|
17
19
|
import sys
|
|
18
20
|
from typing import Annotated
|
|
19
21
|
|
|
@@ -32,6 +34,7 @@ from nextmv.cli.message import confirmation, error, info, success, warning
|
|
|
32
34
|
from nextmv.cli.version import app as version_app
|
|
33
35
|
from nextmv.cli.version import version_callback
|
|
34
36
|
from nextmv.cloud.client import retrieve_endpoint_from_config, retrieve_key_from_config
|
|
37
|
+
from nextmv.local.uv_handler import _find_uv_binary
|
|
35
38
|
|
|
36
39
|
# Disable dim text for the extended help of commands.
|
|
37
40
|
rich_utils.STYLE_HELPTEXT = ""
|
|
@@ -261,6 +264,38 @@ def _remove_go_cli() -> None:
|
|
|
261
264
|
success(f"Deleted [italic red]deprecated[/italic red] [magenta]{GO_CLI_PATH}[/magenta].")
|
|
262
265
|
|
|
263
266
|
|
|
267
|
+
def setup_encoding() -> None:
|
|
268
|
+
"""
|
|
269
|
+
Configure UTF-8 encoding for Windows platforms to make sure emojis and rich text do
|
|
270
|
+
not cause encoding errors.
|
|
271
|
+
"""
|
|
272
|
+
# Only force UTF-8 encoding on Windows where the default encoding is not already UTF-8
|
|
273
|
+
# and where sys.stdout has a buffer attribute (indicating it's a real stream and not a
|
|
274
|
+
# mock).
|
|
275
|
+
if (
|
|
276
|
+
sys.platform == "win32"
|
|
277
|
+
and getattr(sys.stdout, "encoding", "").lower() != "utf-8"
|
|
278
|
+
and hasattr(sys.stdout, "buffer")
|
|
279
|
+
):
|
|
280
|
+
try:
|
|
281
|
+
sys.stdout = io.TextIOWrapper(
|
|
282
|
+
sys.stdout.buffer,
|
|
283
|
+
encoding="utf-8",
|
|
284
|
+
errors="replace", # Don't crash on bad chars
|
|
285
|
+
line_buffering=True,
|
|
286
|
+
)
|
|
287
|
+
sys.stderr = io.TextIOWrapper(
|
|
288
|
+
sys.stderr.buffer,
|
|
289
|
+
encoding="utf-8",
|
|
290
|
+
errors="replace",
|
|
291
|
+
line_buffering=True,
|
|
292
|
+
)
|
|
293
|
+
except Exception:
|
|
294
|
+
# If wrapping fails (e.g. in some CI environments),
|
|
295
|
+
# fall back to the original stream rather than crashing.
|
|
296
|
+
pass
|
|
297
|
+
|
|
298
|
+
|
|
264
299
|
def main() -> None:
|
|
265
300
|
"""
|
|
266
301
|
Entry point for the CLI with global exception handling.
|
|
@@ -269,6 +304,28 @@ def main() -> None:
|
|
|
269
304
|
own exit codes) and displays a clean error message instead of a traceback.
|
|
270
305
|
"""
|
|
271
306
|
|
|
307
|
+
# Improve compatibility with Windows terminals.
|
|
308
|
+
setup_encoding()
|
|
309
|
+
|
|
310
|
+
# Handle --run-script and --run-uv for running scripts with the bundled Python
|
|
311
|
+
# interpreter or via uv. These are used internally in case of frozen PyInstaller
|
|
312
|
+
# distributions.
|
|
313
|
+
if len(sys.argv) > 1 and sys.argv[1] == "--run-script":
|
|
314
|
+
if len(sys.argv) < 3:
|
|
315
|
+
rich.print("[red]Error:[/red] --run-script requires a script path.", file=sys.stderr)
|
|
316
|
+
sys.exit(1)
|
|
317
|
+
script_path = sys.argv[2]
|
|
318
|
+
sys.argv = sys.argv[2:] # script becomes argv[0]; its own args follow
|
|
319
|
+
runpy.run_path(script_path, run_name="__main__")
|
|
320
|
+
sys.exit(0)
|
|
321
|
+
elif len(sys.argv) > 1 and sys.argv[1] == "--run-uv":
|
|
322
|
+
if len(sys.argv) < 3:
|
|
323
|
+
rich.print("[red]Error:[/red] --run-uv requires arguments for 'uv run'.", file=sys.stderr)
|
|
324
|
+
sys.exit(1)
|
|
325
|
+
uv_bin = _find_uv_binary()
|
|
326
|
+
uv_args = [uv_bin, "run"] + sys.argv[2:]
|
|
327
|
+
os.execv(uv_bin, uv_args)
|
|
328
|
+
|
|
272
329
|
try:
|
|
273
330
|
app()
|
|
274
331
|
except (typer.Exit, typer.Abort, SystemExit):
|
|
@@ -418,11 +418,6 @@ def process_run_output(
|
|
|
418
418
|
temp_run_outputs_dir = os.path.join(temp_src, OUTPUTS_KEY)
|
|
419
419
|
|
|
420
420
|
output_format = manifest.configuration.content.format
|
|
421
|
-
_process_run_information(
|
|
422
|
-
run_id=run_id,
|
|
423
|
-
run_dir=run_dir,
|
|
424
|
-
result=result,
|
|
425
|
-
)
|
|
426
421
|
_process_run_metrics(
|
|
427
422
|
temp_run_outputs_dir=temp_run_outputs_dir,
|
|
428
423
|
outputs_dir=outputs_dir,
|
|
@@ -459,6 +454,16 @@ def process_run_output(
|
|
|
459
454
|
run_dir=run_dir,
|
|
460
455
|
outputs_dir=outputs_dir,
|
|
461
456
|
)
|
|
457
|
+
# NOTE: _process_run_information must be called last. It sets status_v2 to "succeeded"
|
|
458
|
+
# or "failed", which signals to pollers that the run is complete. format.output
|
|
459
|
+
# (written by _process_run_solutions) must already be on disk before that status
|
|
460
|
+
# transition is visible, otherwise a poller can observe status=succeeded with
|
|
461
|
+
# format_output=None and crash.
|
|
462
|
+
_process_run_information(
|
|
463
|
+
run_id=run_id,
|
|
464
|
+
run_dir=run_dir,
|
|
465
|
+
result=result,
|
|
466
|
+
)
|
|
462
467
|
|
|
463
468
|
|
|
464
469
|
def _process_run_information(run_id: str, run_dir: str, result: subprocess.CompletedProcess[str]) -> None:
|
|
@@ -1073,6 +1078,12 @@ def __determine_command(manifest: Manifest) -> list[str]:
|
|
|
1073
1078
|
"""
|
|
1074
1079
|
Returns the command to execute based on the application type.
|
|
1075
1080
|
|
|
1081
|
+
When running Python apps as a frozen PyInstaller single binary, ``sys.executable``
|
|
1082
|
+
points to the binary itself rather than a Python interpreter, so the normal
|
|
1083
|
+
``[sys.executable, "-m", "uv", "run", ...]`` invocation cannot work. Instead, we use a
|
|
1084
|
+
special ``[sys.executable, "--run-uv", ...]`` invocation that signals to our own
|
|
1085
|
+
binary to re-execute and pass to the bundled uv binary.
|
|
1086
|
+
|
|
1076
1087
|
Parameters
|
|
1077
1088
|
----------
|
|
1078
1089
|
manifest : Manifest
|
|
@@ -1084,26 +1095,23 @@ def __determine_command(manifest: Manifest) -> list[str]:
|
|
|
1084
1095
|
The command prefix to use for execution. Empty list for binary executables.
|
|
1085
1096
|
"""
|
|
1086
1097
|
if manifest.type == ManifestType.PYTHON:
|
|
1098
|
+
is_frozen = getattr(sys, "frozen", False)
|
|
1099
|
+
entry_point = [sys.executable, "-m", "uv", "run"] if not is_frozen else [sys.executable, "--run-uv"]
|
|
1100
|
+
|
|
1087
1101
|
if manifest.python and manifest.python.pip_requirements:
|
|
1088
1102
|
if isinstance(manifest.python.pip_requirements, list):
|
|
1089
1103
|
return [
|
|
1090
|
-
|
|
1091
|
-
"-m",
|
|
1092
|
-
"uv",
|
|
1093
|
-
"run",
|
|
1104
|
+
*entry_point,
|
|
1094
1105
|
"--with",
|
|
1095
1106
|
",".join(manifest.python.pip_requirements),
|
|
1096
1107
|
]
|
|
1097
1108
|
elif isinstance(manifest.python.pip_requirements, str):
|
|
1098
1109
|
return [
|
|
1099
|
-
|
|
1100
|
-
"-m",
|
|
1101
|
-
"uv",
|
|
1102
|
-
"run",
|
|
1110
|
+
*entry_point,
|
|
1103
1111
|
"--with-requirements",
|
|
1104
1112
|
manifest.python.pip_requirements,
|
|
1105
1113
|
]
|
|
1106
|
-
return [
|
|
1114
|
+
return [*entry_point]
|
|
1107
1115
|
elif manifest.type == ManifestType.GO:
|
|
1108
1116
|
return []
|
|
1109
1117
|
elif manifest.type == ManifestType.BINARY:
|
|
@@ -122,7 +122,18 @@ def run(
|
|
|
122
122
|
"options": options,
|
|
123
123
|
}
|
|
124
124
|
)
|
|
125
|
-
|
|
125
|
+
# When frozen as a single binary (PyInstaller), sys.executable points to
|
|
126
|
+
# the binary itself rather than a Python interpreter, so we use the
|
|
127
|
+
# --run-script mechanism to run executor.py with the bundled interpreter.
|
|
128
|
+
# In a normal Python package install, sys.executable is a real interpreter
|
|
129
|
+
# and executor.py can be launched directly.
|
|
130
|
+
# We always use the absolute path to executor.py so it can be resolved
|
|
131
|
+
# regardless of the working directory the binary was invoked from.
|
|
132
|
+
executor_path = os.path.join(os.path.dirname(__file__), "executor.py")
|
|
133
|
+
if getattr(sys, "frozen", False):
|
|
134
|
+
args = [sys.executable, "--run-script", executor_path]
|
|
135
|
+
else:
|
|
136
|
+
args = [sys.executable, executor_path]
|
|
126
137
|
process = subprocess.Popen(
|
|
127
138
|
args,
|
|
128
139
|
env=os.environ,
|
|
@@ -130,8 +141,8 @@ def run(
|
|
|
130
141
|
stdin=subprocess.PIPE,
|
|
131
142
|
stdout=subprocess.DEVNULL,
|
|
132
143
|
stderr=subprocess.DEVNULL,
|
|
133
|
-
cwd=os.path.dirname(
|
|
134
|
-
start_new_session=True,
|
|
144
|
+
cwd=os.path.dirname(executor_path),
|
|
145
|
+
start_new_session=True,
|
|
135
146
|
)
|
|
136
147
|
process.stdin.write(stdin_input)
|
|
137
148
|
process.stdin.close()
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import platform
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _find_uv_binary() -> str:
|
|
7
|
+
"""
|
|
8
|
+
Locate the uv binary at runtime.
|
|
9
|
+
|
|
10
|
+
Resolves the binary in this order:
|
|
11
|
+
1. If running in a PyInstaller bundle, the uv binary is expected to be bundled under
|
|
12
|
+
`sys._MEIPASS/uv_bin/`.
|
|
13
|
+
2. Otherwise, check whether uv module is installed and use its helper function to find
|
|
14
|
+
the binary.
|
|
15
|
+
3. Finally, check if uv is available on the system PATH.
|
|
16
|
+
|
|
17
|
+
Returns
|
|
18
|
+
-------
|
|
19
|
+
str
|
|
20
|
+
Absolute path to the uv binary.
|
|
21
|
+
|
|
22
|
+
Raises
|
|
23
|
+
------
|
|
24
|
+
FileNotFoundError
|
|
25
|
+
If the uv binary cannot be located.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
# 1. Check for PyInstaller bundled uv binary.
|
|
29
|
+
if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
|
|
30
|
+
# In case of single binary PyInstaller distribution, the uv binary is bundled
|
|
31
|
+
# under sys._MEIPASS/uv_bin/.
|
|
32
|
+
exe_suffix = ".exe" if platform.system() == "Windows" else ""
|
|
33
|
+
uv_path = os.path.join(sys._MEIPASS, "uv_bin", f"uv{exe_suffix}")
|
|
34
|
+
if not os.path.isfile(uv_path):
|
|
35
|
+
raise FileNotFoundError(
|
|
36
|
+
f"Bundled uv binary not found at {uv_path}, please ensure proper installation of the Nextmv CLI."
|
|
37
|
+
)
|
|
38
|
+
return uv_path
|
|
39
|
+
|
|
40
|
+
# 2. Fall back to finding uv via the installed module (if available).
|
|
41
|
+
try:
|
|
42
|
+
from uv import find_uv_bin # type: ignore[import]
|
|
43
|
+
|
|
44
|
+
return find_uv_bin()
|
|
45
|
+
except (ImportError, AttributeError):
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
# 3. Finally, check if uv is available on the system PATH.
|
|
49
|
+
from shutil import which
|
|
50
|
+
|
|
51
|
+
uv_path = which("uv")
|
|
52
|
+
if uv_path:
|
|
53
|
+
return uv_path
|
|
54
|
+
|
|
55
|
+
raise FileNotFoundError(
|
|
56
|
+
"uv binary not found. Please ensure that uv is installed and available "
|
|
57
|
+
+ "on your system (via PATH or as module in Python)."
|
|
58
|
+
)
|
|
@@ -55,7 +55,6 @@ Homepage = "https://www.nextmv.io"
|
|
|
55
55
|
Documentation = "https://nextmv-py.docs.nextmv.io/en/latest/nextmv/"
|
|
56
56
|
Repository = "https://github.com/nextmv-io/nextmv-py"
|
|
57
57
|
|
|
58
|
-
# TODO: Uncomment when CLI is ready for general rollout.
|
|
59
58
|
[project.scripts]
|
|
60
59
|
nextmv = "nextmv.cli.main:main"
|
|
61
60
|
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for the nextmv CLI main module.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import tempfile
|
|
8
|
+
import unittest
|
|
9
|
+
from unittest.mock import patch
|
|
10
|
+
|
|
11
|
+
from nextmv.cli.main import app, main
|
|
12
|
+
from typer.testing import CliRunner
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestCallback(unittest.TestCase):
|
|
16
|
+
"""Tests for the CLI callback behavior."""
|
|
17
|
+
|
|
18
|
+
def setUp(self):
|
|
19
|
+
self.runner = CliRunner()
|
|
20
|
+
self.app = app
|
|
21
|
+
|
|
22
|
+
@patch("nextmv.cli.main._go_cli_exists")
|
|
23
|
+
@patch("nextmv.cli.main.load_config")
|
|
24
|
+
def test_callback_skips_config_check_for_configure(self, mock_load_config, mock_go_cli_exists):
|
|
25
|
+
"""Test that the callback skips config check when running configuration."""
|
|
26
|
+
mock_load_config.return_value = {}
|
|
27
|
+
mock_go_cli_exists.return_value = False
|
|
28
|
+
|
|
29
|
+
# Running configuration should not trigger the error even with empty config
|
|
30
|
+
result = self.runner.invoke(self.app, ["configuration", "--help"])
|
|
31
|
+
self.assertEqual(result.exit_code, 0)
|
|
32
|
+
self.assertIn("Configure the CLI", result.output)
|
|
33
|
+
|
|
34
|
+
@patch("nextmv.cli.main._go_cli_exists")
|
|
35
|
+
@patch("nextmv.cli.main.load_config")
|
|
36
|
+
def test_callback_shows_error_when_no_config(self, mock_load_config, mock_go_cli_exists):
|
|
37
|
+
"""Test that the callback shows error when no config exists for other commands."""
|
|
38
|
+
mock_load_config.return_value = {}
|
|
39
|
+
mock_go_cli_exists.return_value = False
|
|
40
|
+
|
|
41
|
+
result = self.runner.invoke(self.app, ["version"])
|
|
42
|
+
self.assertEqual(result.exit_code, 0)
|
|
43
|
+
|
|
44
|
+
@patch("nextmv.cli.main._go_cli_exists")
|
|
45
|
+
@patch("nextmv.cli.main.load_config")
|
|
46
|
+
def test_callback_allows_command_when_config_exists(self, mock_load_config, mock_go_cli_exists):
|
|
47
|
+
"""Test that the callback allows commands when config exists."""
|
|
48
|
+
mock_load_config.return_value = {"api_key": "test_key"}
|
|
49
|
+
mock_go_cli_exists.return_value = False
|
|
50
|
+
|
|
51
|
+
result = self.runner.invoke(self.app, ["version"])
|
|
52
|
+
self.assertEqual(result.exit_code, 0)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class TestHandleGoCli(unittest.TestCase):
|
|
56
|
+
"""Tests for the Go CLI handling behavior."""
|
|
57
|
+
|
|
58
|
+
def setUp(self):
|
|
59
|
+
self.runner = CliRunner()
|
|
60
|
+
self.app = app
|
|
61
|
+
|
|
62
|
+
@patch("nextmv.cli.main._go_cli_exists")
|
|
63
|
+
@patch("nextmv.cli.main.load_config")
|
|
64
|
+
def test_no_prompt_when_go_cli_not_exists(self, mock_load_config, mock_go_cli_exists):
|
|
65
|
+
"""Test that no prompt is shown when Go CLI does not exist."""
|
|
66
|
+
mock_go_cli_exists.return_value = False
|
|
67
|
+
mock_load_config.return_value = {"api_key": "test_key"}
|
|
68
|
+
|
|
69
|
+
result = self.runner.invoke(self.app, ["version"])
|
|
70
|
+
self.assertEqual(result.exit_code, 0)
|
|
71
|
+
self.assertNotIn("deprecated", result.output)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class TestRunScript(unittest.TestCase):
|
|
75
|
+
"""Tests for the --run-script hidden flag."""
|
|
76
|
+
|
|
77
|
+
def _write_script(self, content: str) -> str:
|
|
78
|
+
"""Write content to a temporary script file and return its path."""
|
|
79
|
+
fd, path = tempfile.mkstemp(suffix=".py")
|
|
80
|
+
with os.fdopen(fd, "w") as f:
|
|
81
|
+
f.write(content)
|
|
82
|
+
return path
|
|
83
|
+
|
|
84
|
+
def test_run_script_executes_script(self):
|
|
85
|
+
"""Test that --run-script runs the given script."""
|
|
86
|
+
script = self._write_script("import sys; sys.exit(42)\n")
|
|
87
|
+
try:
|
|
88
|
+
with patch.object(sys, "argv", ["nextmv", "--run-script", script]):
|
|
89
|
+
with self.assertRaises(SystemExit) as cm:
|
|
90
|
+
main()
|
|
91
|
+
self.assertEqual(cm.exception.code, 42)
|
|
92
|
+
finally:
|
|
93
|
+
os.unlink(script)
|
|
94
|
+
|
|
95
|
+
def test_run_script_forwards_trailing_args(self):
|
|
96
|
+
"""Test that trailing arguments are visible to the script as sys.argv."""
|
|
97
|
+
# The script writes its own argv to a temp file so we can inspect it.
|
|
98
|
+
fd, result_file = tempfile.mkstemp(suffix=".txt")
|
|
99
|
+
os.close(fd)
|
|
100
|
+
script = self._write_script(f"import sys\nopen({result_file!r}, 'w').write(','.join(sys.argv))\n")
|
|
101
|
+
try:
|
|
102
|
+
with patch.object(sys, "argv", ["nextmv", "--run-script", script, "arg1", "arg2"]):
|
|
103
|
+
with self.assertRaises(SystemExit) as cm:
|
|
104
|
+
main()
|
|
105
|
+
self.assertEqual(cm.exception.code, 0)
|
|
106
|
+
with open(result_file) as f:
|
|
107
|
+
recorded = f.read().split(",")
|
|
108
|
+
# The script sees itself as argv[0] and its own trailing args after.
|
|
109
|
+
self.assertEqual(recorded[0], script)
|
|
110
|
+
self.assertEqual(recorded[1], "arg1")
|
|
111
|
+
self.assertEqual(recorded[2], "arg2")
|
|
112
|
+
finally:
|
|
113
|
+
os.unlink(script)
|
|
114
|
+
if os.path.exists(result_file):
|
|
115
|
+
os.unlink(result_file)
|
|
116
|
+
|
|
117
|
+
def test_run_script_missing_path_exits_with_error(self):
|
|
118
|
+
"""Test that omitting the script path prints an error and exits 1."""
|
|
119
|
+
with patch.object(sys, "argv", ["nextmv", "--run-script"]):
|
|
120
|
+
with self.assertRaises(SystemExit) as cm:
|
|
121
|
+
main()
|
|
122
|
+
self.assertEqual(cm.exception.code, 1)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class TestRunUv(unittest.TestCase):
|
|
126
|
+
"""Tests for the --run-uv hidden flag."""
|
|
127
|
+
|
|
128
|
+
def test_run_uv_missing_args_exits_with_error(self):
|
|
129
|
+
"""--run-uv with no further arguments prints an error and exits 1."""
|
|
130
|
+
with patch.object(sys, "argv", ["nextmv", "--run-uv"]):
|
|
131
|
+
with self.assertRaises(SystemExit) as cm:
|
|
132
|
+
main()
|
|
133
|
+
self.assertEqual(cm.exception.code, 1)
|
|
134
|
+
|
|
135
|
+
@patch("nextmv.cli.main._find_uv_binary")
|
|
136
|
+
@patch("os.execv")
|
|
137
|
+
def test_run_uv_calls_execv_with_correct_args(self, mock_execv, mock_find_uv):
|
|
138
|
+
"""--run-uv resolves the uv binary and calls os.execv with 'uv run <args>'."""
|
|
139
|
+
mock_find_uv.return_value = "/usr/bin/uv"
|
|
140
|
+
# os.execv normally replaces the process and never returns; raise SystemExit
|
|
141
|
+
# so that main() stops at that point rather than falling through to app().
|
|
142
|
+
mock_execv.side_effect = SystemExit(0)
|
|
143
|
+
|
|
144
|
+
with patch.object(sys, "argv", ["nextmv", "--run-uv", "script.py", "--option", "val"]):
|
|
145
|
+
with self.assertRaises(SystemExit) as cm:
|
|
146
|
+
main()
|
|
147
|
+
self.assertEqual(cm.exception.code, 0)
|
|
148
|
+
|
|
149
|
+
mock_find_uv.assert_called_once()
|
|
150
|
+
mock_execv.assert_called_once_with(
|
|
151
|
+
"/usr/bin/uv",
|
|
152
|
+
["/usr/bin/uv", "run", "script.py", "--option", "val"],
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
@patch("nextmv.cli.main._find_uv_binary")
|
|
156
|
+
@patch("os.execv")
|
|
157
|
+
def test_run_uv_single_extra_arg(self, mock_execv, mock_find_uv):
|
|
158
|
+
"""--run-uv works with a single extra argument."""
|
|
159
|
+
mock_find_uv.return_value = "/opt/uv/uv"
|
|
160
|
+
mock_execv.side_effect = SystemExit(0)
|
|
161
|
+
|
|
162
|
+
with patch.object(sys, "argv", ["nextmv", "--run-uv", "app.py"]):
|
|
163
|
+
with self.assertRaises(SystemExit):
|
|
164
|
+
main()
|
|
165
|
+
|
|
166
|
+
mock_execv.assert_called_once_with(
|
|
167
|
+
"/opt/uv/uv",
|
|
168
|
+
["/opt/uv/uv", "run", "app.py"],
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
@patch("nextmv.cli.main._find_uv_binary", side_effect=FileNotFoundError("uv not found"))
|
|
172
|
+
def test_run_uv_uv_binary_not_found_raises(self, mock_find_uv):
|
|
173
|
+
"""--run-uv propagates FileNotFoundError when the uv binary cannot be located."""
|
|
174
|
+
with patch.object(sys, "argv", ["nextmv", "--run-uv", "script.py"]):
|
|
175
|
+
with self.assertRaises(FileNotFoundError):
|
|
176
|
+
main()
|
|
@@ -10,10 +10,13 @@ import tempfile
|
|
|
10
10
|
import unittest
|
|
11
11
|
from unittest.mock import Mock, patch
|
|
12
12
|
|
|
13
|
+
import nextmv.local.runner as runner_module
|
|
13
14
|
from nextmv.local.local import NEXTMV_DIR, RUNS_KEY
|
|
14
15
|
from nextmv.local.runner import new_run, record_input, run
|
|
15
16
|
from nextmv.manifest import Manifest, ManifestRuntime
|
|
16
17
|
|
|
18
|
+
EXECUTOR_PATH = os.path.join(os.path.dirname(runner_module.__file__), "executor.py")
|
|
19
|
+
|
|
17
20
|
|
|
18
21
|
class TestLocalRunner(unittest.TestCase):
|
|
19
22
|
"""Test cases for the local runner module."""
|
|
@@ -268,8 +271,8 @@ print(json.dumps(output))
|
|
|
268
271
|
mock_popen.assert_called_once()
|
|
269
272
|
popen_args = mock_popen.call_args
|
|
270
273
|
|
|
271
|
-
# Check the command
|
|
272
|
-
self.assertEqual(popen_args[0][0], [sys.executable,
|
|
274
|
+
# Check the command (normal Python install, not frozen)
|
|
275
|
+
self.assertEqual(popen_args[0][0], [sys.executable, EXECUTOR_PATH])
|
|
273
276
|
|
|
274
277
|
# Check that stdin was written to
|
|
275
278
|
mock_process.stdin.write.assert_called_once()
|
|
@@ -296,6 +299,35 @@ print(json.dumps(output))
|
|
|
296
299
|
self.assertEqual(stdin_json["options"], options)
|
|
297
300
|
self.assertEqual(stdin_json["run_config"], run_config)
|
|
298
301
|
|
|
302
|
+
@patch("nextmv.local.runner.subprocess.Popen")
|
|
303
|
+
@patch("nextmv.local.runner.safe_id")
|
|
304
|
+
def test_run_function_execution_frozen(self, mock_safe_id, mock_popen):
|
|
305
|
+
"""Test that --run-script is used when running as a frozen PyInstaller binary."""
|
|
306
|
+
mock_safe_id.return_value = "test-run-id"
|
|
307
|
+
mock_process = Mock()
|
|
308
|
+
mock_process.stdin = Mock()
|
|
309
|
+
mock_popen.return_value = mock_process
|
|
310
|
+
|
|
311
|
+
manifest = Manifest(
|
|
312
|
+
files=["main.py"],
|
|
313
|
+
runtime=ManifestRuntime.PYTHON,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
run_config = {"format": {"input": {"type": "json"}, "output": {"type": "json"}}}
|
|
317
|
+
|
|
318
|
+
with patch.object(sys, "frozen", True, create=True):
|
|
319
|
+
run(
|
|
320
|
+
app_id="sample-app",
|
|
321
|
+
src=self.test_src,
|
|
322
|
+
manifest=manifest,
|
|
323
|
+
run_config=run_config,
|
|
324
|
+
input_data={},
|
|
325
|
+
options={},
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
popen_args = mock_popen.call_args
|
|
329
|
+
self.assertEqual(popen_args[0][0], [sys.executable, "--run-script", EXECUTOR_PATH])
|
|
330
|
+
|
|
299
331
|
@patch("nextmv.local.runner.subprocess.Popen")
|
|
300
332
|
@patch("nextmv.local.runner.safe_id")
|
|
301
333
|
def test_run_with_inputs_dir_path(self, mock_safe_id, mock_popen):
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for the _find_uv_binary() function in nextmv.local.uv_handler.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import tempfile
|
|
8
|
+
import unittest
|
|
9
|
+
from unittest.mock import MagicMock, patch
|
|
10
|
+
|
|
11
|
+
from nextmv.local.uv_handler import _find_uv_binary
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestFindUvBinary(unittest.TestCase):
|
|
15
|
+
"""Tests for _find_uv_binary()."""
|
|
16
|
+
|
|
17
|
+
# ------------------------------------------------------------------
|
|
18
|
+
# PyInstaller (frozen) branch
|
|
19
|
+
# ------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
def test_frozen_returns_bundled_path(self):
|
|
22
|
+
"""When running as a frozen bundle the path inside _MEIPASS is returned."""
|
|
23
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
24
|
+
uv_bin_dir = os.path.join(tmp, "uv_bin")
|
|
25
|
+
os.makedirs(uv_bin_dir)
|
|
26
|
+
fake_uv = os.path.join(uv_bin_dir, "uv")
|
|
27
|
+
open(fake_uv, "w").close() # create the file
|
|
28
|
+
|
|
29
|
+
with (
|
|
30
|
+
patch.object(sys, "frozen", True, create=True),
|
|
31
|
+
patch.object(sys, "_MEIPASS", tmp, create=True),
|
|
32
|
+
patch("platform.system", return_value="Linux"),
|
|
33
|
+
):
|
|
34
|
+
result = _find_uv_binary()
|
|
35
|
+
|
|
36
|
+
self.assertEqual(result, fake_uv)
|
|
37
|
+
|
|
38
|
+
def test_frozen_windows_uses_exe_suffix(self):
|
|
39
|
+
"""On Windows the bundled binary name ends with .exe."""
|
|
40
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
41
|
+
uv_bin_dir = os.path.join(tmp, "uv_bin")
|
|
42
|
+
os.makedirs(uv_bin_dir)
|
|
43
|
+
fake_uv = os.path.join(uv_bin_dir, "uv.exe")
|
|
44
|
+
open(fake_uv, "w").close()
|
|
45
|
+
|
|
46
|
+
with (
|
|
47
|
+
patch.object(sys, "frozen", True, create=True),
|
|
48
|
+
patch.object(sys, "_MEIPASS", tmp, create=True),
|
|
49
|
+
patch("platform.system", return_value="Windows"),
|
|
50
|
+
):
|
|
51
|
+
result = _find_uv_binary()
|
|
52
|
+
|
|
53
|
+
self.assertEqual(result, fake_uv)
|
|
54
|
+
|
|
55
|
+
def test_frozen_missing_binary_raises(self):
|
|
56
|
+
"""FileNotFoundError is raised when the bundled binary is absent."""
|
|
57
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
58
|
+
# uv_bin directory exists but the binary itself is missing
|
|
59
|
+
os.makedirs(os.path.join(tmp, "uv_bin"))
|
|
60
|
+
|
|
61
|
+
with (
|
|
62
|
+
patch.object(sys, "frozen", True, create=True),
|
|
63
|
+
patch.object(sys, "_MEIPASS", tmp, create=True),
|
|
64
|
+
patch("platform.system", return_value="Linux"),
|
|
65
|
+
):
|
|
66
|
+
with self.assertRaises(FileNotFoundError):
|
|
67
|
+
_find_uv_binary()
|
|
68
|
+
|
|
69
|
+
def test_frozen_missing_uv_bin_dir_raises(self):
|
|
70
|
+
"""FileNotFoundError is raised when the entire uv_bin directory is missing."""
|
|
71
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
72
|
+
with (
|
|
73
|
+
patch.object(sys, "frozen", True, create=True),
|
|
74
|
+
patch.object(sys, "_MEIPASS", tmp, create=True),
|
|
75
|
+
patch("platform.system", return_value="Linux"),
|
|
76
|
+
):
|
|
77
|
+
with self.assertRaises(FileNotFoundError):
|
|
78
|
+
_find_uv_binary()
|
|
79
|
+
|
|
80
|
+
# ------------------------------------------------------------------
|
|
81
|
+
# uv module branch
|
|
82
|
+
# ------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
def test_uv_module_used_when_available(self):
|
|
85
|
+
"""find_uv_bin() from the uv module is used when import succeeds."""
|
|
86
|
+
fake_module = MagicMock()
|
|
87
|
+
fake_module.find_uv_bin.return_value = "/usr/local/bin/uv"
|
|
88
|
+
|
|
89
|
+
# Ensure we are NOT in a frozen bundle.
|
|
90
|
+
with patch.object(sys, "frozen", False, create=True), patch.dict("sys.modules", {"uv": fake_module}):
|
|
91
|
+
result = _find_uv_binary()
|
|
92
|
+
|
|
93
|
+
self.assertEqual(result, "/usr/local/bin/uv")
|
|
94
|
+
fake_module.find_uv_bin.assert_called_once()
|
|
95
|
+
|
|
96
|
+
def test_uv_module_import_error_falls_through(self):
|
|
97
|
+
"""An ImportError from the uv module causes fallback to PATH lookup."""
|
|
98
|
+
with (
|
|
99
|
+
patch.object(sys, "frozen", False, create=True),
|
|
100
|
+
patch.dict("sys.modules", {"uv": None}),
|
|
101
|
+
patch("shutil.which", return_value="/opt/bin/uv"),
|
|
102
|
+
):
|
|
103
|
+
result = _find_uv_binary()
|
|
104
|
+
|
|
105
|
+
self.assertEqual(result, "/opt/bin/uv")
|
|
106
|
+
|
|
107
|
+
def test_uv_module_attribute_error_falls_through(self):
|
|
108
|
+
"""An AttributeError (missing find_uv_bin attr) causes fallback to PATH lookup."""
|
|
109
|
+
bad_module = MagicMock(spec=[]) # no find_uv_bin attribute
|
|
110
|
+
|
|
111
|
+
with (
|
|
112
|
+
patch.object(sys, "frozen", False, create=True),
|
|
113
|
+
patch.dict("sys.modules", {"uv": bad_module}),
|
|
114
|
+
patch("shutil.which", return_value="/opt/bin/uv"),
|
|
115
|
+
):
|
|
116
|
+
result = _find_uv_binary()
|
|
117
|
+
|
|
118
|
+
self.assertEqual(result, "/opt/bin/uv")
|
|
119
|
+
|
|
120
|
+
# ------------------------------------------------------------------
|
|
121
|
+
# PATH branch
|
|
122
|
+
# ------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
def test_path_lookup_used_as_last_resort(self):
|
|
125
|
+
"""shutil.which is used as the last fallback when the uv module is absent."""
|
|
126
|
+
with (
|
|
127
|
+
patch.object(sys, "frozen", False, create=True),
|
|
128
|
+
patch.dict("sys.modules", {"uv": None}),
|
|
129
|
+
patch("shutil.which", return_value="/usr/bin/uv") as mock_which,
|
|
130
|
+
):
|
|
131
|
+
result = _find_uv_binary()
|
|
132
|
+
|
|
133
|
+
self.assertEqual(result, "/usr/bin/uv")
|
|
134
|
+
mock_which.assert_called_once_with("uv")
|
|
135
|
+
|
|
136
|
+
def test_raises_when_nothing_found(self):
|
|
137
|
+
"""FileNotFoundError is raised when neither the module nor PATH provide uv."""
|
|
138
|
+
with (
|
|
139
|
+
patch.object(sys, "frozen", False, create=True),
|
|
140
|
+
patch.dict("sys.modules", {"uv": None}),
|
|
141
|
+
patch("shutil.which", return_value=None),
|
|
142
|
+
):
|
|
143
|
+
with self.assertRaises(FileNotFoundError) as ctx:
|
|
144
|
+
_find_uv_binary()
|
|
145
|
+
|
|
146
|
+
self.assertIn("uv binary not found", str(ctx.exception))
|
|
147
|
+
|
|
148
|
+
def test_error_message_mentions_path_and_module(self):
|
|
149
|
+
"""The FileNotFoundError message hints at both PATH and module installation."""
|
|
150
|
+
with (
|
|
151
|
+
patch.object(sys, "frozen", False, create=True),
|
|
152
|
+
patch.dict("sys.modules", {"uv": None}),
|
|
153
|
+
patch("shutil.which", return_value=None),
|
|
154
|
+
):
|
|
155
|
+
with self.assertRaises(FileNotFoundError) as ctx:
|
|
156
|
+
_find_uv_binary()
|
|
157
|
+
|
|
158
|
+
msg = str(ctx.exception)
|
|
159
|
+
self.assertIn("PATH", msg)
|
|
160
|
+
self.assertIn("module", msg)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
if __name__ == "__main__":
|
|
164
|
+
unittest.main()
|
nextmv-1.7.1/nextmv/__about__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "v1.7.1"
|