From 14c30ed0897461184aaea3512fe70dc175044bd7 Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Tue, 6 Oct 2026 12:03:44 -0400 Subject: [PATCH] chore(deps): add PyYAML to the test extra (#1076) The yaml.safe_load cross-checks in tests/project skipped on every CI leg, since each unit-tests.yml job installs .[test] and PyYAML was in no extra. Test-only: pcapkit never imports yaml. Rewrites the docstrings and skip messages that said PyYAML is in no extra, prose only. --- pyproject.toml | 7 +++++++ tests/project/test_bump_version.py | 2 +- tests/project/test_release_gates.py | 12 ++++++------ .../project/test_unittest_ordering_shards.py | 2 +- tests/project/test_workflow_apt_timeouts.py | 19 ++++++++++--------- 5 files changed, 25 insertions(+), 17 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b921684c3b..01edd40316 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -333,6 +333,13 @@ test = [ # failure: it names the file and prints the diff, where the skip it replaced # said nothing at all. "isort", + # The second opinion on the hand-rolled YAML scanners: the ``yaml.safe_load`` + # cross-checks in ``tests/project/test_project_status.py``, + # ``test_release_gates.py``, ``test_bump_version.py`` and + # ``test_workflow_apt_timeouts.py`` skip without it. + # Test-only, as ``pcapkit`` never imports ``yaml``. Unpinned, since + # ``safe_load`` is all they use. + "PyYAML", ] # setuptools specific diff --git a/tests/project/test_bump_version.py b/tests/project/test_bump_version.py index c7fb5647c8..a75dc9edea 100644 --- a/tests/project/test_bump_version.py +++ b/tests/project/test_bump_version.py @@ -226,7 +226,7 @@ def test_the_rewritten_date_is_a_string_and_not_a_yaml_date(self) -> None: try: import yaml except ImportError: # pragma: no cover - self.skipTest('PyYAML is not in the test extra; the textual form is ' + self.skipTest('PyYAML, from the test extra, is not installed; the textual form is ' 'asserted by test_the_release_date_moves_with_the_version') rewritten = bump_version.plan_citation(FIXTURE, '1.5.0b5', '2026-09-22') diff --git a/tests/project/test_release_gates.py b/tests/project/test_release_gates.py index 2f3928842c..d8ac4cd787 100644 --- a/tests/project/test_release_gates.py +++ b/tests/project/test_release_gates.py @@ -47,9 +47,9 @@ since #887 removed their required reviewers. The question would resurface only if a matrix job were gated by its own environment again. -:mod:`yaml` is not in the ``test`` extra, so the scanner in this module is +:mod:`yaml` comes only with the ``test`` extra, so the scanner in this module is hand-rolled and dependency-free, and :class:`TestYAMLAgreesWithTheScanner` checks -it against :func:`yaml.safe_load` only when PyYAML happens to be installed. The +it against :func:`yaml.safe_load`, skipping on an install without that extra. The same split as ``test_the_rewritten_date_is_a_string_and_not_a_yaml_date`` in :file:`tests/project/test_bump_version.py`: the assertion that must run everywhere does not need the dependency, and the one that needs it guards the substitute. @@ -240,7 +240,7 @@ def job_blocks(text: 'str') -> 'dict[str, str]': """``{job name: the job's own lines}`` for a workflow's top-level ``jobs:``. Indentation-based rather than a YAML parse, so that this module keeps working - on a fresh clone where PyYAML is not installed. + on a fresh clone without the ``test`` extra, where PyYAML is not installed. :class:`TestYAMLAgreesWithTheScanner` is what stops the two from drifting. """ @@ -2692,7 +2692,7 @@ def test_the_same_job_to_environment_mapping(self) -> None: try: import yaml except ImportError: # pragma: no cover - self.skipTest('PyYAML is not in the test extra; the textual scan is ' + self.skipTest('PyYAML, from the test extra, is not installed; the textual scan is ' 'asserted by TestPublishingJobsAreGated') parsed = yaml.safe_load(self.text) @@ -2711,7 +2711,7 @@ def test_the_same_job_to_needs_mapping(self) -> None: try: import yaml except ImportError: # pragma: no cover - self.skipTest('PyYAML is not in the test extra; the textual scan is ' + self.skipTest('PyYAML, from the test extra, is not installed; the textual scan is ' 'asserted by TestGatedJobsDependOnGithub') parsed = yaml.safe_load(self.text) @@ -2738,7 +2738,7 @@ def test_the_same_job_to_if_mapping(self) -> None: try: import yaml except ImportError: # pragma: no cover - self.skipTest('PyYAML is not in the test extra; the textual scan is ' + self.skipTest('PyYAML, from the test extra, is not installed; the textual scan is ' 'asserted by TestEvidenceBasedGating') parsed = yaml.safe_load(self.text) diff --git a/tests/project/test_unittest_ordering_shards.py b/tests/project/test_unittest_ordering_shards.py index a660c008fe..83aa3b505d 100644 --- a/tests/project/test_unittest_ordering_shards.py +++ b/tests/project/test_unittest_ordering_shards.py @@ -16,7 +16,7 @@ ``--exclude``, i.e. runs exactly what it ran before the split. The workflow is read with a small hand-rolled scan rather than :mod:`yaml`, -which is in no extra of :file:`pyproject.toml`; the same split +which comes only with the ``test`` extra of :file:`pyproject.toml`; the same split :file:`tests/project/test_workflow_apt_timeouts.py` uses. """ diff --git a/tests/project/test_workflow_apt_timeouts.py b/tests/project/test_workflow_apt_timeouts.py index c86db71f6f..fcb80edd7e 100644 --- a/tests/project/test_workflow_apt_timeouts.py +++ b/tests/project/test_workflow_apt_timeouts.py @@ -39,12 +39,13 @@ is enforced by the Actions runner, so nothing here can exercise it. The test is an assertion over the file, as #641's gating test is. -:mod:`yaml` is in no extra of :file:`pyproject.toml`, so the scanner here is -hand-rolled and dependency-free, and :class:`TestYAMLAgreesWithTheScanner` -checks it against :func:`yaml.safe_load` only when PyYAML happens to be -installed. That is the same split :file:`tests/project/test_release_gates.py` -uses, and for the same reason: the assertion that must run everywhere does not -need the dependency, and the one that needs it guards the substitute. +:mod:`yaml` comes only with the ``test`` extra of :file:`pyproject.toml`, so +the scanner here is hand-rolled and dependency-free, and +:class:`TestYAMLAgreesWithTheScanner` checks it against :func:`yaml.safe_load`, +skipping on an install without that extra. That is the same split +:file:`tests/project/test_release_gates.py` uses, and for the same reason: the +assertion that must run everywhere does not need the dependency, and the one +that needs it guards the substitute. Comments are stripped before the ``apt-get`` marker is looked for. Without that, prose *about* apt -- including the long note #974 added above the very step this @@ -147,8 +148,8 @@ def steps(path: 'pathlib.Path') -> 'list[Step]': """Every step of the workflow at ``path``. An indentation scan rather than a YAML parse, for the reason this module's - docstring gives: PyYAML is in no extra, so the assertion that must run - everywhere cannot depend on it. + docstring gives: PyYAML comes only with the ``test`` extra, so the + assertion that must run everywhere cannot depend on it. A step runs from its ``- key:`` line to just before the next line that is neither blank nor indented past the ``-``, which is what ends a block in @@ -278,7 +279,7 @@ def setUp(self) -> None: try: import yaml # noqa: F401 except ImportError: # pragma: no cover - self.skipTest('PyYAML is in no extra of pyproject.toml; scanner unchecked here') + self.skipTest('PyYAML, from the test extra, is not installed; scanner unchecked here') def test_yaml_finds_the_same_apt_steps(self) -> None: """:func:`yaml.safe_load` agrees on which steps invoke apt."""