Skip to content

Commit f711859

Browse files
committed
fix: make the input location optional in the run command
The run command always required an input location, so pipelines that do not take any input could only be started by passing an empty string. The input location is now optional. The trailing positional value is only treated as an input when it is not an available pipeline name, and the input options are only built when a location was provided. Signed-off-by: NoiceHax <yashasprakash021@gmail.com>
1 parent 4186863 commit f711859

3 files changed

Lines changed: 51 additions & 8 deletions

File tree

docs/command-line-interface.rst

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -759,8 +759,8 @@ Optional arguments:
759759

760760
.. _cli_run:
761761

762-
`$ run PIPELINE_NAME [PIPELINE_NAME ...] input_location`
763-
--------------------------------------------------------
762+
`$ run PIPELINE_NAME [PIPELINE_NAME ...] [input_location]`
763+
----------------------------------------------------------
764764

765765
A ``run`` command is available for executing pipelines and printing the results
766766
without providing any configuration. This can be useful for running a pipeline to get
@@ -770,6 +770,9 @@ review the results.
770770
.. tip:: You can run multiple pipelines by providing their names, space-separated,
771771
such as `pipeline1 pipeline2`.
772772

773+
The ``input_location`` is optional, so pipelines that do not take any input can be
774+
run without it.
775+
773776
Optional arguments:
774777

775778
- ``--project PROJECT_NAME``: Provide a project name; otherwise, a random value is

scanpipe/management/commands/run.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from collections import defaultdict
2424
from pathlib import Path
2525

26+
from django.apps import apps
2627
from django.core.management import call_command
2728
from django.core.management.base import BaseCommand
2829
from django.core.management.base import CommandError
@@ -31,6 +32,8 @@
3132
from scanpipe.management.commands import extract_tag_from_input_file
3233
from scanpipe.pipes.fetch import SCHEME_TO_FETCHER_MAPPING
3334

35+
scanpipe_app = apps.get_app_config("scanpipe")
36+
3437

3538
class Command(BaseCommand):
3639
help = "Run a pipeline and print the results."
@@ -50,9 +53,11 @@ def add_arguments(self, parser):
5053
)
5154
parser.add_argument(
5255
"input_location",
56+
nargs="?",
5357
help=(
5458
"Input location: file, directory, and URL supported."
55-
'Multiple values can be provided using the "input1,input2" syntax.'
59+
'Multiple values can be provided using the "input1,input2" syntax. '
60+
"Optional, as some pipelines do not require any input."
5661
),
5762
)
5863
parser.add_argument("--project", required=False, help="Project name.")
@@ -64,8 +69,14 @@ def add_arguments(self, parser):
6469
)
6570

6671
def handle(self, *args, **options):
72+
# The ``input_location`` positional is declared for the command usage, but
73+
# argparse collects every positional value in ``pipelines``. The trailing
74+
# value is the input location unless it is an available pipeline name.
6775
pipelines = options["pipelines"]
68-
input_location = options["input_location"]
76+
input_location = None
77+
if len(pipelines) > 1 and not self.is_pipeline_name(pipelines[-1]):
78+
input_location = pipelines.pop()
79+
6980
output_format = options["format"]
7081
# Generate a random name for the project if not provided
7182
project_name = options["project"] or get_random_string(10)
@@ -74,8 +85,9 @@ def handle(self, *args, **options):
7485
"pipeline": pipelines,
7586
"execute": True,
7687
"verbosity": 0,
77-
**self.get_input_options(input_location),
7888
}
89+
if input_location:
90+
create_project_options.update(self.get_input_options(input_location))
7991

8092
# Run the database migrations in case the database is not created or outdated.
8193
call_command("migrate", verbosity=0, interactive=False)
@@ -84,6 +96,13 @@ def handle(self, *args, **options):
8496
# Print the results for the specified format on stdout
8597
call_command("output", project=project_name, format=[output_format], print=True)
8698

99+
@staticmethod
100+
def is_pipeline_name(value):
101+
"""Return True when the provided ``value`` is an available pipeline name."""
102+
pipeline_name, _ = scanpipe_app.extract_group_from_pipeline(value)
103+
pipeline_name = scanpipe_app.get_new_pipeline_name(pipeline_name)
104+
return pipeline_name in scanpipe_app.pipelines
105+
87106
@staticmethod
88107
def get_input_options(input_location):
89108
"""

scanpipe/tests/test_commands.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,9 +1007,7 @@ def test_scanpipe_management_command_create_user_admin_superuser(self):
10071007
self.assertTrue(user.is_superuser)
10081008

10091009
def test_scanpipe_management_command_run(self):
1010-
expected = (
1011-
"Error: the following arguments are required: PIPELINE_NAME, input_location"
1012-
)
1010+
expected = "Error: the following arguments are required: PIPELINE_NAME"
10131011
with self.assertRaisesMessage(CommandError, expected):
10141012
call_command("run")
10151013

@@ -1047,6 +1045,29 @@ def test_scanpipe_management_command_run(self):
10471045
self.assertEqual("do_nothing", runs[1]["pipeline_name"])
10481046
self.assertEqual(["Group1", "Group2"], runs[1]["selected_groups"])
10491047

1048+
def test_scanpipe_management_command_run_without_input_location(self):
1049+
out = StringIO()
1050+
with redirect_stdout(out):
1051+
call_command("run", "do_nothing")
1052+
1053+
json_data = json.loads(out.getvalue())
1054+
self.assertEqual([], json_data["files"])
1055+
runs = json_data["headers"][0]["runs"]
1056+
self.assertEqual(1, len(runs))
1057+
self.assertEqual("do_nothing", runs[0]["pipeline_name"])
1058+
self.assertEqual("success", runs[0]["status"])
1059+
1060+
# A trailing pipeline name is not mistaken for an input location
1061+
out = StringIO()
1062+
with redirect_stdout(out):
1063+
call_command("run", "do_nothing", "profile_step")
1064+
1065+
json_data = json.loads(out.getvalue())
1066+
runs = json_data["headers"][0]["runs"]
1067+
self.assertEqual(2, len(runs))
1068+
self.assertEqual("do_nothing", runs[0]["pipeline_name"])
1069+
self.assertEqual("profile_step", runs[1]["pipeline_name"])
1070+
10501071
@mock.patch("scanpipe.pipes.fetch.is_safe_url", return_value=True)
10511072
@mock.patch("scanpipe.pipes.fetch.check_url")
10521073
@mock.patch("requests.sessions.Session.get")

0 commit comments

Comments
 (0)