This is an automated email from the ASF dual-hosted git repository. kuanhsun pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/submarine.git
The following commit(s) were added to refs/heads/master by this push: new 5eb043f Submarine 1095. Incorporate Click CLI in SDK 5eb043f is described below commit 5eb043fa9d230cae363ade6fc2859b5e9507afee Author: atosystem <atosys...@hotmail.com> AuthorDate: Tue Dec 7 12:38:05 2021 +0800 Submarine 1095. Incorporate Click CLI in SDK ### What is this PR for? Introduce click module to submarine SDK. Allowing the followings commands ``` submarine sandbox start # create submarine sandbox submarine sandbox start --version 0.6.0 submarine sandbox delete # delete submarine sandbox submarine get experiment <id> submarine get notebook <id> submarine get environment <id> submarine list experiment submarine list notebook submarine list environment submarine delete experiment <id> submarine delete notebook <id> submarine delete environment <id> ``` ### What type of PR is it? [Feature] ### Todos * [ ] - Connect backend API * [ ] - Write docs for CLI ### What is the Jira issue? [SUBMARINE-1095](https://issues.apache.org/jira/browse/SUBMARINE-1095) ### How should this be tested? python unit tests are implemented for command line testing ### Screenshots (if appropriate) ### Questions: * Do the license files need updating? No * Are there breaking changes for older versions? No * Does this need new documentation? Yes Author: atosystem <atosys...@hotmail.com> Signed-off-by: kuanhsun <kuanh...@apache.org> Closes #817 from atosystem/SUBMARINE-1095 and squashes the following commits: 8418f3a2 [atosystem] SUBMARINE-1095. Remove comments 18eb2a12 [atosystem] SUBMARINE-1095. Add: basic cli stucture test a27af6ab [atosystem] SUBMARINE-1095. autoformat2 b1a48755 [atosystem] SUBMARINE-1095. autoformat 598b3749 [atosystem] SUBMARINE-1095. Add: basic cli structure --- submarine-sdk/pysubmarine/setup.py | 6 ++ .../pysubmarine/submarine/cli/__init__.py | 31 ++++++++++ .../submarine/cli/environment/__init__.py | 22 +++++++ .../submarine/cli/environment/command.py | 38 ++++++++++++ .../submarine/cli/experiment/__init__.py | 22 +++++++ .../submarine/cli/experiment/command.py | 38 ++++++++++++ submarine-sdk/pysubmarine/submarine/cli/main.py | 67 ++++++++++++++++++++++ .../pysubmarine/submarine/cli/notebook/__init__.py | 22 +++++++ .../pysubmarine/submarine/cli/notebook/command.py | 38 ++++++++++++ .../pysubmarine/submarine/cli/sandbox/__init__.py | 21 +++++++ .../pysubmarine/submarine/cli/sandbox/command.py | 31 ++++++++++ .../pysubmarine/tests/cli/test_environment.py | 41 +++++++++++++ .../pysubmarine/tests/cli/test_experiment.py | 41 +++++++++++++ .../pysubmarine/tests/cli/test_notebook.py | 41 +++++++++++++ .../pysubmarine/tests/cli/test_sandbox.py | 39 +++++++++++++ 15 files changed, 498 insertions(+) diff --git a/submarine-sdk/pysubmarine/setup.py b/submarine-sdk/pysubmarine/setup.py index e1291ac..1e72068 100644 --- a/submarine-sdk/pysubmarine/setup.py +++ b/submarine-sdk/pysubmarine/setup.py @@ -40,6 +40,7 @@ setup( "pyarrow==0.17.0", "mlflow>=1.15.0", "boto3>=1.17.58", + "click==8.0.3", ], extras_require={ "tf": ["tensorflow==1.15.0"], @@ -58,6 +59,11 @@ setup( "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", ], + entry_points={ + "console_scripts": [ + "submarine = submarine.cli.main:entry_point", + ], + }, license="Apache License, Version 2.0", maintainer="Apache Submarine Community", maintainer_email="dev@submarine.apache.org", diff --git a/submarine-sdk/pysubmarine/submarine/cli/__init__.py b/submarine-sdk/pysubmarine/submarine/cli/__init__.py new file mode 100644 index 0000000..a4665f6 --- /dev/null +++ b/submarine-sdk/pysubmarine/submarine/cli/__init__.py @@ -0,0 +1,31 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# coding: utf-8 + +# flake8: noqa +""" + Submarine Command Line Interface + + The Submarine CLI allows you to interact with submarine backend via command line. + + The version of the OpenAPI document: 0.7.0-SNAPSHOT + Contact: dev@submarine.apache.org + Generated by: https://openapi-generator.tech +""" + +from __future__ import absolute_import + +__version__ = "0.7.0-SNAPSHOT" diff --git a/submarine-sdk/pysubmarine/submarine/cli/environment/__init__.py b/submarine-sdk/pysubmarine/submarine/cli/environment/__init__.py new file mode 100644 index 0000000..38a7a27 --- /dev/null +++ b/submarine-sdk/pysubmarine/submarine/cli/environment/__init__.py @@ -0,0 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from submarine.cli.environment.command import delete_environment, get_environment, list_environment + +__all__ = [ + "get_environment", + "list_environment", + "delete_environment", +] diff --git a/submarine-sdk/pysubmarine/submarine/cli/environment/command.py b/submarine-sdk/pysubmarine/submarine/cli/environment/command.py new file mode 100644 index 0000000..85c620f --- /dev/null +++ b/submarine-sdk/pysubmarine/submarine/cli/environment/command.py @@ -0,0 +1,38 @@ +""" + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +""" + +import click + + +@click.command("environment") +def list_environment(): + """List environment""" + click.echo("list environment!") + + +@click.command("environment") +@click.argument("id") +def get_environment(id): + """Get environment""" + click.echo("get environment! id={}".format(id)) + + +@click.command("environment") +@click.argument("id") +def delete_environment(id): + """Delete environment""" + click.echo("delete environment! id={}".format(id)) diff --git a/submarine-sdk/pysubmarine/submarine/cli/experiment/__init__.py b/submarine-sdk/pysubmarine/submarine/cli/experiment/__init__.py new file mode 100644 index 0000000..1e6253c --- /dev/null +++ b/submarine-sdk/pysubmarine/submarine/cli/experiment/__init__.py @@ -0,0 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from submarine.cli.experiment.command import delete_experiment, get_experiment, list_experiment + +__all__ = [ + "get_experiment", + "list_experiment", + "delete_experiment", +] diff --git a/submarine-sdk/pysubmarine/submarine/cli/experiment/command.py b/submarine-sdk/pysubmarine/submarine/cli/experiment/command.py new file mode 100644 index 0000000..6f2c182 --- /dev/null +++ b/submarine-sdk/pysubmarine/submarine/cli/experiment/command.py @@ -0,0 +1,38 @@ +""" + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +""" + +import click + + +@click.command("experiment") +def list_experiment(): + """List experiments""" + click.echo("list experiment!") + + +@click.command("experiment") +@click.argument("id") +def get_experiment(id): + """Get experiments""" + click.echo("get experiment! id={}".format(id)) + + +@click.command("experiment") +@click.argument("id") +def delete_experiment(id): + """Delete experiment""" + click.echo("delete experiment! id={}".format(id)) diff --git a/submarine-sdk/pysubmarine/submarine/cli/main.py b/submarine-sdk/pysubmarine/submarine/cli/main.py new file mode 100644 index 0000000..b8a699b --- /dev/null +++ b/submarine-sdk/pysubmarine/submarine/cli/main.py @@ -0,0 +1,67 @@ +""" + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +""" + +import click + +from submarine.cli.environment import command as environment_cmd +from submarine.cli.experiment import command as experiment_cmd +from submarine.cli.notebook import command as notebook_cmd +from submarine.cli.sandbox import command as sandbox_cmd + + +@click.group() +def entry_point(): + """Submarine CLI Tool!""" + pass + + +@entry_point.group("list") +def cmdgrp_list(): + pass + + +@entry_point.group("get") +def cmdgrp_get(): + pass + + +@entry_point.group("delete") +def cmdgrp_delete(): + pass + + +@entry_point.group("sandbox") +def cmdgrp_sandbox(): + pass + + +# experiment +cmdgrp_list.add_command(experiment_cmd.list_experiment) +cmdgrp_get.add_command(experiment_cmd.get_experiment) +cmdgrp_delete.add_command(experiment_cmd.delete_experiment) +# notebook +cmdgrp_list.add_command(notebook_cmd.list_notebook) +cmdgrp_get.add_command(notebook_cmd.get_notebook) +cmdgrp_delete.add_command(notebook_cmd.delete_notebook) +# environment +cmdgrp_list.add_command(environment_cmd.list_environment) +cmdgrp_get.add_command(environment_cmd.get_environment) +cmdgrp_delete.add_command(environment_cmd.delete_environment) + +# sandbox +cmdgrp_sandbox.add_command(sandbox_cmd.start_sandbox) +cmdgrp_sandbox.add_command(sandbox_cmd.delete_sandbox) diff --git a/submarine-sdk/pysubmarine/submarine/cli/notebook/__init__.py b/submarine-sdk/pysubmarine/submarine/cli/notebook/__init__.py new file mode 100644 index 0000000..0dfd20f --- /dev/null +++ b/submarine-sdk/pysubmarine/submarine/cli/notebook/__init__.py @@ -0,0 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from submarine.cli.notebook.command import delete_notebook, get_notebook, list_notebook + +__all__ = [ + "get_notebook", + "list_notebook", + "delete_notebook", +] diff --git a/submarine-sdk/pysubmarine/submarine/cli/notebook/command.py b/submarine-sdk/pysubmarine/submarine/cli/notebook/command.py new file mode 100644 index 0000000..8deb3cb --- /dev/null +++ b/submarine-sdk/pysubmarine/submarine/cli/notebook/command.py @@ -0,0 +1,38 @@ +""" + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +""" + +import click + + +@click.command("notebook") +def list_notebook(): + """List notebooks""" + click.echo("list notebook!") + + +@click.command("notebook") +@click.argument("id") +def get_notebook(id): + """Get notebooks""" + click.echo("get notebook! id={}".format(id)) + + +@click.command("notebook") +@click.argument("id") +def delete_notebook(id): + """Delete notebook""" + click.echo("delete notebook! id={}".format(id)) diff --git a/submarine-sdk/pysubmarine/submarine/cli/sandbox/__init__.py b/submarine-sdk/pysubmarine/submarine/cli/sandbox/__init__.py new file mode 100644 index 0000000..ee8f305 --- /dev/null +++ b/submarine-sdk/pysubmarine/submarine/cli/sandbox/__init__.py @@ -0,0 +1,21 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from submarine.cli.sandbox.command import delete_sandbox, start_sandbox + +__all__ = [ + "start_sandbox", + "delete_sandbox", +] diff --git a/submarine-sdk/pysubmarine/submarine/cli/sandbox/command.py b/submarine-sdk/pysubmarine/submarine/cli/sandbox/command.py new file mode 100644 index 0000000..fecaf1a --- /dev/null +++ b/submarine-sdk/pysubmarine/submarine/cli/sandbox/command.py @@ -0,0 +1,31 @@ +""" + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +""" + +import click + + +@click.command("start") +@click.option("-v", "--version", "version", help="Specify sandbox version", default="0.6.0") +def start_sandbox(version): + """Start sandbox""" + click.echo("start sandbox! version={}".format(version)) + + +@click.command("delete") +def delete_sandbox(): + """Delete sandbox""" + click.echo("delete sandbox!") diff --git a/submarine-sdk/pysubmarine/tests/cli/test_environment.py b/submarine-sdk/pysubmarine/tests/cli/test_environment.py new file mode 100644 index 0000000..7240bf1 --- /dev/null +++ b/submarine-sdk/pysubmarine/tests/cli/test_environment.py @@ -0,0 +1,41 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from click.testing import CliRunner + +from submarine.cli import main + + +def test_list_environment(): + runner = CliRunner() + result = runner.invoke(main.entry_point, ["list", "environment"]) + assert result.exit_code == 0 + assert "list environment!" in result.output + + +def test_get_environment(): + mock_environment_id = "0" + runner = CliRunner() + result = runner.invoke(main.entry_point, ["get", "environment", mock_environment_id]) + assert result.exit_code == 0 + assert "get environment! id={}".format(mock_environment_id) in result.output + + +def test_delete_environment(): + mock_environment_id = "0" + runner = CliRunner() + result = runner.invoke(main.entry_point, ["delete", "environment", mock_environment_id]) + assert result.exit_code == 0 + assert "delete environment! id={}".format(mock_environment_id) in result.output diff --git a/submarine-sdk/pysubmarine/tests/cli/test_experiment.py b/submarine-sdk/pysubmarine/tests/cli/test_experiment.py new file mode 100644 index 0000000..8d2ff44 --- /dev/null +++ b/submarine-sdk/pysubmarine/tests/cli/test_experiment.py @@ -0,0 +1,41 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from click.testing import CliRunner + +from submarine.cli import main + + +def test_list_experiment(): + runner = CliRunner() + result = runner.invoke(main.entry_point, ["list", "experiment"]) + assert result.exit_code == 0 + assert "list experiment!" in result.output + + +def test_get_experiment(): + mock_experiment_id = "0" + runner = CliRunner() + result = runner.invoke(main.entry_point, ["get", "experiment", mock_experiment_id]) + assert result.exit_code == 0 + assert "get experiment! id={}".format(mock_experiment_id) in result.output + + +def test_delete_experiment(): + mock_experiment_id = "0" + runner = CliRunner() + result = runner.invoke(main.entry_point, ["delete", "experiment", mock_experiment_id]) + assert result.exit_code == 0 + assert "delete experiment! id={}".format(mock_experiment_id) in result.output diff --git a/submarine-sdk/pysubmarine/tests/cli/test_notebook.py b/submarine-sdk/pysubmarine/tests/cli/test_notebook.py new file mode 100644 index 0000000..83bb47a --- /dev/null +++ b/submarine-sdk/pysubmarine/tests/cli/test_notebook.py @@ -0,0 +1,41 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from click.testing import CliRunner + +from submarine.cli import main + + +def test_list_notebook(): + runner = CliRunner() + result = runner.invoke(main.entry_point, ["list", "notebook"]) + assert result.exit_code == 0 + assert "list notebook!" in result.output + + +def test_get_notebook(): + mock_notebook_id = "0" + runner = CliRunner() + result = runner.invoke(main.entry_point, ["get", "notebook", mock_notebook_id]) + assert result.exit_code == 0 + assert "get notebook! id={}".format(mock_notebook_id) in result.output + + +def test_delete_notebook(): + mock_notebook_id = "0" + runner = CliRunner() + result = runner.invoke(main.entry_point, ["delete", "notebook", mock_notebook_id]) + assert result.exit_code == 0 + assert "delete notebook! id={}".format(mock_notebook_id) in result.output diff --git a/submarine-sdk/pysubmarine/tests/cli/test_sandbox.py b/submarine-sdk/pysubmarine/tests/cli/test_sandbox.py new file mode 100644 index 0000000..fb8d9ee --- /dev/null +++ b/submarine-sdk/pysubmarine/tests/cli/test_sandbox.py @@ -0,0 +1,39 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from click.testing import CliRunner + +from submarine.cli import main + + +def test_start_sandbox(): + default_version = "0.6.0" + mock_version = "0.0.0" + runner = CliRunner() + result = runner.invoke(main.entry_point, ["sandbox", "start"]) + assert result.exit_code == 0 + assert "start sandbox! version={}".format(default_version) in result.output + + runner = CliRunner() + result = runner.invoke(main.entry_point, ["sandbox", "start", "-v", mock_version]) + assert result.exit_code == 0 + assert "start sandbox! version={}".format(mock_version) in result.output + + +def test_delete_sandbox(): + runner = CliRunner() + result = runner.invoke(main.entry_point, ["sandbox", "delete"]) + assert result.exit_code == 0 + assert "delete sandbox!" in result.output --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@submarine.apache.org For additional commands, e-mail: dev-h...@submarine.apache.org