Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d9e76a838 | ||
|
|
034cbde2f7 | ||
|
|
e0abe6f176 | ||
|
|
15f6837dc2 | ||
|
|
b4dda91e24 | ||
|
|
3e21e774f7 | ||
|
|
7c11215e57 |
@@ -27,7 +27,7 @@ jobs:
|
|||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
. .venv/bin/activate
|
. .venv/bin/activate
|
||||||
python3 -m devx.tools.check_test_speed --max-seconds 60 --max-single-seconds 2.0
|
python3 -m devx.tools.check_test_speed --max-seconds 4 --max-single-seconds 0.5
|
||||||
- name: Documentation coverage check
|
- name: Documentation coverage check
|
||||||
env:
|
env:
|
||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
|
|||||||
@@ -2,6 +2,25 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [0.8.5] - 2026-06-23
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Retry pip install with --ignore-installed only on failure
|
||||||
|
|
||||||
|
## [0.8.4] - 2026-06-23
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Add --ignore-installed to pip in CI to bypass debian packages
|
||||||
|
|
||||||
|
## [0.8.3] - 2026-06-23
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Lower check_test_speed threshold to 4 seconds
|
||||||
|
- Pass --break-system-packages to pip in CI environments
|
||||||
|
|
||||||
## [0.8.2] - 2026-06-23
|
## [0.8.2] - 2026-06-23
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
+1
-1
@@ -4,4 +4,4 @@
|
|||||||
# Aligned with CI (ci.yml uses same thresholds).
|
# Aligned with CI (ci.yml uses same thresholds).
|
||||||
set -e
|
set -e
|
||||||
export PYTHONPATH=src
|
export PYTHONPATH=src
|
||||||
python3 -m devx.tools.check_test_speed --max-seconds 10 --max-single-seconds 0.5
|
python3 -m devx.tools.check_test_speed --max-seconds 4 --max-single-seconds 0.5
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
"""devx — reusable development and CI/CD tools for oblachno-oss projects."""
|
"""devx — reusable development and CI/CD tools for oblachno-oss projects."""
|
||||||
|
|
||||||
__version__ = "0.8.2"
|
__version__ = "0.8.5"
|
||||||
|
|||||||
+18
-3
@@ -26,9 +26,24 @@ def _run(cmd: list[str]) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def _install_python_deps(bin_dir: str, extras: str = "dev") -> None:
|
def _install_python_deps(bin_dir: str, extras: str = "dev") -> None:
|
||||||
"""Install the project with the specified extras in editable mode."""
|
"""Install the project with the specified extras in editable mode.
|
||||||
|
|
||||||
|
In CI (system Python with PIP_BREAK_SYSTEM_PACKAGES=1), a first attempt
|
||||||
|
uses --break-system-packages. If that fails (e.g. debian-installed
|
||||||
|
packages without RECORD files), retry with --ignore-installed to skip
|
||||||
|
uninstalling system packages entirely.
|
||||||
|
"""
|
||||||
pip = str(Path(bin_dir) / "pip")
|
pip = str(Path(bin_dir) / "pip")
|
||||||
_run([pip, "install", "-e", f".[{extras}]"])
|
cmd = [pip, "install", "-e", f".[{extras}]"]
|
||||||
|
if os.environ.get("PIP_BREAK_SYSTEM_PACKAGES") == "1":
|
||||||
|
cmd.append("--break-system-packages")
|
||||||
|
result = subprocess.run(cmd, check=False) # nosec B603
|
||||||
|
if result.returncode != 0 and os.environ.get("PIP_BREAK_SYSTEM_PACKAGES") == "1":
|
||||||
|
click.echo(" Retrying with --ignore-installed to bypass system packages...")
|
||||||
|
cmd.append("--ignore-installed")
|
||||||
|
_run(cmd)
|
||||||
|
elif result.returncode != 0:
|
||||||
|
raise subprocess.CalledProcessError(result.returncode, cmd)
|
||||||
|
|
||||||
|
|
||||||
def _install_pre_commit_hooks(bin_dir: str) -> None:
|
def _install_pre_commit_hooks(bin_dir: str) -> None:
|
||||||
@@ -40,7 +55,7 @@ def _install_pre_commit_hooks(bin_dir: str) -> None:
|
|||||||
|
|
||||||
def _install_ansible_collections(bin_dir: str) -> None:
|
def _install_ansible_collections(bin_dir: str) -> None:
|
||||||
"""Install required Ansible Galaxy collections if requirements exist."""
|
"""Install required Ansible Galaxy collections if requirements exist."""
|
||||||
galaxy = str(Path(bin_dir) / "ansible-galaxy")
|
galaxy = shutil.which("ansible-galaxy") or str(Path(bin_dir) / "ansible-galaxy")
|
||||||
requirements = Path("ansible/requirements.yml")
|
requirements = Path("ansible/requirements.yml")
|
||||||
if not requirements.exists():
|
if not requirements.exists():
|
||||||
click.echo(" ansible/requirements.yml not found — skipping collections.")
|
click.echo(" ansible/requirements.yml not found — skipping collections.")
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
"""Unit tests for devx.tools.setup."""
|
"""Unit tests for devx.tools.setup."""
|
||||||
|
|
||||||
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
@@ -32,20 +33,48 @@ class TestRun:
|
|||||||
|
|
||||||
|
|
||||||
class TestInstallPythonDeps:
|
class TestInstallPythonDeps:
|
||||||
@patch("devx.tools.setup._run")
|
@patch("devx.tools.setup.subprocess.run")
|
||||||
def test_install_dev(self, mock_run: MagicMock) -> None:
|
def test_install_dev(self, mock_run: MagicMock) -> None:
|
||||||
|
mock_run.return_value = MagicMock(returncode=0)
|
||||||
_install_python_deps(".venv/bin", "dev")
|
_install_python_deps(".venv/bin", "dev")
|
||||||
mock_run.assert_called_once_with([".venv/bin/pip", "install", "-e", ".[dev]"])
|
mock_run.assert_called_once_with([".venv/bin/pip", "install", "-e", ".[dev]"], check=False)
|
||||||
|
|
||||||
@patch("devx.tools.setup._run")
|
@patch("devx.tools.setup.subprocess.run")
|
||||||
def test_install_ci(self, mock_run: MagicMock) -> None:
|
def test_install_ci(self, mock_run: MagicMock) -> None:
|
||||||
|
mock_run.return_value = MagicMock(returncode=0)
|
||||||
_install_python_deps(".venv/bin", "ci")
|
_install_python_deps(".venv/bin", "ci")
|
||||||
mock_run.assert_called_once_with([".venv/bin/pip", "install", "-e", ".[ci]"])
|
mock_run.assert_called_once_with([".venv/bin/pip", "install", "-e", ".[ci]"], check=False)
|
||||||
|
|
||||||
|
@patch("devx.tools.setup.subprocess.run")
|
||||||
|
def test_install_custom_extras(self, mock_run: MagicMock) -> None:
|
||||||
|
mock_run.return_value = MagicMock(returncode=0)
|
||||||
|
_install_python_deps(".venv/bin", "ci,lint")
|
||||||
|
mock_run.assert_called_once_with([".venv/bin/pip", "install", "-e", ".[ci,lint]"], check=False)
|
||||||
|
|
||||||
|
@patch("devx.tools.setup.subprocess.run")
|
||||||
|
def test_install_with_break_system_packages(self, mock_run: MagicMock) -> None:
|
||||||
|
mock_run.return_value = MagicMock(returncode=0)
|
||||||
|
with patch.dict(os.environ, {"PIP_BREAK_SYSTEM_PACKAGES": "1"}):
|
||||||
|
_install_python_deps(".venv/bin", "ci")
|
||||||
|
mock_run.assert_called_once_with(
|
||||||
|
[".venv/bin/pip", "install", "-e", ".[ci]", "--break-system-packages"], check=False
|
||||||
|
)
|
||||||
|
|
||||||
@patch("devx.tools.setup._run")
|
@patch("devx.tools.setup._run")
|
||||||
def test_install_custom_extras(self, mock_run: MagicMock) -> None:
|
@patch("devx.tools.setup.subprocess.run")
|
||||||
_install_python_deps(".venv/bin", "ci,lint")
|
def test_install_retry_with_ignore_installed(self, mock_subprocess: MagicMock, mock_run: MagicMock) -> None:
|
||||||
mock_run.assert_called_once_with([".venv/bin/pip", "install", "-e", ".[ci,lint]"])
|
mock_subprocess.return_value = MagicMock(returncode=1)
|
||||||
|
with patch.dict(os.environ, {"PIP_BREAK_SYSTEM_PACKAGES": "1"}):
|
||||||
|
_install_python_deps(".venv/bin", "ci")
|
||||||
|
mock_run.assert_called_once_with(
|
||||||
|
[".venv/bin/pip", "install", "-e", ".[ci]", "--break-system-packages", "--ignore-installed"]
|
||||||
|
)
|
||||||
|
|
||||||
|
@patch("devx.tools.setup.subprocess.run")
|
||||||
|
def test_install_failure_without_break_system(self, mock_run: MagicMock) -> None:
|
||||||
|
mock_run.return_value = MagicMock(returncode=1)
|
||||||
|
with pytest.raises(subprocess.CalledProcessError):
|
||||||
|
_install_python_deps(".venv/bin", "ci")
|
||||||
|
|
||||||
|
|
||||||
class TestInstallPreCommitHooks:
|
class TestInstallPreCommitHooks:
|
||||||
@@ -60,8 +89,9 @@ class TestInstallPreCommitHooks:
|
|||||||
|
|
||||||
|
|
||||||
class TestInstallAnsibleCollections:
|
class TestInstallAnsibleCollections:
|
||||||
|
@patch("devx.tools.setup.shutil.which", return_value="/usr/local/bin/ansible-galaxy")
|
||||||
@patch("devx.tools.setup._run")
|
@patch("devx.tools.setup._run")
|
||||||
def test_installs_from_requirements(self, mock_run: MagicMock, tmp_path: Path) -> None:
|
def test_installs_from_requirements(self, mock_run: MagicMock, mock_which: MagicMock, tmp_path: Path) -> None:
|
||||||
req = tmp_path / "ansible" / "requirements.yml"
|
req = tmp_path / "ansible" / "requirements.yml"
|
||||||
req.parent.mkdir(parents=True)
|
req.parent.mkdir(parents=True)
|
||||||
req.write_text("collections: []")
|
req.write_text("collections: []")
|
||||||
|
|||||||
Reference in New Issue
Block a user