From ea8b19c9cb5398ebebf6a49fa8ada12f41ee2f3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sat, 13 Jul 2024 21:46:19 +0200 Subject: [PATCH] introduce `tool.poetry.requires-poetry` to define a constraint for the Poetry version the project is compatible with (#9547) --- docs/pyproject.md | 11 +++++++++++ src/poetry/factory.py | 12 ++++++++++++ src/poetry/json/schemas/poetry.json | 5 +++++ .../self_version_not_ok/pyproject.toml | 6 ++++++ tests/fixtures/self_version_ok/pyproject.toml | 6 ++++++ tests/json/fixtures/self_invalid_version.toml | 6 ++++++ tests/json/fixtures/self_valid.toml | 1 + tests/json/test_schema.py | 9 +++++++++ tests/test_factory.py | 18 ++++++++++++++++++ 9 files changed, 74 insertions(+) create mode 100644 tests/fixtures/self_version_not_ok/pyproject.toml create mode 100644 tests/fixtures/self_version_ok/pyproject.toml create mode 100644 tests/json/fixtures/self_invalid_version.toml diff --git a/docs/pyproject.md b/docs/pyproject.md index b204ca12407..1333777c729 100644 --- a/docs/pyproject.md +++ b/docs/pyproject.md @@ -484,6 +484,17 @@ any custom url in the `urls` section. If you publish your package on PyPI, they will appear in the `Project Links` section. +## `requires-poetry` + +A constraint for the Poetry version that is required for this project. +If you are using a Poetry version that is not allowed by this constraint, +an error will be raised. + +```toml +[tool.poetry] +requires-poetry = ">=2.0" +``` + ## `requires-plugins` In this section, you can specify that certain plugins are required for your project: diff --git a/src/poetry/factory.py b/src/poetry/factory.py index d627c537251..4d7a07ad8b9 100644 --- a/src/poetry/factory.py +++ b/src/poetry/factory.py @@ -10,9 +10,12 @@ from cleo.io.null_io import NullIO from packaging.utils import canonicalize_name +from poetry.core.constraints.version import Version +from poetry.core.constraints.version import parse_constraint from poetry.core.factory import Factory as BaseFactory from poetry.core.packages.dependency_group import MAIN_GROUP +from poetry.__version__ import __version__ from poetry.config.config import Config from poetry.exceptions import PoetryException from poetry.json import validate_object @@ -56,6 +59,15 @@ def create_poetry( base_poetry = super().create_poetry(cwd=cwd, with_groups=with_groups) + if version_str := base_poetry.pyproject.poetry_config.get("requires-poetry"): + version_constraint = parse_constraint(version_str) + version = Version.parse(__version__) + if not version_constraint.allows(version): + raise PoetryException( + f"This project requires Poetry {version_constraint}," + f" but you are using Poetry {version}" + ) + poetry_file = base_poetry.pyproject_path locker = Locker(poetry_file.parent / "poetry.lock", base_poetry.pyproject.data) diff --git a/src/poetry/json/schemas/poetry.json b/src/poetry/json/schemas/poetry.json index 3b2f2b5ac11..f1bc5fa774b 100644 --- a/src/poetry/json/schemas/poetry.json +++ b/src/poetry/json/schemas/poetry.json @@ -4,6 +4,11 @@ "type": "object", "required": [], "properties": { + "requires-poetry": { + "type": "string", + "description": "The version constraint for Poetry itself.", + "$ref": "#/definitions/dependency" + }, "requires-plugins": { "type": "object", "description": "Poetry plugins that are required for this project.", diff --git a/tests/fixtures/self_version_not_ok/pyproject.toml b/tests/fixtures/self_version_not_ok/pyproject.toml new file mode 100644 index 00000000000..19e752a0642 --- /dev/null +++ b/tests/fixtures/self_version_not_ok/pyproject.toml @@ -0,0 +1,6 @@ +[tool.poetry] +package-mode = false +requires-poetry = "<1.2" + +[tool.poetry.dependencies] +python = "^3.8" diff --git a/tests/fixtures/self_version_ok/pyproject.toml b/tests/fixtures/self_version_ok/pyproject.toml new file mode 100644 index 00000000000..9347cb92342 --- /dev/null +++ b/tests/fixtures/self_version_ok/pyproject.toml @@ -0,0 +1,6 @@ +[tool.poetry] +package-mode = false +requires-poetry = ">=1.2" + +[tool.poetry.dependencies] +python = "^3.8" diff --git a/tests/json/fixtures/self_invalid_version.toml b/tests/json/fixtures/self_invalid_version.toml new file mode 100644 index 00000000000..ddc07369b03 --- /dev/null +++ b/tests/json/fixtures/self_invalid_version.toml @@ -0,0 +1,6 @@ +[tool.poetry] +name = "foobar" +version = "0.1.0" +description = "" +authors = ["Your Name "] +requires-poetry = 2 diff --git a/tests/json/fixtures/self_valid.toml b/tests/json/fixtures/self_valid.toml index 298a50b7831..44f9c200fbb 100644 --- a/tests/json/fixtures/self_valid.toml +++ b/tests/json/fixtures/self_valid.toml @@ -3,6 +3,7 @@ name = "foobar" version = "0.1.0" description = "" authors = ["Your Name "] +requires-poetry = ">=2.0" [tool.poetry.requires-plugins] foo = ">=1.0" diff --git a/tests/json/test_schema.py b/tests/json/test_schema.py index 88b2e930faf..ef8285fe3ed 100644 --- a/tests/json/test_schema.py +++ b/tests/json/test_schema.py @@ -61,6 +61,15 @@ def test_self_valid() -> None: assert Factory.validate(content) == {"errors": [], "warnings": []} +def test_self_invalid_version() -> None: + toml: dict[str, Any] = TOMLFile(FIXTURE_DIR / "self_invalid_version.toml").read() + content = toml["tool"]["poetry"] + assert Factory.validate(content) == { + "errors": ["data.requires-poetry must be string"], + "warnings": [], + } + + def test_self_invalid_plugin() -> None: toml: dict[str, Any] = TOMLFile(FIXTURE_DIR / "self_invalid_plugin.toml").read() content = toml["tool"]["poetry"] diff --git a/tests/test_factory.py b/tests/test_factory.py index 5cc433bb3c0..25e9e23666d 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -14,6 +14,7 @@ from poetry.core.packages.package import Package from poetry.core.packages.vcs_dependency import VCSDependency +from poetry.__version__ import __version__ from poetry.exceptions import PoetryException from poetry.factory import Factory from poetry.plugins.plugin import Plugin @@ -230,6 +231,23 @@ def test_create_poetry_non_package_mode(fixture_dir: FixtureDirGetter) -> None: assert not poetry.is_package_mode +def test_create_poetry_version_ok(fixture_dir: FixtureDirGetter) -> None: + io = BufferedIO() + Factory().create_poetry(fixture_dir("self_version_ok"), io=io) + + assert io.fetch_output() == "" + assert io.fetch_error() == "" + + +def test_create_poetry_version_not_ok(fixture_dir: FixtureDirGetter) -> None: + with pytest.raises(PoetryException) as e: + Factory().create_poetry(fixture_dir("self_version_not_ok")) + assert ( + str(e.value) + == f"This project requires Poetry <1.2, but you are using Poetry {__version__}" + ) + + def test_poetry_with_default_source_legacy( fixture_dir: FixtureDirGetter, with_simple_keyring: None ) -> None: