pypeline-runner 1.11.0__py3-none-any.whl → 1.13.0__py3-none-any.whl
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.
- pypeline/__init__.py +1 -1
- pypeline/domain/config.py +14 -1
- pypeline/domain/execution_context.py +12 -2
- pypeline/domain/project_slurper.py +4 -0
- pypeline/inputs_parser.py +88 -0
- pypeline/kickstart/templates/project/pypeline.yaml +6 -0
- pypeline/main.py +16 -2
- pypeline/steps/env_setup_script.py +4 -0
- pypeline/steps/scoop_install.py +8 -3
- {pypeline_runner-1.11.0.dist-info → pypeline_runner-1.13.0.dist-info}/METADATA +1 -1
- {pypeline_runner-1.11.0.dist-info → pypeline_runner-1.13.0.dist-info}/RECORD +14 -13
- {pypeline_runner-1.11.0.dist-info → pypeline_runner-1.13.0.dist-info}/LICENSE +0 -0
- {pypeline_runner-1.11.0.dist-info → pypeline_runner-1.13.0.dist-info}/WHEEL +0 -0
- {pypeline_runner-1.11.0.dist-info → pypeline_runner-1.13.0.dist-info}/entry_points.txt +0 -0
pypeline/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.
|
|
1
|
+
__version__ = "1.13.0"
|
pypeline/domain/config.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
from typing import Any, Dict, Optional
|
|
3
|
+
from typing import Any, Dict, Literal, Optional
|
|
4
4
|
|
|
5
5
|
import yaml
|
|
6
6
|
from mashumaro import DataClassDictMixin
|
|
@@ -10,10 +10,23 @@ from yaml.scanner import ScannerError
|
|
|
10
10
|
|
|
11
11
|
from .pipeline import PipelineConfig
|
|
12
12
|
|
|
13
|
+
InputType = Literal["string", "integer", "boolean"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class ProjectInput(DataClassDictMixin):
|
|
18
|
+
"""Represents a single input parameter for a pipeline step similar to GitHub workflows inputs."""
|
|
19
|
+
|
|
20
|
+
type: InputType
|
|
21
|
+
description: Optional[str] = None
|
|
22
|
+
default: Optional[Any] = None
|
|
23
|
+
required: bool = False
|
|
24
|
+
|
|
13
25
|
|
|
14
26
|
@dataclass
|
|
15
27
|
class ProjectConfig(DataClassDictMixin):
|
|
16
28
|
pipeline: PipelineConfig
|
|
29
|
+
inputs: Optional[Dict[str, ProjectInput]] = None
|
|
17
30
|
# This field is intended to keep track of where configuration was loaded from and
|
|
18
31
|
# it is automatically added when configuration is loaded from file
|
|
19
32
|
file: Optional[Path] = None
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from dataclasses import dataclass, field
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import List, Optional
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
5
|
|
|
6
6
|
from py_app_dev.core.data_registry import DataRegistry
|
|
7
7
|
from py_app_dev.core.subprocess import SubprocessExecutor
|
|
@@ -16,13 +16,23 @@ class ExecutionContext:
|
|
|
16
16
|
install_dirs: List[Path] = field(default_factory=list)
|
|
17
17
|
# Data registry to exchange data of any type
|
|
18
18
|
data_registry: DataRegistry = field(default_factory=DataRegistry)
|
|
19
|
+
# Inputs provided for the pipeline run
|
|
20
|
+
inputs: Dict[str, Any] = field(default_factory=dict)
|
|
21
|
+
# Environment variables to be passed to the subprocesses
|
|
22
|
+
env_vars: Dict[str, Any] = field(default_factory=dict)
|
|
23
|
+
|
|
24
|
+
def get_input(self, name: str) -> Optional[Any]:
|
|
25
|
+
return self.inputs.get(name, None)
|
|
19
26
|
|
|
20
27
|
def add_install_dirs(self, install_dirs: List[Path]) -> None:
|
|
21
28
|
self.install_dirs.extend(install_dirs)
|
|
22
29
|
|
|
30
|
+
def add_env_vars(self, env_vars: Dict[str, Any]) -> None:
|
|
31
|
+
self.env_vars.update(env_vars)
|
|
32
|
+
|
|
23
33
|
def create_process_executor(self, command: List[str | Path], cwd: Optional[Path] = None) -> SubprocessExecutor:
|
|
24
|
-
# Add the install directories to the PATH
|
|
25
34
|
env = os.environ.copy()
|
|
35
|
+
env.update(self.env_vars)
|
|
26
36
|
env["PATH"] = os.pathsep.join([path.absolute().as_posix() for path in self.install_dirs] + [env["PATH"]])
|
|
27
37
|
# When started from a windows shell (e.g. cmd on Jenkins) the shell parameter must be set to True
|
|
28
38
|
shell = True if os.name == "nt" else False
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import distutils.util
|
|
3
|
+
from typing import Any, Dict, List
|
|
4
|
+
|
|
5
|
+
from py_app_dev.core.exceptions import UserNotificationException
|
|
6
|
+
|
|
7
|
+
from .domain.config import InputType, ProjectInput
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _map_type_for_argparse(input_type: InputType) -> Any:
|
|
11
|
+
if input_type == "string":
|
|
12
|
+
return str
|
|
13
|
+
elif input_type == "integer":
|
|
14
|
+
return int
|
|
15
|
+
elif input_type == "boolean":
|
|
16
|
+
return lambda x: bool(distutils.util.strtobool(x))
|
|
17
|
+
else:
|
|
18
|
+
raise ValueError(f"Unsupported input type specified: {input_type}")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def create_argument_parser_from_definitions(
|
|
22
|
+
input_definitions: Dict[str, ProjectInput],
|
|
23
|
+
description: str = "Pypeline inputs parser.",
|
|
24
|
+
) -> argparse.ArgumentParser:
|
|
25
|
+
"""Creates and configures an ArgumentParser based on input definitions."""
|
|
26
|
+
parser = argparse.ArgumentParser(
|
|
27
|
+
description=description,
|
|
28
|
+
exit_on_error=False,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
for name, definition in input_definitions.items():
|
|
32
|
+
arg_type = _map_type_for_argparse(definition.type)
|
|
33
|
+
|
|
34
|
+
help_text = definition.description or f"Input parameter '{name}'."
|
|
35
|
+
help_text += f" (Type: {definition.type}"
|
|
36
|
+
if definition.default is not None:
|
|
37
|
+
help_text += f", Default: {definition.default}"
|
|
38
|
+
help_text += ")"
|
|
39
|
+
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
f"--{name}",
|
|
42
|
+
dest=name, # Attribute name in the parsed namespace
|
|
43
|
+
help=help_text,
|
|
44
|
+
type=arg_type, # Type conversion function
|
|
45
|
+
required=definition.required,
|
|
46
|
+
default=definition.default,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
return parser
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class InputsParser:
|
|
53
|
+
"""Parses input arguments based on definitions using argparse."""
|
|
54
|
+
|
|
55
|
+
def __init__(self, parser: argparse.ArgumentParser) -> None:
|
|
56
|
+
self.parser = parser
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def from_inputs_definitions(
|
|
60
|
+
cls,
|
|
61
|
+
input_definitions: Dict[str, ProjectInput],
|
|
62
|
+
description: str = "Pypeline inputs parser.",
|
|
63
|
+
) -> "InputsParser":
|
|
64
|
+
"""Factory method to create an InputsParser instance from input definitions."""
|
|
65
|
+
return cls(create_argument_parser_from_definitions(input_definitions, description))
|
|
66
|
+
|
|
67
|
+
def parse_inputs(self, inputs: List[str]) -> Dict[str, Any]:
|
|
68
|
+
"""Parses and validates the provided input strings against the configured parser. Inputs are expected as a list of 'name=value' elements."""
|
|
69
|
+
try:
|
|
70
|
+
args = []
|
|
71
|
+
for item in inputs:
|
|
72
|
+
if "=" not in item:
|
|
73
|
+
raise UserNotificationException(f"Invalid input format: '{item}', expected 'name=value'")
|
|
74
|
+
name, value = item.split("=", 1)
|
|
75
|
+
args.append(f"--{name}")
|
|
76
|
+
args.append(value)
|
|
77
|
+
|
|
78
|
+
parsed_namespace = self.parser.parse_args(args)
|
|
79
|
+
return vars(parsed_namespace)
|
|
80
|
+
except argparse.ArgumentError as e:
|
|
81
|
+
error_message = f"Input validation error: {e}"
|
|
82
|
+
raise UserNotificationException(error_message) from e
|
|
83
|
+
except SystemExit as e:
|
|
84
|
+
error_message = f"Input parsing failed: {e}"
|
|
85
|
+
raise UserNotificationException(error_message) from e
|
|
86
|
+
except Exception as e:
|
|
87
|
+
error_message = f"An unexpected error occurred during input parsing: {e}"
|
|
88
|
+
raise UserNotificationException(error_message) from e
|
pypeline/main.py
CHANGED
|
@@ -10,6 +10,7 @@ from pypeline import __version__
|
|
|
10
10
|
from pypeline.domain.execution_context import ExecutionContext
|
|
11
11
|
from pypeline.domain.pipeline import PipelineConfigIterator
|
|
12
12
|
from pypeline.domain.project_slurper import ProjectSlurper
|
|
13
|
+
from pypeline.inputs_parser import InputsParser
|
|
13
14
|
from pypeline.kickstart.create import KickstartProject
|
|
14
15
|
from pypeline.pypeline import PipelineScheduler, PipelineStepsExecutor
|
|
15
16
|
|
|
@@ -48,6 +49,12 @@ def run(
|
|
|
48
49
|
print: bool = typer.Option(False, help="Print the pipeline steps."),
|
|
49
50
|
force_run: bool = typer.Option(False, help="Force the execution of a step even if it is not dirty."),
|
|
50
51
|
dry_run: bool = typer.Option(False, help="Do not run any step, just print the steps that would be executed."),
|
|
52
|
+
inputs: Optional[List[str]] = typer.Option( # noqa: B008
|
|
53
|
+
None,
|
|
54
|
+
"--input",
|
|
55
|
+
"-i",
|
|
56
|
+
help="Provide input parameters as key=value pairs (e.g., -i name=value -i flag=true).",
|
|
57
|
+
),
|
|
51
58
|
) -> None:
|
|
52
59
|
project_slurper = ProjectSlurper(project_dir, config_file)
|
|
53
60
|
if print:
|
|
@@ -65,8 +72,15 @@ def run(
|
|
|
65
72
|
if not steps_references:
|
|
66
73
|
logger.info("No steps to run.")
|
|
67
74
|
return
|
|
68
|
-
|
|
69
|
-
|
|
75
|
+
# Parse the inputs
|
|
76
|
+
input_definitions = project_slurper.project_config.inputs
|
|
77
|
+
if input_definitions is None and inputs:
|
|
78
|
+
raise UserNotificationException(f"Inputs are not accepted because there are no inputs defined in the '{project_slurper.project_config.file}' configuration.")
|
|
79
|
+
if input_definitions and inputs:
|
|
80
|
+
inputs_dict = InputsParser.from_inputs_definitions(input_definitions).parse_inputs(inputs)
|
|
81
|
+
else:
|
|
82
|
+
inputs_dict = {}
|
|
83
|
+
PipelineStepsExecutor[ExecutionContext](ExecutionContext(project_dir, inputs=inputs_dict), steps_references, force_run, dry_run).run()
|
|
70
84
|
|
|
71
85
|
|
|
72
86
|
def main() -> None:
|
|
@@ -31,6 +31,10 @@ class GenerateEnvSetupScript(PipelineStep[ExecutionContext]):
|
|
|
31
31
|
else:
|
|
32
32
|
logger.warning(f".env file not found: {dot_env_file}")
|
|
33
33
|
env_vars = {}
|
|
34
|
+
|
|
35
|
+
# Merge execution context environment variables
|
|
36
|
+
env_vars.update(self.execution_context.env_vars)
|
|
37
|
+
|
|
34
38
|
# Generate the environment setup scripts
|
|
35
39
|
BatEnvSetupScriptGenerator(
|
|
36
40
|
install_dirs=self.execution_context.install_dirs,
|
pypeline/steps/scoop_install.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import io
|
|
2
2
|
import json
|
|
3
3
|
import traceback
|
|
4
|
-
from dataclasses import dataclass
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Any, ClassVar, Dict, List, Optional
|
|
7
7
|
|
|
@@ -18,6 +18,7 @@ from ..domain.pipeline import PipelineStep
|
|
|
18
18
|
@dataclass
|
|
19
19
|
class ScoopInstallExecutionInfo(DataClassJSONMixin):
|
|
20
20
|
install_dirs: List[Path]
|
|
21
|
+
env_vars: Dict[str, Any] = field(default_factory=dict)
|
|
21
22
|
|
|
22
23
|
class Config(BaseConfig):
|
|
23
24
|
"""Base configuration for JSON serialization with omitted None values."""
|
|
@@ -72,6 +73,8 @@ class ScoopInstall(PipelineStep[ExecutionContext]):
|
|
|
72
73
|
for app in installed_apps:
|
|
73
74
|
self.logger.debug(f" - {app.name} ({app.version})")
|
|
74
75
|
self.install_dirs.extend(app.get_all_required_paths())
|
|
76
|
+
# Collect environment variables from each app
|
|
77
|
+
self.execution_info.env_vars.update(app.env_vars)
|
|
75
78
|
self.execution_info.to_json_file(self.execution_info_file)
|
|
76
79
|
return 0
|
|
77
80
|
|
|
@@ -82,8 +85,10 @@ class ScoopInstall(PipelineStep[ExecutionContext]):
|
|
|
82
85
|
return [self.execution_info_file, *self.install_dirs]
|
|
83
86
|
|
|
84
87
|
def update_execution_context(self) -> None:
|
|
85
|
-
|
|
88
|
+
execution_info = ScoopInstallExecutionInfo.from_json_file(self.execution_info_file)
|
|
86
89
|
# Make the list unique and keep the order
|
|
87
|
-
unique_paths = list(dict.fromkeys(install_dirs))
|
|
90
|
+
unique_paths = list(dict.fromkeys(execution_info.install_dirs))
|
|
88
91
|
# Update the install directories for the subsequent steps
|
|
89
92
|
self.execution_context.add_install_dirs(unique_paths)
|
|
93
|
+
if execution_info.env_vars:
|
|
94
|
+
self.execution_context.add_env_vars(execution_info.env_vars)
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
pypeline/__init__.py,sha256=
|
|
1
|
+
pypeline/__init__.py,sha256=qyRV1Az4m5yhA_Nx1r_oZmIwZAZJpLTJdDw9oDJaqr8,23
|
|
2
2
|
pypeline/__run.py,sha256=TCdaX05Qm3g8T4QYryKB25Xxf0L5Km7hFOHe1mK9vI0,350
|
|
3
3
|
pypeline/domain/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
pypeline/domain/artifacts.py,sha256=5k7cVfHhLmvWXNuHKxXb9ca4Lxu0JytGQqazENCeKEU,1404
|
|
5
|
-
pypeline/domain/config.py,sha256=
|
|
6
|
-
pypeline/domain/execution_context.py,sha256=
|
|
5
|
+
pypeline/domain/config.py,sha256=6vWdHi7B6MA7NGi9wWXQE-YhSg1COSRmc3b1ji6AdAk,2053
|
|
6
|
+
pypeline/domain/execution_context.py,sha256=cDdUOU32hKg2Mv6FNXCG1co8GUrBf5QOVKhaKO-xVxI,1848
|
|
7
7
|
pypeline/domain/pipeline.py,sha256=2BsN2lw2znUxLH--Novyqe6SubVKs6XeHQSQf9yxirw,7788
|
|
8
|
-
pypeline/domain/project_slurper.py,sha256=
|
|
8
|
+
pypeline/domain/project_slurper.py,sha256=64aqgVsrLgAK-c5QOM2N0wGCkOM1uNMio8yKjO2zDLU,1069
|
|
9
|
+
pypeline/inputs_parser.py,sha256=8WJnWbACNmw_iYLhbAwcgGohCZWult8HtiJC9KiG3R0,3389
|
|
9
10
|
pypeline/kickstart/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
11
|
pypeline/kickstart/create.py,sha256=iaB8MMC7PinpPBwRmz3rWZuE-DRbsLh2NtvczYaVgi0,2133
|
|
11
12
|
pypeline/kickstart/templates/project/.gitignore,sha256=y8GJoVvRPez1LBokf1NaDOt2X1XtGwKFMF5yjA8AVS0,24
|
|
@@ -13,21 +14,21 @@ pypeline/kickstart/templates/project/bootstrap.ps1,sha256=eR8cyIJwDVt-bA2H3GWmxU
|
|
|
13
14
|
pypeline/kickstart/templates/project/bootstrap.py,sha256=9cJp_sbU0SKvDjJluvyQfh0_xIsf6E1ct7sa7rRecNU,17244
|
|
14
15
|
pypeline/kickstart/templates/project/poetry.toml,sha256=qgVxBdPcJZOHdHCTOBoZYna3cke4VGgRkNZ0bKgN6rs,32
|
|
15
16
|
pypeline/kickstart/templates/project/pypeline.ps1,sha256=PjCJULG8XA3AHKbNt3oHrIgD04huvvpIue_gjSo3PMA,104
|
|
16
|
-
pypeline/kickstart/templates/project/pypeline.yaml,sha256=
|
|
17
|
+
pypeline/kickstart/templates/project/pypeline.yaml,sha256=dyzn8vmHugpINVyB9l0QujK0J-oi--JQXunjUj9n3_k,385
|
|
17
18
|
pypeline/kickstart/templates/project/pyproject.toml,sha256=A60HZ6Aqf8KTFLoC35SexuJ2Ze1I-khuYmYUKhphfNY,247
|
|
18
19
|
pypeline/kickstart/templates/project/scoopfile.json,sha256=DcfZ8jYf9hmPHM-AWwnPKQJCzRG3fCuYtMeoY01nkag,219
|
|
19
20
|
pypeline/kickstart/templates/project/steps/my_step.py,sha256=iZYTzWtL-qxEW_t7q079d-xpnRST_tumSzxqiQDW7sM,707
|
|
20
21
|
pypeline/kickstart/templates/project/west.yaml,sha256=ZfVym7M4yzzC-Nm0vESdhqNYs6EaJuMQWGJBht_i0b4,188
|
|
21
|
-
pypeline/main.py,sha256=
|
|
22
|
+
pypeline/main.py,sha256=zkDqmYuTjMuYLsKdAHO-8RiEXUAgNLWoS3pbFbIp0Eg,4070
|
|
22
23
|
pypeline/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
24
|
pypeline/pypeline.py,sha256=-mquLfFlEvESk-HORhvjRMESIzdlVAgBLPjwUDOPLqg,7452
|
|
24
25
|
pypeline/steps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
26
|
pypeline/steps/create_venv.py,sha256=sajEl43Yqcd1f0BN9lZOhm93UIRXj4Qx4TdAOSxLkTU,2509
|
|
26
|
-
pypeline/steps/env_setup_script.py,sha256=
|
|
27
|
-
pypeline/steps/scoop_install.py,sha256=
|
|
27
|
+
pypeline/steps/env_setup_script.py,sha256=u08A6pvMccFQbcnU0xruFvpU30PbDrttnbOjl1gDqog,2340
|
|
28
|
+
pypeline/steps/scoop_install.py,sha256=E9wd5ygxRCuF2LD4X3wwTTnKa4XDmZ5fqeiH4dO0vjE,3788
|
|
28
29
|
pypeline/steps/west_install.py,sha256=hPyr28ksdKsQ0tv0gMNytzupgk1IgjN9CpmaBdX5zps,1947
|
|
29
|
-
pypeline_runner-1.
|
|
30
|
-
pypeline_runner-1.
|
|
31
|
-
pypeline_runner-1.
|
|
32
|
-
pypeline_runner-1.
|
|
33
|
-
pypeline_runner-1.
|
|
30
|
+
pypeline_runner-1.13.0.dist-info/LICENSE,sha256=sKxdoqSmW9ezvPvt0ZGJbneyA0SBcm0GiqzTv2jN230,1066
|
|
31
|
+
pypeline_runner-1.13.0.dist-info/METADATA,sha256=mXar7ok3VkWZqyqsiDqIOCyv-sw_t-LenSKDphG8Q8M,7553
|
|
32
|
+
pypeline_runner-1.13.0.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
|
33
|
+
pypeline_runner-1.13.0.dist-info/entry_points.txt,sha256=pe1u0uuhPI_yeQ0KjEw6jK-EvQfPcZwBSajgbAdKz1o,47
|
|
34
|
+
pypeline_runner-1.13.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|