Repository: mathieu-lemay/pipeline-runner (language: python)

Issue:
Add typing + mypy



Discussion / hints:

Fix patch:
diff --git a/pipeline_runner/models.py b/pipeline_runner/models.py
index 0efd338..fef0562 100644
--- a/pipeline_runner/models.py
+++ b/pipeline_runner/models.py
@@ -197,15 +197,31 @@ def as_int(self) -> int:
 
 class Pipe(BaseModel):
     pipe: str
-    variables: dict[str, str] = Field(default_factory=dict)
+    variables: dict[str, str | list[str]] = Field(default_factory=dict)
 
     def as_cmd(self) -> str:
         cmd = "docker run --rm"
-        if self.variables:
-            cmd += " " + " ".join(f'-e {k}="{self._escape_value(v)}"' for k, v in self.variables.items())
+
+        variables = self.expand_variables()
+        if variables:
+            cmd += " " + " ".join(f'-e {k}="{self._escape_value(v)}"' for k, v in variables.items())
 
         return f"{cmd} {self.get_image()}"
 
+    def expand_variables(self) -> dict[str, str]:
+        expanded_variables = {}
+
+        for key, value in self.variables.items():
+            if isinstance(value, list):
+                for i, v in enumerate(value):
+                    expanded_variables[f"{key}_{i}"] = v
+
+                expanded_variables[f"{key}_COUNT"] = str(len(value))
+            else:
+                expanded_variables[key] = value
+
+        return expanded_variables
+
     @staticmethod
     def _escape_value(v: str) -> str:
         return v.replace('"', '\\"')


Test patch:
diff --git a/tests/unit/test_models.py b/tests/unit/test_models.py
index 694877b..3604849 100644
--- a/tests/unit/test_models.py
+++ b/tests/unit/test_models.py
@@ -81,18 +81,6 @@ def test_pipeline_result_ok_returns_false_if_exit_code_is_not_zero() -> None:
         assert res.ok is False
 
 
-def test_pipe_get_image_returns_its_name_as_docker_image() -> None:
-    p = Pipe(pipe="foo/bar:1.2.3", variables={})
-
-    assert p.get_image() == "foo/bar:1.2.3"
-
-
-def test_pipe_get_image_returns_the_right_docker_image_if_pipe_is_from_atlassian() -> None:
-    p = Pipe(pipe="atlassian/bar:1.2.3", variables={})
-
-    assert p.get_image() == "bitbucketpipelines/bar:1.2.3"
-
-
 def test_pipe_as_cmd_transforms_the_pipe_into_a_docker_command() -> None:
     p = Pipe(
         pipe="atlassian/foo:1.2.3",
@@ -100,16 +88,52 @@ def test_pipe_as_cmd_transforms_the_pipe_into_a_docker_command() -> None:
             "FOO": "BAR",
             "BAZ": '[{"some": "json with \'single-quotes\'", "more": "json with line\nbreak"}]',
             "ENV": "${SOME_ENVVAR}",
+            "EXTRA_ARGS": ["a", "b"],
         },
     )
 
     assert p.as_cmd() == (
-        'docker run --rm -e FOO="BAR" '
+        "docker run --rm "
+        '-e FOO="BAR" '
         '-e BAZ="[{\\"some\\": \\"json with \'single-quotes\'\\", \\"more\\": \\"json with line\nbreak\\"}]" '
-        '-e ENV="${SOME_ENVVAR}" bitbucketpipelines/foo:1.2.3'
+        '-e ENV="${SOME_ENVVAR}" '
+        '-e EXTRA_ARGS_0="a" '
+        '-e EXTRA_ARGS_1="b" '
+        '-e EXTRA_ARGS_COUNT="2" '
+        "bitbucketpipelines/foo:1.2.3"
     )
 
 
+def test_pipe_expand_variables_expands_list_variables() -> None:
+    p = Pipe(
+        pipe="atlassian/foo:1.2.3",
+        variables={
+            "STRING_VARIABLE": "some-string,with-commas",
+            "LIST_VARIABLE": ["list", "of", "values"],
+        },
+    )
+
+    assert p.expand_variables() == {
+        "STRING_VARIABLE": "some-string,with-commas",
+        "LIST_VARIABLE_0": "list",
+        "LIST_VARIABLE_1": "of",
+        "LIST_VARIABLE_2": "values",
+        "LIST_VARIABLE_COUNT": "3",
+    }
+
+
+def test_pipe_get_image_returns_its_name_as_docker_image() -> None:
+    p = Pipe(pipe="foo/bar:1.2.3", variables={})
+
+    assert p.get_image() == "foo/bar:1.2.3"
+
+
+def test_pipe_get_image_returns_the_right_docker_image_if_pipe_is_from_atlassian() -> None:
+    p = Pipe(pipe="atlassian/bar:1.2.3", variables={})
+
+    assert p.get_image() == "bitbucketpipelines/bar:1.2.3"
+
+
 def test_project_metadata_load_from_file_generates_new_metadata_if_not_exists(
     user_data_directory: Path, faker: Faker
 ) -> None: