From 05397130cda7b1603c08521a483fdba08f769335 Mon Sep 17 00:00:00 2001 From: Gil Forcada Codinachs Date: Tue, 17 Mar 2026 12:58:49 +0200 Subject: [PATCH 1/4] feat(pyproject): ensure manual metadata is kept --- src/plone/meta/config_package.py | 17 +++++++++++++++++ src/plone/meta/default/pyproject.toml.j2 | 2 ++ tests/conftest.py | 14 +++++++++++++- tests/test_package_config_ci.py | 19 ++++++++++++++++++- 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/plone/meta/config_package.py b/src/plone/meta/config_package.py index 92c20e22..9fdb3839 100755 --- a/src/plone/meta/config_package.py +++ b/src/plone/meta/config_package.py @@ -6,6 +6,7 @@ from functools import cached_property from importlib.metadata import version from packaging.version import Version +from pathlib import Path import argparse import collections @@ -425,6 +426,8 @@ def pyproject_toml(self): "If you want to use Towncrier, you have to create a 'news/' folder manually.", ) + options["project_metadata"] = self._get_manual_metadata() + filename = self.copy_with_meta( "pyproject.toml.j2", **options, @@ -432,6 +435,20 @@ def pyproject_toml(self): files.append(filename) return files + def _get_manual_metadata(self): + metadata = "" + suffix = "MARKER-MANUAL-CONFIG" + pyproject_path = (Path(self.path) / "pyproject.toml") + if not pyproject_path.exists(): + return "" + actual_pyproject = pyproject_path.read_text() + start_marker = actual_pyproject.find(f"# START-{suffix}") + end_marker = actual_pyproject.find(f"# END-{suffix}") + if start_marker > -1 and end_marker > -1: + end_marker = end_marker + len(f"# END-{suffix}") + metadata = actual_pyproject[start_marker:end_marker] + return metadata + def tox(self): options = self._get_options_for( "tox", diff --git a/src/plone/meta/default/pyproject.toml.j2 b/src/plone/meta/default/pyproject.toml.j2 index cfad7dcc..07ba76ac 100644 --- a/src/plone/meta/default/pyproject.toml.j2 +++ b/src/plone/meta/default/pyproject.toml.j2 @@ -1,6 +1,8 @@ [build-system] requires = ["setuptools>=68.2,<%(setuptools_upper_bound)s", "wheel"] +%(project_metadata)s + {% if news_folder_exists %} [tool.towncrier] directory = "news/" diff --git a/tests/conftest.py b/tests/conftest.py index 1c65e73a..2abf5009 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -44,11 +44,23 @@ def mock_args(mock_git_repo): @pytest.fixture -def package_config(meta_toml_factory, mock_args): +def pyproject_toml(mock_git_repo): + """Create an empty pyproject.toml""" + + def _create(): + toml_path = mock_git_repo / "pyproject.toml" + toml_path.touch() + + return _create + + +@pytest.fixture +def package_config(pyproject_toml, meta_toml_factory, mock_args): """Create a PackageConfiguration with mocked subprocess calls.""" from plone.meta.config_package import PackageConfiguration meta_toml_factory() # creates default .meta.toml + pyproject_toml() # creates an empty pyproject.toml with ( patch( "plone.meta.config_package.git_server_url", diff --git a/tests/test_package_config_ci.py b/tests/test_package_config_ci.py index 229b71c6..2b566b99 100644 --- a/tests/test_package_config_ci.py +++ b/tests/test_package_config_ci.py @@ -79,9 +79,11 @@ def test_flake8(self, package_config): class TestPyproject: def test_minimal_files(self, package_config): + pyproject_file_path = package_config.path / "pyproject.toml" result = package_config.pyproject_toml() assert len(result) == 1 - assert (package_config.path / "pyproject.toml").exists() + text = pyproject_file_path.read_text() + assert len(text.splitlines()) > 50 def test_if_news_folder_exists(self, package_config): (package_config.path / "news").mkdir(parents=True, exist_ok=True) @@ -96,6 +98,21 @@ def test_if_changes_md_exists(self, package_config): assert len(result) == 2 assert (package_config.path / "news" / ".changelog_template.jinja").exists() + def test_metadata_is_kept(self, package_config): + pyproject_file_path = package_config.path / "pyproject.toml" + text = [ + "# START-MARKER-MANUAL-CONFIG", + "[project]", + 'name="random-project"', + "# END-MARKER-MANUAL-CONFIG", + ] + pyproject_file_path.write_text("\n".join(text)) + package_config.pyproject_toml() + final_toml_text = pyproject_file_path.read_text() + assert len(final_toml_text.splitlines()) > len(text) + for line in text: + assert line in final_toml_text + class TestSetuptoolsUpperBound: @pytest.mark.parametrize(["is_native", "expected"], [[True, "82"], [False, "83"]]) From 3be76aed6302888fbec2e26b5e1f8cbaaa4416c7 Mon Sep 17 00:00:00 2001 From: Gil Forcada Codinachs Date: Tue, 17 Mar 2026 16:26:45 +0200 Subject: [PATCH 2/4] feat(docs): explain the pyproject.toml markers --- docs/sources/reference/generated-files.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/docs/sources/reference/generated-files.md b/docs/sources/reference/generated-files.md index f3e1644c..3a21cc61 100644 --- a/docs/sources/reference/generated-files.md +++ b/docs/sources/reference/generated-files.md @@ -109,12 +109,21 @@ Includes pyupgrade, isort, black, zpretty, flake8, codespell, check-manifest, py **Template:** {file}`pyproject.toml.j2` -**Purpose:** Python tooling configuration for isort, black, codespell, check-manifest, and z3c.dependencychecker. -Also includes towncrier configuration if a {file}`news/` folder exists. +**Purpose:** Python distribution metadata and tooling configuration. + +Contrary to all other generated files, {file}`pyproject.toml` keeps whatever is within some special markers. + +These markers are meant to be around the `[project]` table, it should look like: + +```toml +# START-MARKER-MANUAL-CONFIG +[project] +name = "plone.meta" +version = "1.2.4" +... +# END-MARKER-MANUAL-CONFIG +``` -:::{note} -plone.meta overwrites {file}`pyproject.toml` completely, like all other generated files. All customization must go through {file}`.meta.toml`. -::: ## tox.ini From e4f814bea3a7e496416fd3063c3eab4e396bd2c1 Mon Sep 17 00:00:00 2001 From: Gil Forcada Codinachs Date: Wed, 18 Mar 2026 11:16:36 +0200 Subject: [PATCH 3/4] Add news entry --- news/315.feature.2 | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/315.feature.2 diff --git a/news/315.feature.2 b/news/315.feature.2 new file mode 100644 index 00000000..23aca729 --- /dev/null +++ b/news/315.feature.2 @@ -0,0 +1 @@ +Ensure the `[project]` table is not removed from `pyproject.toml` @gforcada From 4d80a29d78584f037a7728b80259e718c2adf409 Mon Sep 17 00:00:00 2001 From: Maurits van Rees Date: Thu, 19 Mar 2026 14:03:49 +0100 Subject: [PATCH 4/4] format --- src/plone/meta/config_package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plone/meta/config_package.py b/src/plone/meta/config_package.py index 9fdb3839..3f4b3a2a 100755 --- a/src/plone/meta/config_package.py +++ b/src/plone/meta/config_package.py @@ -438,7 +438,7 @@ def pyproject_toml(self): def _get_manual_metadata(self): metadata = "" suffix = "MARKER-MANUAL-CONFIG" - pyproject_path = (Path(self.path) / "pyproject.toml") + pyproject_path = Path(self.path) / "pyproject.toml" if not pyproject_path.exists(): return "" actual_pyproject = pyproject_path.read_text()