diff --git a/docs/pyproject.md b/docs/pyproject.md index de68fe7b9a1..39b7b192253 100644 --- a/docs/pyproject.md +++ b/docs/pyproject.md @@ -843,6 +843,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 8675a438651..b404f1da79a 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.local_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 b8fa520478d..b35743fffb4 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 edbbe50186b..9d8a791ccb9 100644 --- a/tests/json/test_schema.py +++ b/tests/json/test_schema.py @@ -56,6 +56,14 @@ def test_self_valid() -> None: assert Factory.validate(toml) == {"errors": [], "warnings": []} +def test_self_invalid_version() -> None: + toml: dict[str, Any] = TOMLFile(FIXTURE_DIR / "self_invalid_version.toml").read() + assert Factory.validate(toml) == { + "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() assert Factory.validate(toml) == { diff --git a/tests/test_factory.py b/tests/test_factory.py index dee66ba6a9f..5f36026aed1 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: