From 81e2be2204e25f3c5d83cf18a4382ab5595c853e Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Tue, 3 Nov 2026 15:55:22 +0100 Subject: [PATCH] fix(dev-python): hand requires-python to the remote script with a valid backreference 76adaeb9e8 wrote the regex_search group reference as '\1' inside the folded task scalar. Jinja unescapes string literals, so the filter received \x01, rejected it as an unknown argument and failed the dev-python task on every remote host. The literal is now '\\1', and the unit test renders the task's environment expression the way Ansible does instead of only checking that the variable is named. Co-Authored-By: Claude Opus 5.5 --- roles/dev-python/tasks/00_core.yml | 2 +- .../roles/dev-python/files/test_install.py | 29 ++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/roles/dev-python/tasks/00_core.yml b/roles/dev-python/tasks/00_core.yml index 7310e0e4c4..e095bb29d8 100644 --- a/roles/dev-python/tasks/00_core.yml +++ b/roles/dev-python/tasks/00_core.yml @@ -10,7 +10,7 @@ SKIP_DOCKER_APT_SANITIZER: "1" INFINITO_PYTHON_REQUIRES: >- {{ lookup('file', lookup('path_absolute', 'pyproject.toml')) - | regex_search('(?m)^requires-python\s*=\s*"([^"]+)"', '\1') + | regex_search('(?m)^requires-python\\s*=\\s*"([^"]+)"', '\\1') | first }} become: true diff --git a/tests/unit/python/roles/dev-python/files/test_install.py b/tests/unit/python/roles/dev-python/files/test_install.py index 3559dee048..54207452df 100644 --- a/tests/unit/python/roles/dev-python/files/test_install.py +++ b/tests/unit/python/roles/dev-python/files/test_install.py @@ -6,7 +6,11 @@ import tomllib import unittest from pathlib import Path +import jinja2 +from ansible.plugins.filter.core import regex_search + from utils.cache.files import PROJECT_ROOT, read_text +from utils.cache.yaml import load_yaml_str SCRIPT = PROJECT_ROOT / "roles/dev-python/files/shell/install.sh" ROLE_TASKS = PROJECT_ROOT / "roles/dev-python/tasks/00_core.yml" @@ -199,7 +203,30 @@ class TestBoundsFromRequiresPython(unittest.TestCase): requires=">=3.12,<3.14", ) self.assertEqual(result.stdout, "12 13") - self.assertIn("INFINITO_PYTHON_REQUIRES", read_text(str(ROLE_TASKS))) + + def test_the_role_renders_requires_python_from_pyproject(self) -> None: + task = next( + task + for task in load_yaml_str(read_text(str(ROLE_TASKS))) + if "ansible.builtin.script" in task + ) + expression = task["environment"]["INFINITO_PYTHON_REQUIRES"] + + def lookup(name: str, term: str) -> str: + return { + "path_absolute": lambda: str(PROJECT_ROOT / term), + "file": lambda: read_text(term), + }[name]() + + environment = jinja2.Environment( + autoescape=jinja2.select_autoescape(default_for_string=False) + ) + environment.filters["regex_search"] = regex_search + environment.globals["lookup"] = lookup + spec = tomllib.loads(read_text(str(PROJECT_ROOT / "pyproject.toml")))[ + "project" + ]["requires-python"] + self.assertEqual(environment.from_string(expression).render(), spec) def test_an_unsupported_requires_python_form_fails_loudly(self) -> None: result = _call("true", requires=">=3.11")