pypeline-runner 1.22.0__tar.gz → 1.23.1__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.
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/PKG-INFO +1 -1
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/pyproject.toml +1 -1
- pypeline_runner-1.23.1/src/pypeline/__init__.py +1 -0
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/src/pypeline/bootstrap/run.py +13 -4
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/src/pypeline/steps/create_venv.py +71 -5
- pypeline_runner-1.23.1/src/pypeline/steps/env_setup_script.py +117 -0
- pypeline_runner-1.22.0/src/pypeline/__init__.py +0 -1
- pypeline_runner-1.22.0/src/pypeline/steps/env_setup_script.py +0 -65
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/LICENSE +0 -0
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/README.md +0 -0
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/src/pypeline/__run.py +0 -0
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/src/pypeline/bootstrap/__init__.py +0 -0
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/src/pypeline/domain/__init__.py +0 -0
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/src/pypeline/domain/artifacts.py +0 -0
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/src/pypeline/domain/config.py +0 -0
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/src/pypeline/domain/execution_context.py +0 -0
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/src/pypeline/domain/pipeline.py +0 -0
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/src/pypeline/domain/project_slurper.py +0 -0
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/src/pypeline/inputs_parser.py +0 -0
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/src/pypeline/kickstart/__init__.py +0 -0
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/src/pypeline/kickstart/create.py +0 -0
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/src/pypeline/kickstart/templates/project/.gitignore +0 -0
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/src/pypeline/kickstart/templates/project/pypeline.ps1 +0 -0
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/src/pypeline/kickstart/templates/project/pypeline.yaml +0 -0
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/src/pypeline/kickstart/templates/project/pyproject.toml +0 -0
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/src/pypeline/kickstart/templates/project/steps/my_step.py +0 -0
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/src/pypeline/kickstart/templates/project/west.yaml +0 -0
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/src/pypeline/main.py +0 -0
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/src/pypeline/py.typed +0 -0
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/src/pypeline/pypeline.py +0 -0
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/src/pypeline/steps/__init__.py +0 -0
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/src/pypeline/steps/scoop_install.py +0 -0
- {pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/src/pypeline/steps/west_install.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.23.1"
|
|
@@ -632,22 +632,31 @@ class CreateVirtualEnvironment(Runnable):
|
|
|
632
632
|
except OSError as exc:
|
|
633
633
|
logger.warning(f"Could not write Python version marker: {exc}")
|
|
634
634
|
|
|
635
|
+
def _set_env_var(self, key: str, value: str) -> None:
|
|
636
|
+
"""Helper method to set environment variables (easier to mock in tests)."""
|
|
637
|
+
os.environ[key] = value
|
|
638
|
+
|
|
635
639
|
def _ensure_in_project_venv(self) -> None:
|
|
636
640
|
"""Configure package managers to create venv in-project (.venv in repository)."""
|
|
637
641
|
if self.package_manager_name == "poetry":
|
|
638
642
|
# Set environment variable for poetry to create venv in-project
|
|
639
|
-
|
|
643
|
+
self._set_env_var("POETRY_VIRTUALENVS_IN_PROJECT", "true")
|
|
640
644
|
elif self.package_manager_name == "pipenv":
|
|
641
645
|
# Set environment variable for pipenv
|
|
642
|
-
|
|
646
|
+
self._set_env_var("PIPENV_VENV_IN_PROJECT", "1")
|
|
643
647
|
# UV creates .venv in-project by default, no configuration needed
|
|
644
648
|
|
|
645
649
|
def _ensure_correct_python_version(self) -> None:
|
|
646
650
|
"""Ensure the correct Python version is used in the virtual environment."""
|
|
647
651
|
if self.package_manager_name == "poetry":
|
|
648
652
|
# Make Poetry use the Python interpreter it's being run with
|
|
649
|
-
|
|
650
|
-
|
|
653
|
+
self._set_env_var("POETRY_VIRTUALENVS_PREFER_ACTIVE_PYTHON", "false")
|
|
654
|
+
self._set_env_var("POETRY_VIRTUALENVS_USE_POETRY_PYTHON", "true")
|
|
655
|
+
elif self.package_manager_name == "uv":
|
|
656
|
+
# Make UV use the Python interpreter it's being run with
|
|
657
|
+
self._set_env_var("UV_PYTHON", self.config.python_version)
|
|
658
|
+
self._set_env_var("UV_MANAGED_PYTHON", "false")
|
|
659
|
+
self._set_env_var("UV_NO_PYTHON_DOWNLOADS", "true")
|
|
651
660
|
|
|
652
661
|
def _get_install_argument(self) -> str:
|
|
653
662
|
if self.package_manager_name == "uv":
|
|
@@ -2,6 +2,7 @@ import io
|
|
|
2
2
|
import json
|
|
3
3
|
import re
|
|
4
4
|
import shutil
|
|
5
|
+
import subprocess
|
|
5
6
|
import sys
|
|
6
7
|
import traceback
|
|
7
8
|
from dataclasses import dataclass
|
|
@@ -85,17 +86,73 @@ class CreateVEnv(PipelineStep[ExecutionContext]):
|
|
|
85
86
|
]
|
|
86
87
|
)
|
|
87
88
|
|
|
89
|
+
def _verify_python_version(self, executable: str, expected_version: str) -> bool:
|
|
90
|
+
"""
|
|
91
|
+
Verify that a Python executable matches the expected version.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
----
|
|
95
|
+
executable: Name or path of Python executable to check
|
|
96
|
+
expected_version: Expected version string (e.g., "3.11" or "3.11.5")
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
-------
|
|
100
|
+
True if the executable's version matches expected_version (ignoring patch),
|
|
101
|
+
False otherwise or if the executable cannot be queried.
|
|
102
|
+
|
|
103
|
+
"""
|
|
104
|
+
try:
|
|
105
|
+
# Run python --version to get the version string
|
|
106
|
+
result = subprocess.run(
|
|
107
|
+
[executable, "--version"], # noqa: S603
|
|
108
|
+
capture_output=True,
|
|
109
|
+
text=True,
|
|
110
|
+
check=False,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if result.returncode != 0:
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
# Parse version from output (e.g., "Python 3.11.5")
|
|
117
|
+
version_output = result.stdout.strip()
|
|
118
|
+
match = re.match(r"Python\s+(\d+)\.(\d+)(?:\.\d+)?", version_output)
|
|
119
|
+
if not match:
|
|
120
|
+
self.logger.warning(f"Could not parse version from: {version_output}")
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
actual_major = match.group(1)
|
|
124
|
+
actual_minor = match.group(2)
|
|
125
|
+
|
|
126
|
+
# Parse expected version
|
|
127
|
+
expected_parts = expected_version.split(".")
|
|
128
|
+
if len(expected_parts) == 0:
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
expected_major = expected_parts[0]
|
|
132
|
+
# If only major version specified, only compare major
|
|
133
|
+
if len(expected_parts) == 1:
|
|
134
|
+
return actual_major == expected_major
|
|
135
|
+
|
|
136
|
+
# Compare major.minor
|
|
137
|
+
expected_minor = expected_parts[1]
|
|
138
|
+
return actual_major == expected_major and actual_minor == expected_minor
|
|
139
|
+
|
|
140
|
+
except (FileNotFoundError, OSError) as e:
|
|
141
|
+
self.logger.debug(f"Failed to verify Python version for {executable}: {e}")
|
|
142
|
+
return False
|
|
143
|
+
|
|
88
144
|
def _find_python_executable(self, python_version: str) -> Optional[str]:
|
|
89
145
|
"""
|
|
90
146
|
Find Python executable based on version string.
|
|
91
147
|
|
|
92
148
|
Supports version formats:
|
|
93
|
-
- "3.11.5" or "3.11" -> tries python3.11, python311
|
|
94
|
-
- "3" -> tries python3
|
|
149
|
+
- "3.11.5" or "3.11" -> tries python3.11, python311, then falls back to python
|
|
150
|
+
- "3" -> tries python3, then falls back to python
|
|
95
151
|
|
|
96
|
-
Always ignores patch version.
|
|
152
|
+
Always ignores patch version. Falls back to generic 'python' if version-specific
|
|
153
|
+
executables are not found, but verifies the version matches.
|
|
97
154
|
|
|
98
|
-
Returns the first executable found in PATH, or None if not found.
|
|
155
|
+
Returns the first executable found in PATH, or None if not found or version mismatch.
|
|
99
156
|
"""
|
|
100
157
|
# Handle empty string
|
|
101
158
|
if not python_version:
|
|
@@ -133,7 +190,16 @@ class CreateVEnv(PipelineStep[ExecutionContext]):
|
|
|
133
190
|
self.logger.debug(f"Found Python executable: {executable_path} (candidate: {candidate})")
|
|
134
191
|
return candidate
|
|
135
192
|
|
|
136
|
-
#
|
|
193
|
+
# Fallback to generic 'python' executable with version verification
|
|
194
|
+
self.logger.debug(f"No version-specific Python executable found for {python_version}, trying generic 'python'")
|
|
195
|
+
if shutil.which("python"):
|
|
196
|
+
if self._verify_python_version("python", python_version):
|
|
197
|
+
self.logger.info(f"Using generic 'python' executable (verified as Python {python_version})")
|
|
198
|
+
return "python"
|
|
199
|
+
else:
|
|
200
|
+
self.logger.warning(f"Generic 'python' executable found but version does not match {python_version}")
|
|
201
|
+
|
|
202
|
+
# No suitable executable found
|
|
137
203
|
return None
|
|
138
204
|
|
|
139
205
|
@property
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import platform
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
from py_app_dev.core.env_setup_scripts import BatEnvSetupScriptGenerator, EnvSetupScriptGenerator, Ps1EnvSetupScriptGenerator
|
|
6
|
+
from py_app_dev.core.logging import logger
|
|
7
|
+
|
|
8
|
+
from pypeline.domain.execution_context import ExecutionContext
|
|
9
|
+
from pypeline.domain.pipeline import PipelineStep
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def read_dot_env_file(dot_env_file: Path) -> Dict[str, str]:
|
|
13
|
+
"""Reads a .env file and returns a dictionary of environment variables."""
|
|
14
|
+
env_vars = {}
|
|
15
|
+
with dot_env_file.open("r") as f:
|
|
16
|
+
for line in f:
|
|
17
|
+
line = line.strip()
|
|
18
|
+
if line and not line.startswith("#"):
|
|
19
|
+
key, value = line.split("=", 1)
|
|
20
|
+
env_vars[key.strip()] = value.strip().strip('"').strip("'")
|
|
21
|
+
return env_vars
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ShEnvSetupScriptGenerator(EnvSetupScriptGenerator):
|
|
25
|
+
"""Generates a bash/sh script to set environment variables and update PATH."""
|
|
26
|
+
|
|
27
|
+
def generate_content(self) -> str:
|
|
28
|
+
lines = ["#!/bin/bash"]
|
|
29
|
+
|
|
30
|
+
for key, value in self.environment.items():
|
|
31
|
+
# Escape single quotes by replacing ' with '\''
|
|
32
|
+
# This closes the string, adds an escaped quote, then reopens the string
|
|
33
|
+
escaped_value = value.replace("'", "'\\''")
|
|
34
|
+
# Use single quotes for the value to prevent variable expansion
|
|
35
|
+
lines.append(f"export {key}='{escaped_value}'")
|
|
36
|
+
|
|
37
|
+
if self.install_dirs:
|
|
38
|
+
# Convert to POSIX paths to ensure forward slashes on all platforms
|
|
39
|
+
path_string = ":".join([path.as_posix() for path in self.install_dirs])
|
|
40
|
+
# Escape single quotes in paths
|
|
41
|
+
escaped_path_string = path_string.replace("'", "'\\''")
|
|
42
|
+
lines.append(f"export PATH='{escaped_path_string}':\"$PATH\"")
|
|
43
|
+
else:
|
|
44
|
+
self.logger.debug("No install directories provided for PATH update.")
|
|
45
|
+
lines.append("")
|
|
46
|
+
|
|
47
|
+
return "\n".join(lines)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class GenerateEnvSetupScript(PipelineStep[ExecutionContext]):
|
|
51
|
+
def __init__(self, execution_context: ExecutionContext, group_name: Optional[str], config: Optional[Dict[str, Any]] = None) -> None:
|
|
52
|
+
super().__init__(execution_context, group_name, config)
|
|
53
|
+
self._generated_scripts: List[Path] = []
|
|
54
|
+
|
|
55
|
+
def run(self) -> None:
|
|
56
|
+
logger.info(f"Generating environment setup scripts under {self.output_dir} ...")
|
|
57
|
+
|
|
58
|
+
# Read the .env file and set up the environment variables
|
|
59
|
+
dot_env_file = self.execution_context.project_root_dir.joinpath(".env")
|
|
60
|
+
if dot_env_file.exists():
|
|
61
|
+
logger.debug(f"Reading .env file: {dot_env_file}")
|
|
62
|
+
env_vars = read_dot_env_file(dot_env_file)
|
|
63
|
+
else:
|
|
64
|
+
logger.info(f".env file not found: {dot_env_file}.")
|
|
65
|
+
env_vars = {}
|
|
66
|
+
|
|
67
|
+
# Merge execution context environment variables
|
|
68
|
+
env_vars.update(self.execution_context.env_vars)
|
|
69
|
+
# Update the execution context with the merged environment variables to ensure they are available for subsequent steps
|
|
70
|
+
self.execution_context.env_vars.update(env_vars)
|
|
71
|
+
|
|
72
|
+
# Get generate-all option and detect OS
|
|
73
|
+
generate_all = self.execution_context.get_input("generate-all") or False
|
|
74
|
+
is_windows = platform.system() == "Windows"
|
|
75
|
+
|
|
76
|
+
# Generate Windows scripts if on Windows OR generate-all is True
|
|
77
|
+
if is_windows or generate_all:
|
|
78
|
+
bat_script = self.output_dir.joinpath("env_setup.bat")
|
|
79
|
+
BatEnvSetupScriptGenerator(
|
|
80
|
+
install_dirs=self.execution_context.install_dirs,
|
|
81
|
+
environment=env_vars,
|
|
82
|
+
output_file=bat_script,
|
|
83
|
+
).to_file()
|
|
84
|
+
self._generated_scripts.append(bat_script)
|
|
85
|
+
|
|
86
|
+
ps1_script = self.output_dir.joinpath("env_setup.ps1")
|
|
87
|
+
Ps1EnvSetupScriptGenerator(
|
|
88
|
+
install_dirs=self.execution_context.install_dirs,
|
|
89
|
+
environment=env_vars,
|
|
90
|
+
output_file=ps1_script,
|
|
91
|
+
).to_file()
|
|
92
|
+
self._generated_scripts.append(ps1_script)
|
|
93
|
+
|
|
94
|
+
# Generate Unix/Linux/macOS script if NOT on Windows OR generate-all is True
|
|
95
|
+
if not is_windows or generate_all:
|
|
96
|
+
sh_script = self.output_dir.joinpath("env_setup.sh")
|
|
97
|
+
ShEnvSetupScriptGenerator(
|
|
98
|
+
install_dirs=self.execution_context.install_dirs,
|
|
99
|
+
environment=env_vars,
|
|
100
|
+
output_file=sh_script,
|
|
101
|
+
).to_file()
|
|
102
|
+
self._generated_scripts.append(sh_script)
|
|
103
|
+
|
|
104
|
+
def get_inputs(self) -> List[Path]:
|
|
105
|
+
return []
|
|
106
|
+
|
|
107
|
+
def get_outputs(self) -> List[Path]:
|
|
108
|
+
return self._generated_scripts
|
|
109
|
+
|
|
110
|
+
def get_name(self) -> str:
|
|
111
|
+
return self.__class__.__name__
|
|
112
|
+
|
|
113
|
+
def update_execution_context(self) -> None:
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
def get_needs_dependency_management(self) -> bool:
|
|
117
|
+
return False
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.22.0"
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
from typing import Dict, List
|
|
3
|
-
|
|
4
|
-
from py_app_dev.core.env_setup_scripts import BatEnvSetupScriptGenerator, Ps1EnvSetupScriptGenerator
|
|
5
|
-
from py_app_dev.core.logging import logger
|
|
6
|
-
|
|
7
|
-
from pypeline.domain.execution_context import ExecutionContext
|
|
8
|
-
from pypeline.domain.pipeline import PipelineStep
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def read_dot_env_file(dot_env_file: Path) -> Dict[str, str]:
|
|
12
|
-
"""Reads a .env file and returns a dictionary of environment variables."""
|
|
13
|
-
env_vars = {}
|
|
14
|
-
with dot_env_file.open("r") as f:
|
|
15
|
-
for line in f:
|
|
16
|
-
line = line.strip()
|
|
17
|
-
if line and not line.startswith("#"):
|
|
18
|
-
key, value = line.split("=", 1)
|
|
19
|
-
env_vars[key.strip()] = value.strip().strip('"').strip("'")
|
|
20
|
-
return env_vars
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class GenerateEnvSetupScript(PipelineStep[ExecutionContext]):
|
|
24
|
-
def run(self) -> None:
|
|
25
|
-
logger.info(f"Generating environment setup scripts under {self.output_dir} ...")
|
|
26
|
-
# Read the .env file and set up the environment variables
|
|
27
|
-
dot_env_file = self.execution_context.project_root_dir.joinpath(".env")
|
|
28
|
-
if dot_env_file.exists():
|
|
29
|
-
logger.debug(f"Reading .env file: {dot_env_file}")
|
|
30
|
-
env_vars = read_dot_env_file(dot_env_file)
|
|
31
|
-
else:
|
|
32
|
-
logger.warning(f".env file not found: {dot_env_file}")
|
|
33
|
-
env_vars = {}
|
|
34
|
-
|
|
35
|
-
# Merge execution context environment variables
|
|
36
|
-
env_vars.update(self.execution_context.env_vars)
|
|
37
|
-
# Update the execution context with the merged environment variables to ensure they are available for subsequent steps
|
|
38
|
-
self.execution_context.env_vars.update(env_vars)
|
|
39
|
-
|
|
40
|
-
# Generate the environment setup scripts
|
|
41
|
-
BatEnvSetupScriptGenerator(
|
|
42
|
-
install_dirs=self.execution_context.install_dirs,
|
|
43
|
-
environment=env_vars,
|
|
44
|
-
output_file=self.output_dir.joinpath("env_setup.bat"),
|
|
45
|
-
).to_file()
|
|
46
|
-
Ps1EnvSetupScriptGenerator(
|
|
47
|
-
install_dirs=self.execution_context.install_dirs,
|
|
48
|
-
environment=env_vars,
|
|
49
|
-
output_file=self.output_dir.joinpath("env_setup.ps1"),
|
|
50
|
-
).to_file()
|
|
51
|
-
|
|
52
|
-
def get_inputs(self) -> List[Path]:
|
|
53
|
-
return []
|
|
54
|
-
|
|
55
|
-
def get_outputs(self) -> List[Path]:
|
|
56
|
-
return []
|
|
57
|
-
|
|
58
|
-
def get_name(self) -> str:
|
|
59
|
-
return self.__class__.__name__
|
|
60
|
-
|
|
61
|
-
def update_execution_context(self) -> None:
|
|
62
|
-
pass
|
|
63
|
-
|
|
64
|
-
def get_needs_dependency_management(self) -> bool:
|
|
65
|
-
return False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pypeline_runner-1.22.0 → pypeline_runner-1.23.1}/src/pypeline/kickstart/templates/project/west.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|