From 382d9b889d274c2b330b12cbc400371f835c72ad Mon Sep 17 00:00:00 2001 From: Gil Forcada Codinachs Date: Fri, 25 Sep 2026 15:39:42 +0200 Subject: [PATCH 1/6] chore: allow to pass input to system call --- src/plone/meta/shared/call.py | 6 ++++-- tests/test_shared_call.py | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/plone/meta/shared/call.py b/src/plone/meta/shared/call.py index 304bab6f..2441a4eb 100644 --- a/src/plone/meta/shared/call.py +++ b/src/plone/meta/shared/call.py @@ -10,12 +10,14 @@ def abort(exitcode): sys.exit(exitcode) -def call(*args, capture_output=False, cwd=None, allowed_return_codes=(0,)): +def call(*args, capture_output=False, cwd=None, allowed_return_codes=(0,), input=None): """Call `args` as a subprocess. If it fails exit the process. """ - result = subprocess.run(args, capture_output=capture_output, text=True, cwd=cwd) + result = subprocess.run( + args, capture_output=capture_output, text=True, cwd=cwd, input=input + ) if result.returncode not in allowed_return_codes: print(f"ERROR: exit code {result.returncode}.") print("output:") diff --git a/tests/test_shared_call.py b/tests/test_shared_call.py index 201b9cc4..e8c7b02e 100644 --- a/tests/test_shared_call.py +++ b/tests/test_shared_call.py @@ -23,7 +23,7 @@ def test_passes_args_to_subprocess(self, mock_run): ) call("git", "status") mock_run.assert_called_once_with( - ("git", "status"), capture_output=False, text=True, cwd=None + ("git", "status"), capture_output=False, text=True, cwd=None, input=None ) @patch("plone.meta.shared.call.subprocess.run") @@ -33,7 +33,7 @@ def test_capture_output(self, mock_run): ) call("cmd", capture_output=True) mock_run.assert_called_once_with( - ("cmd",), capture_output=True, text=True, cwd=None + ("cmd",), capture_output=True, text=True, cwd=None, input=None ) @patch("plone.meta.shared.call.subprocess.run") @@ -41,7 +41,7 @@ def test_cwd_kwarg(self, mock_run): mock_run.return_value = subprocess.CompletedProcess(args=["cmd"], returncode=0) call("cmd", cwd="/tmp") mock_run.assert_called_once_with( - ("cmd",), capture_output=False, text=True, cwd="/tmp" + ("cmd",), capture_output=False, text=True, cwd="/tmp", input=None ) @patch("plone.meta.shared.call.abort") From 33d2b4e3dd9d1510dc46f91dc880378e8ad2b80f Mon Sep 17 00:00:00 2001 From: Gil Forcada Codinachs Date: Fri, 25 Sep 2026 15:45:20 +0200 Subject: [PATCH 2/6] chore: get maximal python version from test matrix --- src/plone/meta/config_package.py | 16 ++++++++++++++++ tests/test_package_config_ci.py | 22 ++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/plone/meta/config_package.py b/src/plone/meta/config_package.py index 10499775..a5bd8683 100755 --- a/src/plone/meta/config_package.py +++ b/src/plone/meta/config_package.py @@ -341,6 +341,22 @@ def _minimal_python_version(self): min_version = py_version return min_version + def _maximum_python_version(self): + """Detect the maximum Python version to be used in tooling (black, pre-commit...). + + Returns something like "3.14". + """ + options = self._get_options_for("tox", ("test_matrix",)) + test_matrix = get_test_matrix(options.get("test_matrix")) + max_version = None + for python_versions in test_matrix.values(): + for py_version in python_versions: + if py_version.startswith("pypy"): + continue + if max_version is None or Version(py_version) > Version(max_version): + max_version = py_version + return max_version + def _setuptools_upper_bound(self): """Determine upper bound for setuptools in build-system. diff --git a/tests/test_package_config_ci.py b/tests/test_package_config_ci.py index f513beb5..2855d029 100644 --- a/tests/test_package_config_ci.py +++ b/tests/test_package_config_ci.py @@ -222,6 +222,28 @@ def test_python_version(self, package_config, matrix, output): assert result == output +class TestMaximalPythonVersion: + @pytest.mark.parametrize( + ["matrix", "output"], + [ + [None, "3.14"], + [{"6.0": ["*"]}, "3.13"], + [{"6.1": ["*"]}, "3.13"], + [{"6.2": ["*"]}, "3.14"], + [{"6.2": ["3.13"]}, "3.13"], + [{"6.2": ["3.13"], "6.1": ["3.9"]}, "3.13"], + [{"6.2": ["3.13", "3.10"], "5.2": ["3.8"]}, "3.13"], + [{"6.2": ["pypy3.10", "3.11"]}, "3.11"], + ], + ) + def test_python_version(self, package_config, matrix, output): + if matrix: + package_config.meta_cfg["tox"]["test_matrix"] = matrix + + result = package_config._maximum_python_version() + assert result == output + + class TestHandleGhActions: def test_returns_empty_when_disabled(self, package_config): package_config.meta_cfg["tox"]["use_test_matrix"] = False From 6beef3f479a559c5595acd4608b255b78f352f06 Mon Sep 17 00:00:00 2001 From: Gil Forcada Codinachs Date: Mon, 28 Sep 2026 09:54:22 +0200 Subject: [PATCH 3/6] chore: new dependency --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 0383eeed..2127f491 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ dependencies = [ "tox", "validate-pyproject[all]", "zest.releaser", + "check-python-versions", ] [project.optional-dependencies] From 74096f474e10bc807272a7e3a9b520fd2b19c6cc Mon Sep 17 00:00:00 2001 From: Gil Forcada Codinachs Date: Mon, 28 Sep 2026 09:55:19 +0200 Subject: [PATCH 4/6] chore: keep dependencies alphabetically sorted --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2127f491..2049a5a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,15 +32,15 @@ classifiers = [ "Operating System :: OS Independent", ] dependencies = [ - "Jinja2", + "check-python-versions", "editorconfig", + "Jinja2", "packaging", "pyyaml", "tomlkit", "tox", "validate-pyproject[all]", "zest.releaser", - "check-python-versions", ] [project.optional-dependencies] From 23c228aefeec2244daa817eed8dfa9832da71bf7 Mon Sep 17 00:00:00 2001 From: Gil Forcada Codinachs Date: Mon, 28 Sep 2026 09:50:23 +0200 Subject: [PATCH 5/6] feat: update python classifiers --- src/plone/meta/config_package.py | 29 +++++++++++++++++++++++++++++ tests/test_package_config_ci.py | 17 +++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/plone/meta/config_package.py b/src/plone/meta/config_package.py index a5bd8683..dfb84bf1 100755 --- a/src/plone/meta/config_package.py +++ b/src/plone/meta/config_package.py @@ -503,6 +503,8 @@ def pyproject_toml(self): "pyproject.toml.j2", **options, ) + self._update_python_versions_on_pyproject_toml() + files.append(filename) return files @@ -520,6 +522,33 @@ def _get_manual_metadata(self): metadata = actual_pyproject[start_marker:end_marker] return metadata + def _update_python_versions_on_pyproject_toml(self): + """Update trove classifiers and `requires-python` on pyproject.toml + + Based on the test matrix. + """ + min_version = self._minimal_python_version() + max_version = self._maximum_python_version() + + config = (self.path / "pyproject.toml").read_text() + if "classifiers" not in config: + self.print_warning( + "Python versions", + "Project metadata, classifiers at least, are not found on pyproject.toml," + "thus they can not be updated to state.", + ) + return + + with change_dir(self.path): + call( + "check-python-versions", + "--update", + f"{min_version}-{max_version}", + "--only", + "pyproject.toml", + input="y\ny\n", + ) + def tox(self): options = self._get_options_for( "tox", diff --git a/tests/test_package_config_ci.py b/tests/test_package_config_ci.py index 2855d029..75e3fc40 100644 --- a/tests/test_package_config_ci.py +++ b/tests/test_package_config_ci.py @@ -188,6 +188,23 @@ def test_metadata_is_kept(self, package_config): for line in text: assert line in final_toml_text + def test_update_classifiers(self, package_config): + pyproject_file_path = package_config.path / "pyproject.toml" + text = [ + "# START-MARKER-MANUAL-CONFIG", + "[project]", + 'name="random-project"', + "classifiers = []", + "# END-MARKER-MANUAL-CONFIG", + ] + pyproject_file_path.write_text("\n".join(text)) + # set a specific test matrix to ensure test stability + package_config.meta_cfg["tox"]["test_matrix"] = {"6.2": ["3.14", "3.13"]} + package_config.pyproject_toml() + final_toml_text = pyproject_file_path.read_text() + assert '"Programming Language :: Python :: 3.13",' in final_toml_text + assert '"Programming Language :: Python :: 3.14",' in final_toml_text + class TestSetuptoolsUpperBound: @pytest.mark.parametrize(["is_native", "expected"], [[True, "82"], [False, "83"]]) From a83c7e08b4acb354b13e1a06c1237d4f9513e368 Mon Sep 17 00:00:00 2001 From: Gil Forcada Codinachs Date: Mon, 28 Sep 2026 09:51:32 +0200 Subject: [PATCH 6/6] Add news entry --- news/381.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/381.feature diff --git a/news/381.feature b/news/381.feature new file mode 100644 index 00000000..efb3d734 --- /dev/null +++ b/news/381.feature @@ -0,0 +1 @@ +Update trove classifiers to match the python test matrix @gforcada