pypeline-runner 1.6.0__tar.gz → 1.7.0__tar.gz

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.
Files changed (31) hide show
  1. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/PKG-INFO +1 -1
  2. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/pyproject.toml +1 -1
  3. pypeline_runner-1.7.0/src/pypeline/__init__.py +1 -0
  4. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/src/pypeline/domain/artifacts.py +2 -5
  5. pypeline_runner-1.7.0/src/pypeline/domain/pipeline.py +181 -0
  6. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/src/pypeline/domain/project_slurper.py +3 -2
  7. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/src/pypeline/main.py +2 -1
  8. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/src/pypeline/pypeline.py +19 -76
  9. pypeline_runner-1.6.0/src/pypeline/__init__.py +0 -1
  10. pypeline_runner-1.6.0/src/pypeline/domain/pipeline.py +0 -109
  11. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/LICENSE +0 -0
  12. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/README.md +0 -0
  13. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/src/pypeline/__run.py +0 -0
  14. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/src/pypeline/domain/__init__.py +0 -0
  15. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/src/pypeline/domain/config.py +0 -0
  16. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/src/pypeline/domain/execution_context.py +0 -0
  17. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/src/pypeline/kickstart/__init__.py +0 -0
  18. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/src/pypeline/kickstart/create.py +0 -0
  19. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/src/pypeline/kickstart/templates/project/.gitignore +0 -0
  20. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/src/pypeline/kickstart/templates/project/bootstrap.ps1 +0 -0
  21. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/src/pypeline/kickstart/templates/project/poetry.toml +0 -0
  22. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/src/pypeline/kickstart/templates/project/pypeline.ps1 +0 -0
  23. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/src/pypeline/kickstart/templates/project/pypeline.yaml +0 -0
  24. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/src/pypeline/kickstart/templates/project/pyproject.toml +0 -0
  25. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/src/pypeline/kickstart/templates/project/scoopfile.json +0 -0
  26. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/src/pypeline/kickstart/templates/project/steps/my_step.py +0 -0
  27. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/src/pypeline/py.typed +0 -0
  28. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/src/pypeline/steps/__init__.py +0 -0
  29. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/src/pypeline/steps/create_venv.py +0 -0
  30. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/src/pypeline/steps/scoop_install.py +0 -0
  31. {pypeline_runner-1.6.0 → pypeline_runner-1.7.0}/src/pypeline/steps/west_install.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pypeline-runner
3
- Version: 1.6.0
3
+ Version: 1.7.0
4
4
  Summary: Configure and execute pipelines with Python (similar to GitHub workflows or Jenkins pipelines).
5
5
  License: MIT
6
6
  Author: cuinixam
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pypeline-runner"
3
- version = "1.6.0"
3
+ version = "1.7.0"
4
4
  description = "Configure and execute pipelines with Python (similar to GitHub workflows or Jenkins pipelines)."
5
5
  authors = ["cuinixam <me@cuinixam.com>"]
6
6
  license = "MIT"
@@ -0,0 +1 @@
1
+ __version__ = "1.7.0"
@@ -10,13 +10,10 @@ CONFIG_FILENAME = "pypeline.yaml"
10
10
  class ProjectArtifactsLocator:
11
11
  """Provides paths to project artifacts."""
12
12
 
13
- def __init__(
14
- self,
15
- project_root_dir: Path,
16
- ) -> None:
13
+ def __init__(self, project_root_dir: Path, config_file: Optional[str] = None) -> None:
17
14
  self.project_root_dir = project_root_dir
18
15
  self.build_dir = project_root_dir / "build"
19
- self.config_file = project_root_dir / CONFIG_FILENAME
16
+ self.config_file = project_root_dir.joinpath(config_file if config_file else CONFIG_FILENAME)
20
17
  self.external_dependencies_dir = self.build_dir / "external"
21
18
  scripts_dir = "Scripts" if sys.platform.startswith("win32") else "bin"
22
19
  self.venv_scripts_dir = self.project_root_dir.joinpath(".venv").joinpath(scripts_dir)
@@ -0,0 +1,181 @@
1
+ import importlib
2
+ from abc import abstractmethod
3
+ from dataclasses import dataclass
4
+ from importlib.util import module_from_spec, spec_from_file_location
5
+ from pathlib import Path
6
+ from typing import (
7
+ Any,
8
+ Dict,
9
+ Generic,
10
+ Iterator,
11
+ List,
12
+ Optional,
13
+ OrderedDict,
14
+ Protocol,
15
+ Tuple,
16
+ Type,
17
+ TypeAlias,
18
+ TypeVar,
19
+ Union,
20
+ )
21
+
22
+ from mashumaro import DataClassDictMixin
23
+ from py_app_dev.core.exceptions import UserNotificationException
24
+ from py_app_dev.core.runnable import Runnable
25
+
26
+ from .execution_context import ExecutionContext
27
+
28
+
29
+ @dataclass
30
+ class PipelineStepConfig(DataClassDictMixin):
31
+ #: Step name or class name if file is not specified
32
+ step: str
33
+ #: Path to file with step class
34
+ file: Optional[str] = None
35
+ #: Python module with step class
36
+ module: Optional[str] = None
37
+ #: Step class name
38
+ class_name: Optional[str] = None
39
+ #: Command to run. For simple steps that don't need a class. Example: run: [echo, 'Hello World!']
40
+ run: Optional[Union[str, List[str]]] = None
41
+ #: Step description
42
+ description: Optional[str] = None
43
+ #: Step timeout in seconds
44
+ timeout_sec: Optional[int] = None
45
+ #: Custom step configuration
46
+ config: Optional[Dict[str, Any]] = None
47
+
48
+
49
+ PipelineConfig: TypeAlias = Union[List[PipelineStepConfig], OrderedDict[str, List[PipelineStepConfig]]]
50
+
51
+ TPipelineStep = TypeVar("TPipelineStep", covariant=True)
52
+
53
+
54
+ @dataclass
55
+ class PipelineStepReference(Generic[TPipelineStep]):
56
+ """Once a Step is found, keep the Step class reference to be able to instantiate it later."""
57
+
58
+ group_name: Optional[str]
59
+ _class: Type[TPipelineStep]
60
+ config: Optional[Dict[str, Any]] = None
61
+
62
+ @property
63
+ def name(self) -> str:
64
+ return self._class.__name__
65
+
66
+
67
+ class PipelineConfigIterator:
68
+ """
69
+ Iterates over the pipeline configuration, yielding group name and steps configuration.
70
+
71
+ This class abstracts the iteration logic for PipelineConfig, which can be:
72
+ - A list of steps (group name is None)
73
+ - An OrderedDict with group names as keys and lists of steps as values.
74
+
75
+ The iterator yields tuples of (group_name, steps).
76
+ """
77
+
78
+ def __init__(self, pipeline_config: PipelineConfig) -> None:
79
+ self._items = pipeline_config.items() if isinstance(pipeline_config, OrderedDict) else [(None, pipeline_config)]
80
+
81
+ def __iter__(self) -> Iterator[Tuple[Optional[str], List[PipelineStepConfig]]]:
82
+ """Return an iterator."""
83
+ yield from self._items
84
+
85
+
86
+ class StepClassFactory(Generic[TPipelineStep], Protocol):
87
+ def create_step_class(self, step_config: PipelineStepConfig, project_root_dir: Path) -> Type[TPipelineStep]: ...
88
+
89
+
90
+ class PipelineLoader(Generic[TPipelineStep]):
91
+ def __init__(self, pipeline_config: PipelineConfig, project_root_dir: Path, step_class_factory: Optional[StepClassFactory[TPipelineStep]] = None) -> None:
92
+ self.pipeline_config = pipeline_config
93
+ self.project_root_dir = project_root_dir
94
+ self.step_class_factory = step_class_factory
95
+
96
+ def load_steps_references(self) -> List[PipelineStepReference[TPipelineStep]]:
97
+ result = []
98
+ for group_name, steps_config in PipelineConfigIterator(self.pipeline_config):
99
+ result.extend(self._load_steps(group_name, steps_config, self.project_root_dir, self.step_class_factory))
100
+ return result
101
+
102
+ @staticmethod
103
+ def _load_steps(
104
+ group_name: Optional[str], steps_config: List[PipelineStepConfig], project_root_dir: Path, step_class_factory: Optional[StepClassFactory[TPipelineStep]] = None
105
+ ) -> List[PipelineStepReference[TPipelineStep]]:
106
+ result = []
107
+ for step_config in steps_config:
108
+ step_class_name = step_config.class_name or step_config.step
109
+ if step_config.module:
110
+ step_class = PipelineLoader[TPipelineStep]._load_module_step(step_config.module, step_class_name)
111
+ elif step_config.file:
112
+ step_class = PipelineLoader[TPipelineStep]._load_user_step(project_root_dir.joinpath(step_config.file), step_class_name)
113
+ else:
114
+ if step_class_factory:
115
+ step_class = step_class_factory.create_step_class(step_config, project_root_dir)
116
+ else:
117
+ raise UserNotificationException(
118
+ f"Step '{step_class_name}' has no 'module' nor 'file' defined nor a custom step class factory was provided. Please check your pipeline configuration."
119
+ )
120
+ result.append(PipelineStepReference(group_name, step_class, step_config.config))
121
+ return result
122
+
123
+ @staticmethod
124
+ def _load_user_step(python_file: Path, step_class_name: str) -> Type[TPipelineStep]:
125
+ # Create a module specification from the file path
126
+ spec = spec_from_file_location(f"user__{python_file.stem}", python_file)
127
+ if spec and spec.loader:
128
+ step_module = module_from_spec(spec)
129
+ # Import the module
130
+ spec.loader.exec_module(step_module)
131
+ try:
132
+ step_class = getattr(step_module, step_class_name)
133
+ except AttributeError:
134
+ raise UserNotificationException(f"Could not load class '{step_class_name}' from file '{python_file}'. Please check your pipeline configuration.") from None
135
+ return step_class
136
+ raise UserNotificationException(f"Could not load file '{python_file}'. Please check the file for any errors.")
137
+
138
+ @staticmethod
139
+ def _load_module_step(module_name: str, step_class_name: str) -> Type[TPipelineStep]:
140
+ try:
141
+ module = importlib.import_module(module_name)
142
+ step_class = getattr(module, step_class_name)
143
+ except ImportError:
144
+ raise UserNotificationException(f"Could not load module '{module_name}'. Please check your pipeline configuration.") from None
145
+ except AttributeError:
146
+ raise UserNotificationException(f"Could not load class '{step_class_name}' from module '{module_name}'. Please check your pipeline configuration.") from None
147
+ return step_class
148
+
149
+
150
+ TExecutionContext = TypeVar("TExecutionContext", bound=ExecutionContext)
151
+
152
+
153
+ class PipelineStep(Generic[TExecutionContext], Runnable):
154
+ """One can create subclasses of PipelineStep that specify the type of ExecutionContext they require."""
155
+
156
+ def __init__(self, execution_context: TExecutionContext, group_name: Optional[str], config: Optional[Dict[str, Any]] = None) -> None:
157
+ super().__init__(self.get_needs_dependency_management())
158
+ self.execution_context = execution_context
159
+ self.group_name = group_name
160
+ self.config = config
161
+ self.project_root_dir = self.execution_context.project_root_dir
162
+
163
+ @property
164
+ def output_dir(self) -> Path:
165
+ output_dir = self.execution_context.create_artifacts_locator().build_dir
166
+ if self.group_name:
167
+ output_dir = output_dir / self.group_name
168
+ return output_dir
169
+
170
+ @abstractmethod
171
+ def update_execution_context(self) -> None:
172
+ """
173
+ Even if the step does not need to run ( because it is not outdated ), it can still update the execution context.
174
+
175
+ A typical use case is for steps installing software that need to provide the install directories in the execution context even if all tools are already installed.
176
+ """
177
+ pass
178
+
179
+ def get_needs_dependency_management(self) -> bool:
180
+ """If false, the step executor will not check for outdated dependencies. This is useful for steps consisting of command lines which shall always run."""
181
+ return True
@@ -1,4 +1,5 @@
1
1
  from pathlib import Path
2
+ from typing import Optional
2
3
 
3
4
  from py_app_dev.core.exceptions import UserNotificationException
4
5
  from py_app_dev.core.logging import logger
@@ -8,9 +9,9 @@ from .config import PipelineConfig, ProjectConfig
8
9
 
9
10
 
10
11
  class ProjectSlurper:
11
- def __init__(self, project_dir: Path) -> None:
12
+ def __init__(self, project_dir: Path, config_file: Optional[str] = None) -> None:
12
13
  self.logger = logger.bind()
13
- self.artifacts_locator = ProjectArtifactsLocator(project_dir)
14
+ self.artifacts_locator = ProjectArtifactsLocator(project_dir, config_file)
14
15
  try:
15
16
  self.user_config: ProjectConfig = ProjectConfig.from_file(self.artifacts_locator.config_file)
16
17
  except FileNotFoundError:
@@ -42,6 +42,7 @@ def init(
42
42
  @time_it("run")
43
43
  def run(
44
44
  project_dir: Path = typer.Option(Path.cwd().absolute(), help="The project directory"), # noqa: B008,
45
+ config_file: Optional[str] = typer.Option(None, help="The name of the YAML configuration file containing the pypeline definition."),
45
46
  step: Optional[str] = typer.Option(
46
47
  None,
47
48
  help="Name of the step to run (as written in the pipeline config).",
@@ -67,7 +68,7 @@ def run(
67
68
  is_flag=True,
68
69
  ),
69
70
  ) -> None:
70
- project_slurper = ProjectSlurper(project_dir)
71
+ project_slurper = ProjectSlurper(project_dir, config_file)
71
72
  if print:
72
73
  logger.info("Pipeline steps:")
73
74
  for group, step_configs in PipelineConfigIterator(project_slurper.pipeline):
@@ -1,5 +1,3 @@
1
- import importlib
2
- from importlib.util import module_from_spec, spec_from_file_location
3
1
  from pathlib import Path
4
2
  from typing import (
5
3
  Any,
@@ -8,7 +6,6 @@ from typing import (
8
6
  List,
9
7
  Optional,
10
8
  Type,
11
- cast,
12
9
  )
13
10
 
14
11
  from py_app_dev.core.exceptions import UserNotificationException
@@ -17,75 +14,18 @@ from py_app_dev.core.runnable import Executor
17
14
 
18
15
  from .domain.artifacts import ProjectArtifactsLocator
19
16
  from .domain.execution_context import ExecutionContext
20
- from .domain.pipeline import PipelineConfig, PipelineConfigIterator, PipelineStep, PipelineStepConfig, PipelineStepReference, TExecutionContext
17
+ from .domain.pipeline import PipelineConfig, PipelineLoader, PipelineStep, PipelineStepConfig, PipelineStepReference, StepClassFactory, TExecutionContext
21
18
 
22
19
 
23
- class PipelineLoader(Generic[TExecutionContext]):
24
- """
25
- Loads pipeline steps from a pipeline configuration.
26
-
27
- The steps are not instantiated, only the references are returned (lazy load).
28
- The pipeline loader needs to know the project root directory to be able to find the
29
- user custom local steps.
30
- """
31
-
32
- def __init__(self, pipeline_config: PipelineConfig, project_root_dir: Path) -> None:
33
- self.pipeline_config = pipeline_config
34
- self.project_root_dir = project_root_dir
35
-
36
- def load_steps_references(self) -> List[PipelineStepReference[TExecutionContext]]:
37
- result = []
38
- for group_name, steps_config in PipelineConfigIterator(self.pipeline_config):
39
- result.extend(self._load_steps(group_name, steps_config, self.project_root_dir))
40
- return result
41
-
42
- @staticmethod
43
- def _load_steps(
44
- group_name: Optional[str],
45
- steps_config: List[PipelineStepConfig],
46
- project_root_dir: Path,
47
- ) -> List[PipelineStepReference[TExecutionContext]]:
48
- result = []
49
- for step_config in steps_config:
50
- step_class_name = step_config.class_name or step_config.step
51
- if step_config.module:
52
- step_class = PipelineLoader._load_module_step(step_config.module, step_class_name)
53
- elif step_config.file:
54
- step_class = PipelineLoader._load_user_step(project_root_dir.joinpath(step_config.file), step_class_name)
55
- elif step_config.run:
56
- # We want the run field to always return a list of strings (the command and its arguments).
57
- run_command = step_config.run.split(" ") if isinstance(step_config.run, str) else step_config.run
58
- step_class = PipelineLoader._create_run_command_step_class(run_command, step_class_name)
59
- else:
60
- raise UserNotificationException(f"Step '{step_class_name}' has no 'module' nor 'file' nor `run` defined. Please check your pipeline configuration.")
61
- result.append(PipelineStepReference[TExecutionContext](group_name, cast(Type[PipelineStep[TExecutionContext]], step_class), step_config.config))
62
- return result
63
-
64
- @staticmethod
65
- def _load_user_step(python_file: Path, step_class_name: str) -> Type[PipelineStep[ExecutionContext]]:
66
- # Create a module specification from the file path
67
- spec = spec_from_file_location(f"user__{python_file.stem}", python_file)
68
- if spec and spec.loader:
69
- step_module = module_from_spec(spec)
70
- # Import the module
71
- spec.loader.exec_module(step_module)
72
- try:
73
- step_class = getattr(step_module, step_class_name)
74
- except AttributeError:
75
- raise UserNotificationException(f"Could not load class '{step_class_name}' from file '{python_file}'. Please check your pipeline configuration.") from None
76
- return step_class
77
- raise UserNotificationException(f"Could not load file '{python_file}'. Please check the file for any errors.")
78
-
79
- @staticmethod
80
- def _load_module_step(module_name: str, step_class_name: str) -> Type[PipelineStep[ExecutionContext]]:
81
- try:
82
- module = importlib.import_module(module_name)
83
- step_class = getattr(module, step_class_name)
84
- except ImportError:
85
- raise UserNotificationException(f"Could not load module '{module_name}'. Please check your pipeline configuration.") from None
86
- except AttributeError:
87
- raise UserNotificationException(f"Could not load class '{step_class_name}' from module '{module_name}'. Please check your pipeline configuration.") from None
88
- return step_class
20
+ class RunCommandClassFactory(StepClassFactory[PipelineStep[TExecutionContext]]):
21
+ def create_step_class(self, step_config: PipelineStepConfig, project_root_dir: Path) -> Type[PipelineStep[ExecutionContext]]:
22
+ _ = project_root_dir # Unused because we do not need to locate files relative to the project root directory
23
+ step_name = step_config.class_name or step_config.step
24
+ if step_config.run:
25
+ # We want the run field to always return a list of strings (the command and its arguments).
26
+ run_command = step_config.run.split(" ") if isinstance(step_config.run, str) else step_config.run
27
+ return self._create_run_command_step_class(run_command, step_name)
28
+ raise UserNotificationException(f"Step '{step_name}' has `run` command defined. Please check your pipeline configuration.")
89
29
 
90
30
  @staticmethod
91
31
  def _create_run_command_step_class(command: List[str], name: str) -> Type[PipelineStep[ExecutionContext]]:
@@ -133,7 +73,7 @@ class PipelineStepsExecutor(Generic[TExecutionContext]):
133
73
  def __init__(
134
74
  self,
135
75
  execution_context: TExecutionContext,
136
- steps_references: List[PipelineStepReference[TExecutionContext]],
76
+ steps_references: List[PipelineStepReference[PipelineStep[TExecutionContext]]],
137
77
  force_run: bool = False,
138
78
  dry_run: bool = False,
139
79
  ) -> None:
@@ -175,16 +115,15 @@ class PipelineScheduler(Generic[TExecutionContext]):
175
115
  self.project_root_dir = project_root_dir
176
116
  self.logger = logger.bind()
177
117
 
178
- def get_steps_to_run(self, step_name: Optional[str] = None, single: bool = False) -> List[PipelineStepReference[TExecutionContext]]:
179
- pipeline_loader = PipelineLoader[TExecutionContext](self.pipeline, self.project_root_dir)
180
- return self.filter_steps_references(pipeline_loader.load_steps_references(), step_name, single)
118
+ def get_steps_to_run(self, step_name: Optional[str] = None, single: bool = False) -> List[PipelineStepReference[PipelineStep[TExecutionContext]]]:
119
+ return self.filter_steps_references(self.create_pipeline_loader(self.pipeline, self.project_root_dir).load_steps_references(), step_name, single)
181
120
 
182
121
  @staticmethod
183
122
  def filter_steps_references(
184
- steps_references: List[PipelineStepReference[TExecutionContext]],
123
+ steps_references: List[PipelineStepReference[PipelineStep[TExecutionContext]]],
185
124
  step_name: Optional[str],
186
125
  single: Optional[bool],
187
- ) -> List[PipelineStepReference[TExecutionContext]]:
126
+ ) -> List[PipelineStepReference[PipelineStep[TExecutionContext]]]:
188
127
  if step_name:
189
128
  step_reference = next((step for step in steps_references if step.name == step_name), None)
190
129
  if not step_reference:
@@ -193,3 +132,7 @@ class PipelineScheduler(Generic[TExecutionContext]):
193
132
  return [step_reference]
194
133
  return [step for step in steps_references if steps_references.index(step) <= steps_references.index(step_reference)]
195
134
  return steps_references
135
+
136
+ @staticmethod
137
+ def create_pipeline_loader(pipeline: PipelineConfig, project_root_dir: Path) -> PipelineLoader[PipelineStep[TExecutionContext]]:
138
+ return PipelineLoader[PipelineStep[TExecutionContext]](pipeline, project_root_dir, RunCommandClassFactory())
@@ -1 +0,0 @@
1
- __version__ = "1.6.0"
@@ -1,109 +0,0 @@
1
- from abc import abstractmethod
2
- from dataclasses import dataclass
3
- from pathlib import Path
4
- from typing import (
5
- Any,
6
- Dict,
7
- Generic,
8
- Iterator,
9
- List,
10
- Optional,
11
- OrderedDict,
12
- Tuple,
13
- Type,
14
- TypeAlias,
15
- TypeVar,
16
- Union,
17
- )
18
-
19
- from mashumaro import DataClassDictMixin
20
- from py_app_dev.core.runnable import Runnable
21
-
22
- from .execution_context import ExecutionContext
23
-
24
-
25
- @dataclass
26
- class PipelineStepConfig(DataClassDictMixin):
27
- #: Step name or class name if file is not specified
28
- step: str
29
- #: Path to file with step class
30
- file: Optional[str] = None
31
- #: Python module with step class
32
- module: Optional[str] = None
33
- #: Step class name
34
- class_name: Optional[str] = None
35
- #: Command to run. For simple steps that don't need a class. Example: run: [echo, 'Hello World!']
36
- run: Optional[Union[str, List[str]]] = None
37
- #: Step description
38
- description: Optional[str] = None
39
- #: Step timeout in seconds
40
- timeout_sec: Optional[int] = None
41
- #: Custom step configuration
42
- config: Optional[Dict[str, Any]] = None
43
-
44
-
45
- PipelineConfig: TypeAlias = Union[List[PipelineStepConfig], OrderedDict[str, List[PipelineStepConfig]]]
46
-
47
-
48
- class PipelineConfigIterator:
49
- """
50
- Iterates over the pipeline configuration, yielding group name and steps configuration.
51
-
52
- This class abstracts the iteration logic for PipelineConfig, which can be:
53
- - A list of steps (group name is None)
54
- - An OrderedDict with group names as keys and lists of steps as values.
55
-
56
- The iterator yields tuples of (group_name, steps).
57
- """
58
-
59
- def __init__(self, pipeline_config: PipelineConfig) -> None:
60
- self._items = pipeline_config.items() if isinstance(pipeline_config, OrderedDict) else [(None, pipeline_config)]
61
-
62
- def __iter__(self) -> Iterator[Tuple[Optional[str], List[PipelineStepConfig]]]:
63
- """Return an iterator."""
64
- yield from self._items
65
-
66
-
67
- TExecutionContext = TypeVar("TExecutionContext", bound=ExecutionContext)
68
-
69
-
70
- class PipelineStep(Generic[TExecutionContext], Runnable):
71
- """One can create subclasses of PipelineStep that specify the type of ExecutionContext they require."""
72
-
73
- def __init__(self, execution_context: TExecutionContext, group_name: Optional[str], config: Optional[Dict[str, Any]] = None) -> None:
74
- super().__init__(self.get_needs_dependency_management())
75
- self.execution_context = execution_context
76
- self.group_name = group_name
77
- self.config = config
78
- self.project_root_dir = self.execution_context.project_root_dir
79
-
80
- @property
81
- def output_dir(self) -> Path:
82
- output_dir = self.execution_context.create_artifacts_locator().build_dir
83
- if self.group_name:
84
- output_dir = output_dir / self.group_name
85
- return output_dir
86
-
87
- @abstractmethod
88
- def update_execution_context(self) -> None:
89
- """
90
- Even if the step does not need to run ( because it is not outdated ), it can still update the execution context.
91
-
92
- A typical use case is for steps installing software that need to provide the install directories in the execution context even if all tools are already installed.
93
- """
94
- pass
95
-
96
- def get_needs_dependency_management(self) -> bool:
97
- """If false, the step executor will not check for outdated dependencies. This is useful for steps consisting of command lines which shall always run."""
98
- return True
99
-
100
-
101
- class PipelineStepReference(Generic[TExecutionContext]):
102
- def __init__(self, group_name: Optional[str], _class: Type[PipelineStep[TExecutionContext]], config: Optional[Dict[str, Any]] = None) -> None:
103
- self.group_name = group_name
104
- self._class = _class
105
- self.config = config
106
-
107
- @property
108
- def name(self) -> str:
109
- return self._class.__name__
File without changes