diff --git a/.github/workflows/vhs.yml b/.github/workflows/vhs.yml index 211bef0d..bc513fa3 100644 --- a/.github/workflows/vhs.yml +++ b/.github/workflows/vhs.yml @@ -1,8 +1,8 @@ name: vhs on: - push: - branches-ignore: - - main + # push: + # branches-ignore: + # - main workflow_dispatch: diff --git a/CHANGELOG.md b/CHANGELOG.md index 116966d5..634b29fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## Release 1.3.1 (10/29/23) +## Release 1.3.1 (10/30/23) ### Added diff --git a/docs/cli/demos/config.gif b/docs/cli/demos/config.gif index d62ab6ed..2bece596 100644 Binary files a/docs/cli/demos/config.gif and b/docs/cli/demos/config.gif differ diff --git a/docs/cli/demos/help.gif b/docs/cli/demos/help.gif index a20415d8..508d1a2f 100644 Binary files a/docs/cli/demos/help.gif and b/docs/cli/demos/help.gif differ diff --git a/docs/cli/demos/ssh.gif b/docs/cli/demos/ssh.gif index ddbda2f5..daa0dd5b 100644 Binary files a/docs/cli/demos/ssh.gif and b/docs/cli/demos/ssh.gif differ diff --git a/requirements.txt b/requirements.txt index c914296c..9d21a3db 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,7 @@ pillow >= 9.5.0 prettytable >= 3.8.0 py-cpuinfo >= 9.0.0 python-dotenv >= 1.0.0 +inquirerpy == 0.3.4 requests >= 2.31.0 tomli >= 2.0.1 tomlkit >= 0.12.1 diff --git a/runpod/cli/groups/project/commands.py b/runpod/cli/groups/project/commands.py index bcad4176..f234690a 100644 --- a/runpod/cli/groups/project/commands.py +++ b/runpod/cli/groups/project/commands.py @@ -3,8 +3,11 @@ ''' import os +import sys import click +from InquirerPy import prompt as cli_select +from runpod import get_user from .functions import ( create_new_project, launch_project, start_project_api, create_project_endpoint ) @@ -24,6 +27,12 @@ def project_cli(): @click.option('--init', '-i', 'init_current_dir', is_flag=True, default=False) def new_project_wizard(project_name, model_type, model_name, init_current_dir): """ Create a new project. """ + network_volumes = get_user()['networkVolumes'] + if len(network_volumes) == 0: + click.echo("You do not have any network volumes.") + click.echo("Please create a network volume (https://runpod.io/console/user/storage) and try again.") # pylint: disable=line-too-long + sys.exit(1) + click.echo("Creating a new project...") if init_current_dir: @@ -34,8 +43,24 @@ def new_project_wizard(project_name, model_type, model_name, init_current_dir): validate_project_name(project_name) - runpod_volume_id = click.prompt( - " > Enter a network storage ID (https://runpod.io/console/user/storage)", type=str) + def print_net_vol(vol): + return { + 'name':f"{vol['id']}: {vol['name']} ({vol['size']} GB, {vol['dataCenterId']})", + 'value':vol['id'] + } + + network_volumes = list(map(print_net_vol,network_volumes)) + questions = [ + { + 'type': 'rawlist', + 'name': 'volume-id', + 'qmark': '', + 'amark': '', + 'message': ' > Select a Network Volume:', + 'choices': network_volumes + } + ] + runpod_volume_id = cli_select(questions)['volume-id'] python_version = click.prompt( " > Select a Python version, or press enter to use the default", diff --git a/runpod/cli/groups/project/functions.py b/runpod/cli/groups/project/functions.py index e3e7fb72..b4a794e6 100644 --- a/runpod/cli/groups/project/functions.py +++ b/runpod/cli/groups/project/functions.py @@ -70,7 +70,7 @@ def create_new_project(project_name, runpod_volume_id, python_version, # pylint: project_table = table() project_table.add("uuid", project_uuid) project_table.add("name", project_name) - project_table.add("base_image", "runpod/base:0.1.0") + project_table.add("base_image", "runpod/base:0.2.1") project_table.add("gpu_types", [ "NVIDIA RTX A4000", "NVIDIA RTX A4500", "NVIDIA RTX A5000", "NVIDIA GeForce RTX 3090", "NVIDIA RTX A6000"]) diff --git a/tests/test_cli/test_cli_groups/test_project_commands.py b/tests/test_cli/test_cli_groups/test_project_commands.py index d5af3e83..63f4e8a8 100644 --- a/tests/test_cli/test_cli_groups/test_project_commands.py +++ b/tests/test_cli/test_cli_groups/test_project_commands.py @@ -16,6 +16,17 @@ class TestProjectCLI(unittest.TestCase): def setUp(self): self.runner = CliRunner() + def test_new_project_wizard_no_network_volumes(self): + ''' + Tests the new_project_wizard command with no network volumes. + ''' + with patch('runpod.cli.groups.project.commands.get_user') as mock_get_user: + mock_get_user.return_value = {'networkVolumes':[]} + + result = self.runner.invoke(new_project_wizard) + + self.assertEqual(result.exit_code, 1) + self.assertIn("You do not have any network volumes.", result.output) def test_new_project_wizard_success(self): ''' @@ -23,9 +34,12 @@ def test_new_project_wizard_success(self): ''' with patch('click.prompt') as mock_prompt, \ patch('click.confirm', return_value=True) as mock_confirm, \ - patch('runpod.cli.groups.project.commands.create_new_project') as mock_create: - - mock_prompt.side_effect = ['TestProject', 'XYZ_VOLUME', '3.10'] + patch('runpod.cli.groups.project.commands.create_new_project') as mock_create, \ + patch('runpod.cli.groups.project.commands.get_user') as mock_get_user, \ + patch('runpod.cli.groups.project.commands.cli_select') as mock_select: + mock_get_user.return_value = {'networkVolumes':[{ 'id': 'XYZ_VOLUME', 'name': 'XYZ_VOLUME', 'size': 100, 'dataCenterId': 'XYZ' }]} # pylint: disable=line-too-long + mock_prompt.side_effect = ['TestProject', '3.10'] + mock_select.return_value = {'volume-id': 'XYZ_VOLUME'} result = self.runner.invoke(new_project_wizard, ['--type', 'llama2', '--model', 'meta-llama/Llama-2-7b']) # pylint: disable=line-too-long @@ -43,8 +57,11 @@ def test_new_project_wizard_success_init_current_dir(self): with patch('click.prompt') as mock_prompt, \ patch('click.confirm', return_value=True) as mock_confirm, \ patch('runpod.cli.groups.project.commands.create_new_project') as mock_create, \ + patch('runpod.cli.groups.project.commands.get_user') as mock_get_user, \ + patch('runpod.cli.groups.project.commands.cli_select') as mock_select, \ patch('os.getcwd') as mock_getcwd: - + mock_get_user.return_value = {'networkVolumes':[{ 'id': 'XYZ_VOLUME', 'name': 'XYZ_VOLUME', 'size': 100, 'dataCenterId': 'XYZ' }]} # pylint: disable=line-too-long + mock_select.return_value = {'volume-id': 'XYZ_VOLUME'} mock_prompt.side_effect = ['XYZ_VOLUME', '3.10'] self.runner.invoke(new_project_wizard, ['--init']) @@ -56,7 +73,10 @@ def test_new_project_wizard_invalid_name(self): ''' Tests the new_project_wizard command with an invalid project name. ''' - result = self.runner.invoke(new_project_wizard, ['--name', 'Invalid/Name']) + with patch('runpod.cli.groups.project.commands.get_user') as mock_get_user: + mock_get_user.return_value = {'networkVolumes':["XYZ_VOLUME"]} + + result = self.runner.invoke(new_project_wizard, ['--name', 'Invalid/Name']) self.assertEqual(result.exit_code, 2) self.assertIn("Project name contains an invalid character", result.output)