diff --git a/docs/dependency-specification.md b/docs/dependency-specification.md index adc4c000a66..635b40f9921 100644 --- a/docs/dependency-specification.md +++ b/docs/dependency-specification.md @@ -554,3 +554,17 @@ markers = "platform_python_implementation == 'CPython'" The same information is still present, and ends up providing the exact same specification. It's simply split into multiple, slightly more readable, lines. + +### Handling of pre-releases + +Per default, Poetry will prefer stable releases and only choose a pre-release +if no stable release satisfies a version constraint. In some cases, this may result in +a solution containing pre-releases even if another solution without pre-releases exists. + +If you want to disallow pre-releases for a specific dependency, +you can set `allow-prereleases` to `false`. In this case, dependency resolution will +fail if there is no solution without choosing a pre-release. + +If you want to prefer the latest version of a dependency even if it is a pre-release, +you can set `allow-prereleases` to `true` so that Poetry makes no distinction +between stable and pre-release versions during dependency resolution. diff --git a/poetry.lock b/poetry.lock index 63c85ff1d4a..7d4c038ca43 100644 --- a/poetry.lock +++ b/poetry.lock @@ -975,7 +975,7 @@ develop = false type = "git" url = "https://github.com/python-poetry/poetry-core.git" reference = "main" -resolved_reference = "589ecb03f28b30d9a604dc13b51fba55f3981ef5" +resolved_reference = "616d7bfaf018d50bd09bc24c630b697b6368d5a3" [[package]] name = "pre-commit" diff --git a/src/poetry/console/commands/add.py b/src/poetry/console/commands/add.py index 055c6c42005..3c2cc38d0f6 100644 --- a/src/poetry/console/commands/add.py +++ b/src/poetry/console/commands/add.py @@ -219,7 +219,7 @@ def handle(self) -> int: requirements = self._determine_requirements( packages, - allow_prereleases=self.option("allow-prereleases"), + allow_prereleases=self.option("allow-prereleases") or None, source=self.option("source"), ) diff --git a/src/poetry/console/commands/init.py b/src/poetry/console/commands/init.py index b1244965fbf..61a44178590 100644 --- a/src/poetry/console/commands/init.py +++ b/src/poetry/console/commands/init.py @@ -303,7 +303,7 @@ def _generate_choice_list( def _determine_requirements( self, requires: list[str], - allow_prereleases: bool = False, + allow_prereleases: bool | None = None, source: str | None = None, is_interactive: bool | None = None, ) -> list[dict[str, Any]]: @@ -440,7 +440,7 @@ def _find_best_version_for_package( self, name: str, required_version: str | None = None, - allow_prereleases: bool = False, + allow_prereleases: bool | None = None, source: str | None = None, ) -> tuple[str, str]: from poetry.version.version_selector import VersionSelector diff --git a/src/poetry/console/commands/show.py b/src/poetry/console/commands/show.py index b47b4574666..b9e0490ddaa 100644 --- a/src/poetry/console/commands/show.py +++ b/src/poetry/console/commands/show.py @@ -546,7 +546,7 @@ def find_latest_package( provider = Provider(root, self.poetry.pool, NullIO()) return provider.search_for_direct_origin_dependency(dep) - allow_prereleases = False + allow_prereleases: bool | None = None for dep in requires: if dep.name == package.name: allow_prereleases = dep.allows_prereleases() diff --git a/src/poetry/repositories/repository.py b/src/poetry/repositories/repository.py index f1586090b14..9bd4ac54f14 100644 --- a/src/poetry/repositories/repository.py +++ b/src/poetry/repositories/repository.py @@ -60,6 +60,8 @@ def find_packages(self, dependency: Dependency) -> list[Package]: level="debug", ) + if allow_prereleases is False: # in contrast to None! + return packages return packages or ignored_pre_release_packages def has_package(self, package: Package) -> bool: diff --git a/src/poetry/version/version_selector.py b/src/poetry/version/version_selector.py index 195e83bd1a8..34859d5bd52 100644 --- a/src/poetry/version/version_selector.py +++ b/src/poetry/version/version_selector.py @@ -17,7 +17,7 @@ def find_best_candidate( self, package_name: str, target_package_version: str | None = None, - allow_prereleases: bool = False, + allow_prereleases: bool | None = None, source: str | None = None, ) -> Package | None: """ diff --git a/tests/repositories/test_repository.py b/tests/repositories/test_repository.py index 551691d9ff7..9e894fa027e 100644 --- a/tests/repositories/test_repository.py +++ b/tests/repositories/test_repository.py @@ -10,13 +10,46 @@ @pytest.fixture(scope="module") -def black_repository() -> Repository: +def repository() -> Repository: repo = Repository("repo") + + # latest version pre-release + repo.add_package(get_package("foo", "1.0")) + repo.add_package(get_package("foo", "2.0.b0")) + + # latest version yanked repo.add_package(get_package("black", "19.10b0")) repo.add_package(get_package("black", "21.11b0", yanked="reason")) + return repo +@pytest.mark.parametrize( + ("allow_prereleases", "constraint", "expected"), + [ + (None, ">=1.0", ["1.0"]), + (False, ">=1.0", ["1.0"]), + (True, ">=1.0", ["1.0", "2.0.b0"]), + (None, ">=1.5", ["2.0.b0"]), + (False, ">=1.5", []), + (True, ">=1.5", ["2.0.b0"]), + ], +) +def test_find_packages_allow_prereleases( + repository: Repository, + allow_prereleases: bool | None, + constraint: str, + expected: list[str], +) -> None: + packages = repository.find_packages( + Factory.create_dependency( + "foo", {"version": constraint, "allow-prereleases": allow_prereleases} + ) + ) + + assert [str(p.version) for p in packages] == expected + + @pytest.mark.parametrize( ["constraint", "expected"], [ @@ -29,11 +62,9 @@ def black_repository() -> Repository: ], ) def test_find_packages_yanked( - black_repository: Repository, constraint: str, expected: list[str] + repository: Repository, constraint: str, expected: list[str] ) -> None: - packages = black_repository.find_packages( - Factory.create_dependency("black", constraint) - ) + packages = repository.find_packages(Factory.create_dependency("black", constraint)) assert [str(p.version) for p in packages] == expected @@ -46,13 +77,13 @@ def test_find_packages_yanked( ], ) def test_package_yanked( - black_repository: Repository, + repository: Repository, package_name: str, version: str, yanked: bool, yanked_reason: str, ) -> None: - package = black_repository.package(package_name, Version.parse(version)) + package = repository.package(package_name, Version.parse(version)) assert package.name == package_name assert str(package.version) == version