pypeline-runner 0.2.1__py3-none-any.whl → 0.3.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 CHANGED
@@ -1 +1 @@
1
- __version__ = "0.2.1"
1
+ __version__ = "0.3.0"
pypeline/domain/config.py CHANGED
@@ -5,10 +5,11 @@ from typing import Any, Dict, Optional
5
5
  import yaml
6
6
  from mashumaro import DataClassDictMixin
7
7
  from py_app_dev.core.exceptions import UserNotificationException
8
- from py_app_dev.core.pipeline import PipelineConfig
9
8
  from yaml.parser import ParserError
10
9
  from yaml.scanner import ScannerError
11
10
 
11
+ from .pipeline import PipelineConfig
12
+
12
13
 
13
14
  @dataclass
14
15
  class ProjectConfig(DataClassDictMixin):
@@ -1,14 +1,48 @@
1
1
  from abc import abstractmethod
2
+ from dataclasses import dataclass
2
3
  from pathlib import Path
3
- from typing import Any, Dict, Optional, Type
4
+ from typing import (
5
+ Any,
6
+ Dict,
7
+ List,
8
+ Optional,
9
+ OrderedDict,
10
+ Type,
11
+ TypeAlias,
12
+ )
4
13
 
14
+ from mashumaro import DataClassDictMixin
5
15
  from py_app_dev.core.runnable import Runnable
6
16
 
7
17
  from .execution_context import ExecutionContext
8
18
 
9
19
 
20
+ @dataclass
21
+ class PipelineStepConfig(DataClassDictMixin):
22
+ #: Step name or class name if file is not specified
23
+ step: str
24
+ #: Path to file with step class
25
+ file: Optional[str] = None
26
+ #: Python module with step class
27
+ module: Optional[str] = None
28
+ #: Step class name
29
+ class_name: Optional[str] = None
30
+ #: Command to run. For simple steps that don't need a class
31
+ run: Optional[str] = None
32
+ #: Step description
33
+ description: Optional[str] = None
34
+ #: Step timeout in seconds
35
+ timeout_sec: Optional[int] = None
36
+ #: Custom step configuration
37
+ config: Optional[Dict[str, Any]] = None
38
+
39
+
40
+ PipelineConfig: TypeAlias = OrderedDict[str, List[PipelineStepConfig]]
41
+
42
+
10
43
  class PipelineStep(Runnable):
11
44
  def __init__(self, execution_context: ExecutionContext, output_dir: Path, config: Optional[Dict[str, Any]] = None) -> None:
45
+ super().__init__(self.get_needs_dependency_management())
12
46
  self.execution_context = execution_context
13
47
  self.output_dir = output_dir
14
48
  self.config = config
@@ -18,6 +52,9 @@ class PipelineStep(Runnable):
18
52
  def update_execution_context(self) -> None:
19
53
  pass
20
54
 
55
+ def get_needs_dependency_management(self) -> bool:
56
+ return True
57
+
21
58
 
22
59
  class PipelineStepReference:
23
60
  def __init__(self, group_name: str, _class: Type[PipelineStep], config: Optional[Dict[str, Any]] = None) -> None:
pypeline/main.py CHANGED
@@ -86,10 +86,14 @@ def run(
86
86
  PipelineStepsExecutor(project_slurper.artifacts_locator, steps_references, force_run, dry_run).run()
87
87
 
88
88
 
89
- if __name__ == "__main__":
89
+ def main() -> None:
90
90
  try:
91
91
  setup_logger()
92
92
  app()
93
93
  except UserNotificationException as e:
94
94
  logger.error(f"{e}")
95
95
  sys.exit(1)
96
+
97
+
98
+ if __name__ == "__main__":
99
+ main()
pypeline/pypeline.py CHANGED
@@ -1,14 +1,21 @@
1
+ import importlib
2
+ from importlib.util import module_from_spec, spec_from_file_location
1
3
  from pathlib import Path
2
- from typing import List, Optional
3
-
4
+ from typing import (
5
+ Any,
6
+ Dict,
7
+ List,
8
+ Optional,
9
+ Type,
10
+ )
11
+
12
+ from py_app_dev.core.exceptions import UserNotificationException
4
13
  from py_app_dev.core.logging import logger
5
- from py_app_dev.core.pipeline import PipelineConfig
6
- from py_app_dev.core.pipeline import PipelineLoader as GenericPipelineLoader
7
14
  from py_app_dev.core.runnable import Executor
8
15
 
9
16
  from .domain.artifacts import ProjectArtifactsLocator
10
17
  from .domain.execution_context import ExecutionContext
11
- from .domain.pipeline import PipelineStep, PipelineStepReference
18
+ from .domain.pipeline import PipelineConfig, PipelineStep, PipelineStepConfig, PipelineStepReference
12
19
 
13
20
 
14
21
  class PipelineLoader:
@@ -23,10 +30,95 @@ class PipelineLoader:
23
30
  def __init__(self, pipeline_config: PipelineConfig, project_root_dir: Path) -> None:
24
31
  self.pipeline_config = pipeline_config
25
32
  self.project_root_dir = project_root_dir
26
- self._loader = GenericPipelineLoader[PipelineStep](self.pipeline_config, self.project_root_dir)
27
33
 
28
34
  def load_steps_references(self) -> List[PipelineStepReference]:
29
- return [PipelineStepReference(step_reference.group_name, step_reference._class, step_reference.config) for step_reference in self._loader.load_steps()]
35
+ result = []
36
+ for group_name, steps_config in self.pipeline_config.items():
37
+ result.extend(self._load_steps(group_name, steps_config, self.project_root_dir))
38
+ return result
39
+
40
+ @staticmethod
41
+ def _load_steps(
42
+ group_name: str,
43
+ steps_config: List[PipelineStepConfig],
44
+ project_root_dir: Path,
45
+ ) -> List[PipelineStepReference]:
46
+ result = []
47
+ for step_config in steps_config:
48
+ step_class_name = step_config.class_name or step_config.step
49
+ if step_config.module:
50
+ step_class = PipelineLoader._load_module_step(step_config.module, step_class_name)
51
+ elif step_config.file:
52
+ step_class = PipelineLoader._load_user_step(project_root_dir.joinpath(step_config.file), step_class_name)
53
+ elif step_config.run:
54
+ step_class = PipelineLoader._create_run_command_step_class(step_config.run, step_class_name)
55
+ else:
56
+ raise UserNotificationException(f"Step '{step_class_name}' has no 'module' nor 'file' nor `run` defined." " Please check your pipeline configuration.")
57
+ result.append(PipelineStepReference(group_name, step_class, step_config.config))
58
+ return result
59
+
60
+ @staticmethod
61
+ def _load_user_step(python_file: Path, step_class_name: str) -> Type[PipelineStep]:
62
+ # Create a module specification from the file path
63
+ spec = spec_from_file_location(f"user__{step_class_name}", python_file)
64
+ if spec and spec.loader:
65
+ step_module = module_from_spec(spec)
66
+ # Import the module
67
+ spec.loader.exec_module(step_module)
68
+ try:
69
+ step_class = getattr(step_module, step_class_name)
70
+ except AttributeError:
71
+ raise UserNotificationException(f"Could not load class '{step_class_name}' from file '{python_file}'." " Please check your pipeline configuration.") from None
72
+ return step_class
73
+ raise UserNotificationException(f"Could not load file '{python_file}'." " Please check the file for any errors.")
74
+
75
+ @staticmethod
76
+ def _load_module_step(module_name: str, step_class_name: str) -> Type[PipelineStep]:
77
+ try:
78
+ module = importlib.import_module(module_name)
79
+ step_class = getattr(module, step_class_name)
80
+ except ImportError:
81
+ raise UserNotificationException(f"Could not load module '{module_name}'. Please check your pipeline configuration.") from None
82
+ except AttributeError:
83
+ raise UserNotificationException(f"Could not load class '{step_class_name}' from module '{module_name}'." " Please check your pipeline configuration.") from None
84
+ return step_class
85
+
86
+ @staticmethod
87
+ def _create_run_command_step_class(command: str, name: str) -> Type[PipelineStep]:
88
+ """Dynamically creates a step class for a given command."""
89
+
90
+ class DynamicRunCommandStep(PipelineStep):
91
+ """A simple step that runs a command."""
92
+
93
+ def __init__(self, execution_context: ExecutionContext, output_dir: Path, config: Optional[Dict[str, Any]] = None) -> None:
94
+ super().__init__(execution_context, output_dir, config)
95
+ self.command = command
96
+ self.name = name
97
+
98
+ def get_needs_dependency_management(self) -> bool:
99
+ """A command step does not need dependency management."""
100
+ return False
101
+
102
+ def run(self) -> int:
103
+ self.execution_context.create_process_executor(
104
+ [self.command],
105
+ cwd=self.project_root_dir,
106
+ ).execute()
107
+ return 0
108
+
109
+ def get_name(self) -> str:
110
+ return self.name
111
+
112
+ def get_inputs(self) -> List[Path]:
113
+ return []
114
+
115
+ def get_outputs(self) -> List[Path]:
116
+ return []
117
+
118
+ def update_execution_context(self) -> None:
119
+ pass
120
+
121
+ return DynamicRunCommandStep
30
122
 
31
123
 
32
124
  class PipelineStepsExecutor:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pypeline-runner
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Summary: Configure and execute pipelines with Python (similar to GitHub workflows or Jenkins pipelines).
5
5
  Home-page: https://github.com/cuinixam/pypeline
6
6
  License: MIT
@@ -17,9 +17,9 @@ Classifier: Programming Language :: Python :: 3.10
17
17
  Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Topic :: Software Development :: Libraries
20
- Requires-Dist: py-app-dev (>=2.1.1,<3.0.0)
21
- Requires-Dist: pyyaml (>=6.0.1,<7.0.0)
22
- Requires-Dist: typer[all] (>=0.12.0,<0.13.0)
20
+ Requires-Dist: py-app-dev (>=2.2,<3.0)
21
+ Requires-Dist: pyyaml (>=6.0,<7.0)
22
+ Requires-Dist: typer[all] (>=0.12,<0.13)
23
23
  Project-URL: Bug Tracker, https://github.com/cuinixam/pypeline/issues
24
24
  Project-URL: Changelog, https://github.com/cuinixam/pypeline/blob/main/CHANGELOG.md
25
25
  Project-URL: Documentation, https://pypeline-runner.readthedocs.io
@@ -1,10 +1,10 @@
1
- pypeline/__init__.py,sha256=HfjVOrpTnmZ-xVFCYSVmX50EXaBQeJteUHG-PD6iQs8,22
1
+ pypeline/__init__.py,sha256=VrXpHDu3erkzwl_WXrqINBm9xWkcyUy53IQOj042dOs,22
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=qXshnk9umi0AVGV4m5iEiy_MQ5Ad2LDZwI8OULU-qMk,1355
5
- pypeline/domain/config.py,sha256=e2DdbC6Gv4qJzjYaNnX90f2V-uYSHC5F6bViBTe80RE,1663
5
+ pypeline/domain/config.py,sha256=AlavAaz5hSxa6yaKYnj-x71ClhOtA41yv5Qf2JIE47k,1650
6
6
  pypeline/domain/execution_context.py,sha256=0zc3OgXeIMDpgWWYMaDGub7fY5urLLR79yuCaXVxoTQ,1101
7
- pypeline/domain/pipeline.py,sha256=gN2PUdBEYl1D534_XZ-bLwdhBlDr48SR_A8mdJ3ZzJg,980
7
+ pypeline/domain/pipeline.py,sha256=TbnM1JI1AFEdDfMSfdDVi2DZjFAAWBRMzIycOsui4Vs,1997
8
8
  pypeline/domain/project_slurper.py,sha256=YCho7V1BHjFmC_foxHFaWX8c_VbMJ16XEB4CQBlMrhc,894
9
9
  pypeline/kickstart/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  pypeline/kickstart/create.py,sha256=Gz2rVfhoQCKgYWlJoefh5Azcgamc9JJtiNsP1DTKdQo,2235
@@ -17,15 +17,15 @@ pypeline/kickstart/templates/project/pypeline.yaml,sha256=i9YSqpS2o-_Bzl69fvdOFV
17
17
  pypeline/kickstart/templates/project/pyproject.toml,sha256=yc6RCo-bUo1PXF91XfM-dButgfxU16Uud34NidgJ0zQ,225
18
18
  pypeline/kickstart/templates/project/scoopfile.json,sha256=DcfZ8jYf9hmPHM-AWwnPKQJCzRG3fCuYtMeoY01nkag,219
19
19
  pypeline/kickstart/templates/project/steps/my_step.py,sha256=YNRdb7ofqo3PxMQe8Vhtf7K9eZclv-6J0IwMrd3E70w,651
20
- pypeline/main.py,sha256=JLY75JHoQ1Q8F-Yyw6_s41n1CFjKh0971tDiI3drUUY,3227
20
+ pypeline/main.py,sha256=gwzbTUu-9_LXHpMDUDJU4gUInA4wGRmngO0VaTAqptk,3260
21
21
  pypeline/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
- pypeline/pypeline.py,sha256=3coRh9KGwR--yDK88ltXisE2nAR8BNUMQ21YHDjoKPY,4359
22
+ pypeline/pypeline.py,sha256=YiKZ8IH1t1FsTqJ45dVBkr2I2XRN2Su0ChlJFbUIqBc,8335
23
23
  pypeline/steps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  pypeline/steps/create_venv.py,sha256=od1zLVPhNdN-sWqEqYMkX19c5IEt4lIIOcASBeoqUUw,1954
25
25
  pypeline/steps/scoop_install.py,sha256=MmoC4Yy1lOhHrfKBrgJKHMcRUNPbRztzUbSgIlazer8,3356
26
26
  pypeline/steps/west_install.py,sha256=J1p-62owTDOY6bVTGCr5d9YFOL8DeRUPTnq0TbiaWlA,1930
27
- pypeline_runner-0.2.1.dist-info/LICENSE,sha256=sKxdoqSmW9ezvPvt0ZGJbneyA0SBcm0GiqzTv2jN230,1066
28
- pypeline_runner-0.2.1.dist-info/METADATA,sha256=-x52h84lUMHd33QQxo7RzR5A-fy7OihSdmqpFzrwGds,7168
29
- pypeline_runner-0.2.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
30
- pypeline_runner-0.2.1.dist-info/entry_points.txt,sha256=pe1u0uuhPI_yeQ0KjEw6jK-EvQfPcZwBSajgbAdKz1o,47
31
- pypeline_runner-0.2.1.dist-info/RECORD,,
27
+ pypeline_runner-0.3.0.dist-info/LICENSE,sha256=sKxdoqSmW9ezvPvt0ZGJbneyA0SBcm0GiqzTv2jN230,1066
28
+ pypeline_runner-0.3.0.dist-info/METADATA,sha256=f0qMzL-SHEe-n5BJw1DHJ-9WovmEmIaBh1X4bUMaYiY,7156
29
+ pypeline_runner-0.3.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
30
+ pypeline_runner-0.3.0.dist-info/entry_points.txt,sha256=pe1u0uuhPI_yeQ0KjEw6jK-EvQfPcZwBSajgbAdKz1o,47
31
+ pypeline_runner-0.3.0.dist-info/RECORD,,