diff --git a/docs/pyproject.md b/docs/pyproject.md index 1ab3f0fca5d..aceba01d4c9 100644 --- a/docs/pyproject.md +++ b/docs/pyproject.md @@ -487,9 +487,18 @@ If you publish your package on PyPI, they will appear in the `Project Links` sec ## `self` In this section, you can specify requirements for Poetry itself. +For example, you can specify a constraint for the Poetry version that is required +for this project: -You can also specify that a plugin is required for your project: -You can specify that certain plugins are required for your project: +```toml +[tool.poetry.self] +version = ">=2.0" +``` + +If you are using a Poetry version that is not allowed by this constraint, +an error will be raised. + +Further, you can specify that certain plugins are required for your project: ```toml [tool.poetry.self.plugins] diff --git a/src/poetry/factory.py b/src/poetry/factory.py index d627c537251..b9e14f4add3 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,17 @@ def create_poetry( base_poetry = super().create_poetry(cwd=cwd, with_groups=with_groups) + if version_str := base_poetry.pyproject.poetry_config.get("self", {}).get( + "version" + ): + 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 1f1984bbcb1..d8d2394a38c 100644 --- a/src/poetry/json/schemas/poetry.json +++ b/src/poetry/json/schemas/poetry.json @@ -8,6 +8,11 @@ "type": "object", "description": "Requirements for Poetry itself.", "properties": { + "version": { + "type": "string", + "description": "The version constraint for Poetry itself.", + "$ref": "#/definitions/dependency" + }, "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..5c7f4d0f200 --- /dev/null +++ b/tests/fixtures/self_version_not_ok/pyproject.toml @@ -0,0 +1,8 @@ +[tool.poetry] +package-mode = false + +[tool.poetry.self] +version = "<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..0bfccd13ffb --- /dev/null +++ b/tests/fixtures/self_version_ok/pyproject.toml @@ -0,0 +1,8 @@ +[tool.poetry] +package-mode = false + +[tool.poetry.self] +version = ">=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..2fba9b638f2 --- /dev/null +++ b/tests/json/fixtures/self_invalid_version.toml @@ -0,0 +1,8 @@ +[tool.poetry] +name = "foobar" +version = "0.1.0" +description = "" +authors = ["Your Name "] + +[tool.poetry.self] +version = 2 diff --git a/tests/json/fixtures/self_valid.toml b/tests/json/fixtures/self_valid.toml index b5e4edaccd3..2e6cc6b61ab 100644 --- a/tests/json/fixtures/self_valid.toml +++ b/tests/json/fixtures/self_valid.toml @@ -4,5 +4,8 @@ version = "0.1.0" description = "" authors = ["Your Name "] +[tool.poetry.self] +version = ">=2.0" + [tool.poetry.self.plugins] foo = ">=1.0" diff --git a/tests/json/test_schema.py b/tests/json/test_schema.py index 13580a0aed7..ddb9c125fb4 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.self.version 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: