Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-poetry for openSUSE:Factory checked in at 2023-05-31 21:54:10 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-poetry (Old) and /work/SRC/openSUSE:Factory/.python-poetry.new.1533 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-poetry" Wed May 31 21:54:10 2023 rev:26 rq:1089626 version:1.5.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-poetry/python-poetry.changes 2023-05-23 14:54:48.182575302 +0200 +++ /work/SRC/openSUSE:Factory/.python-poetry.new.1533/python-poetry.changes 2023-05-31 21:54:11.916899423 +0200 @@ -1,0 +2,25 @@ +Mon May 29 17:02:57 UTC 2023 - Ben Greiner <c...@bnavigator.de> + +- Update to 1.5.1 + ## Added + * Improve dependency resolution performance in cases with a lot + of backtracking (#7950). + ## Changed + * Disable wheel content validation during installation (#7987). + ## Fixed + * Fix an issue where partially downloaded wheels were cached + (#7968). + * Fix an issue where poetry run did no longer execute + relative-path scripts (#7963). + * Fix an issue where dependencies were not installed in + in-project environments (#7977). + * Fix an issue where no solution was found for a transitive + dependency on a pre-release of a package (#7978). + * Fix an issue where cached repository packages were incorrectly + parsed, leading to its dependencies being ignored (#7995). + * Fix an issue where an explicit source was ignored so that a + direct origin dependency was used instead (#7973). + * Fix an issue where the installation of big wheels consumed a + lot of memory (#7987). + +------------------------------------------------------------------- Old: ---- poetry-1.5.0.tar.gz New: ---- poetry-1.5.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-poetry.spec ++++++ --- /var/tmp/diff_new_pack.HTePBn/_old 2023-05-31 21:54:12.800904648 +0200 +++ /var/tmp/diff_new_pack.HTePBn/_new 2023-05-31 21:54:12.808904696 +0200 @@ -27,7 +27,7 @@ %{?sle15_python_module_pythons} Name: python-poetry%{psuffix} -Version: 1.5.0 +Version: 1.5.1 Release: 0 Summary: Python dependency management and packaging License: MIT @@ -37,7 +37,7 @@ Source: https://github.com/python-poetry/poetry/archive/%{version}.tar.gz#/poetry-%{version}.tar.gz BuildRequires: %{python_module base >= 3.7} BuildRequires: %{python_module pip} -BuildRequires: %{python_module poetry-core = 1.6.0} +BuildRequires: %{python_module poetry-core = 1.6.1} BuildRequires: fdupes BuildRequires: python-rpm-macros Requires: python-CacheControl >= 0.12.9 @@ -47,8 +47,8 @@ Requires: python-dulwich >= 0.21.2 Requires: python-filelock >= 3.8.0 Requires: python-html5lib >= 1.0 -Requires: python-poetry-core = 1.6.0 -Requires: python-poetry-plugin-export >= 1.3.1 +Requires: python-poetry-core = 1.6.1 +Requires: python-poetry-plugin-export >= 1.4.0 %if 0%{?python_version_nodots} < 310 Requires: python-importlib-metadata >= 4.4 %endif ++++++ poetry-1.5.0.tar.gz -> poetry-1.5.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-1.5.0/CHANGELOG.md new/poetry-1.5.1/CHANGELOG.md --- old/poetry-1.5.0/CHANGELOG.md 2023-05-19 14:17:06.000000000 +0200 +++ new/poetry-1.5.1/CHANGELOG.md 2023-05-29 17:59:47.000000000 +0200 @@ -1,6 +1,40 @@ # Change Log +## [1.5.1] - 2023-05-29 + +### Added + +- Improve dependency resolution performance in cases with a lot of backtracking ([#7950](https://github.com/python-poetry/poetry/pull/7950)). + +### Changed + +- Disable wheel content validation during installation ([#7987](https://github.com/python-poetry/poetry/pull/7987)). + +### Fixed + +- Fix an issue where partially downloaded wheels were cached ([#7968](https://github.com/python-poetry/poetry/pull/7968)). +- Fix an issue where `poetry run` did no longer execute relative-path scripts ([#7963](https://github.com/python-poetry/poetry/pull/7963)). +- Fix an issue where dependencies were not installed in `in-project` environments ([#7977](https://github.com/python-poetry/poetry/pull/7977)). +- Fix an issue where no solution was found for a transitive dependency on a pre-release of a package ([#7978](https://github.com/python-poetry/poetry/pull/7978)). +- Fix an issue where cached repository packages were incorrectly parsed, leading to its dependencies being ignored ([#7995](https://github.com/python-poetry/poetry/pull/7995)). +- Fix an issue where an explicit source was ignored so that a direct origin dependency was used instead ([#7973](https://github.com/python-poetry/poetry/pull/7973)). +- Fix an issue where the installation of big wheels consumed a lot of memory ([#7987](https://github.com/python-poetry/poetry/pull/7987)). + +### Docs + +- Add information about multiple constraints dependencies with direct origin and version dependencies ([#7973](https://github.com/python-poetry/poetry/pull/7973)). + +### poetry-core ([`1.6.1`](https://github.com/python-poetry/poetry-core/releases/tag/1.6.1)) + +- Fix an endless recursion in marker handling ([#593](https://github.com/python-poetry/poetry-core/pull/593)). +- Fix an issue where the wheel tag was not built correctly under certain circumstances ([#591](https://github.com/python-poetry/poetry-core/pull/591)). + +### poetry-plugin-export ([`^1.4.0`](https://github.com/python-poetry/poetry-plugin-export/releases/tag/1.4.0)) + +- Fix an issue where `--extra-index-url` and `--trusted-host` was not generated for sources with priority `explicit` ([#205](https://github.com/python-poetry/poetry-plugin-export/pull/205)). + + ## [1.5.0] - 2023-05-19 ### Added @@ -23,7 +57,7 @@ - **Remove the old deprecated installer and the corresponding setting `experimental.new-installer`** ([#7356](https://github.com/python-poetry/poetry/pull/7356)). - **Introduce `priority` key for sources and deprecate flags `default` and `secondary`** ([#7658](https://github.com/python-poetry/poetry/pull/7658)). -- Deprecate `poetry run <script>` if the script was not previously installed via `poetry install` ([#7606](https://github.com/python-poetry/poetry/pull/7606)). +- Deprecate `poetry run <entry point>` if the entry point was not previously installed via `poetry install` ([#7606](https://github.com/python-poetry/poetry/pull/7606)). - Only write the lock file if the installation succeeds ([#7498](https://github.com/python-poetry/poetry/pull/7498)). - Do not write the unused package category into the lock file ([#7637](https://github.com/python-poetry/poetry/pull/7637)). @@ -1870,7 +1904,8 @@ -[Unreleased]: https://github.com/python-poetry/poetry/compare/1.5.0...master +[Unreleased]: https://github.com/python-poetry/poetry/compare/1.5.1...master +[1.5.1]: https://github.com/python-poetry/poetry/releases/tag/1.5.1 [1.5.0]: https://github.com/python-poetry/poetry/releases/tag/1.5.0 [1.4.2]: https://github.com/python-poetry/poetry/releases/tag/1.4.2 [1.4.1]: https://github.com/python-poetry/poetry/releases/tag/1.4.1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-1.5.0/docs/configuration.md new/poetry-1.5.1/docs/configuration.md --- old/poetry-1.5.0/docs/configuration.md 2023-05-19 14:17:06.000000000 +0200 +++ new/poetry-1.5.1/docs/configuration.md 2023-05-29 17:59:47.000000000 +0200 @@ -202,6 +202,8 @@ **Default**: `true` +**Environment Variable**: `POETRY_INSTALLER_MODERN_INSTALLATION` + *Introduced in 1.4.0* Use a more modern and faster method for package installation. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-1.5.0/docs/dependency-specification.md new/poetry-1.5.1/docs/dependency-specification.md --- old/poetry-1.5.0/docs/dependency-specification.md 2023-05-19 14:17:06.000000000 +0200 +++ new/poetry-1.5.1/docs/dependency-specification.md 2023-05-29 17:59:47.000000000 +0200 @@ -322,6 +322,29 @@ otherwise it will cause an error when resolving dependencies. {{% /note %}} +### Combining git / url / path dependencies with source repositories + +Direct origin (`git`/ `url`/ `path`) dependencies can satisfy the requirement of a dependency that +doesn't explicitly specify a source, even when mutually exclusive markers are used. For instance +in the following example the url package will also be a valid solution for the second requirement: +```toml +foo = [ + { platform = "darwin", url = "https://example.com/example-1.0-py3-none-any.whl" }, + { platform = "linux", version = "^1.0" }, +] +``` + +Sometimes you may instead want to use a direct origin dependency for specific conditions +(i.e. a compiled package that is not available on PyPI for a certain platform/architecture) while +falling back on source repositories in other cases. In this case you should explicitly ask for your +dependency to be satisfied by another `source`. For example: +```toml +foo = [ + { platform = "darwin", url = "https://example.com/foo-1.0.0-py3-none-macosx_11_0_arm64.whl" }, + { platform = "linux", version = "^1.0", source = "pypi" }, +] +``` + ## Expanded dependency specification syntax In the case of more complex dependency specifications, you may find that you diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-1.5.0/docs/repositories.md new/poetry-1.5.1/docs/repositories.md --- old/poetry-1.5.0/docs/repositories.md 2023-05-19 14:17:06.000000000 +0200 +++ new/poetry-1.5.1/docs/repositories.md 2023-05-29 17:59:47.000000000 +0200 @@ -29,7 +29,7 @@ install a dependency to your project for a [simple API repository](#simple-api-repository)? Let's do it. -First, [configure](#project-configuration) the [package source](#package-source) as a [supplemental](#supplemental-package-sources) (or [explicit](#explicit-package-sources)) package source to your +First, [configure](#project-configuration) the [package source](#package-sources) as a [supplemental](#supplemental-package-sources) (or [explicit](#explicit-package-sources)) package source to your project. ```bash @@ -227,7 +227,7 @@ If package sources are configured as explicit, these sources are only searched when a package configuration [explicitly indicates](#package-source-constraint) that it should be found on this package source. -You can configure a package source as an explicit source with `priority = "explicit` in your package source configuration. +You can configure a package source as an explicit source with `priority = "explicit"` in your package source configuration. ```bash poetry source add --priority=explicit foo https://foo.bar/simple/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-1.5.0/poetry.lock new/poetry-1.5.1/poetry.lock --- old/poetry-1.5.0/poetry.lock 2023-05-19 14:17:06.000000000 +0200 +++ new/poetry-1.5.1/poetry.lock 2023-05-29 17:59:47.000000000 +0200 @@ -1074,14 +1074,14 @@ [[package]] name = "poetry-core" -version = "1.6.0" +version = "1.6.1" description = "Poetry PEP 517 Build Backend" category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ - {file = "poetry_core-1.6.0-py3-none-any.whl", hash = "sha256:ff96620579f02ec30ee9f789fa5dfdcddd9ab4ac848394f6f8f2f4e88306b534"}, - {file = "poetry_core-1.6.0.tar.gz", hash = "sha256:a9c7296a12d6c8e4f8aa50a66ef3c967b2b50fba634da144d358e676fad9989f"}, + {file = "poetry_core-1.6.1-py3-none-any.whl", hash = "sha256:70707340447dee0e7f334f9495ae652481c67b32d8d218f296a376ac2ed73573"}, + {file = "poetry_core-1.6.1.tar.gz", hash = "sha256:0f9b0de39665f36d6594657e7d57b6f463cc10f30c28e6d1c3b9ff54c26c9ac3"}, ] [package.dependencies] @@ -1089,19 +1089,19 @@ [[package]] name = "poetry-plugin-export" -version = "1.3.1" +version = "1.4.0" description = "Poetry plugin to export the dependencies to various formats" category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ - {file = "poetry_plugin_export-1.3.1-py3-none-any.whl", hash = "sha256:941d7ba02a59671d6327b16dc6deecc9262477abbc120d728a500cf125bc1e06"}, - {file = "poetry_plugin_export-1.3.1.tar.gz", hash = "sha256:d949742757a8a5f0b5810495bffaf4ed8a767f2e2ffda9887cf72f896deabf84"}, + {file = "poetry_plugin_export-1.4.0-py3-none-any.whl", hash = "sha256:5d9186d6f77cf2bf35fc96bd11fe650cc7656e515b17d99cb65018d50ba22589"}, + {file = "poetry_plugin_export-1.4.0.tar.gz", hash = "sha256:f16974cd9f222d4ef640fa97a8d661b04d4fb339e51da93973f1bc9d578e183f"}, ] [package.dependencies] -poetry = ">=1.3.0,<2.0.0" -poetry-core = ">=1.3.0,<2.0.0" +poetry = ">=1.5.0,<2.0.0" +poetry-core = ">=1.6.0,<2.0.0" [[package]] name = "pre-commit" @@ -1889,4 +1889,4 @@ [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "88510d026496be61125bc3157ece9584978ce58bf42fb551ca6d03066b8d8c61" +content-hash = "e82b06ae38c4bf4fb0366fe8ffa915bd71f04a6710e90750810b388435ac98b3" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-1.5.0/pyproject.toml new/poetry-1.5.1/pyproject.toml --- old/poetry-1.5.0/pyproject.toml 2023-05-19 14:17:06.000000000 +0200 +++ new/poetry-1.5.1/pyproject.toml 2023-05-29 17:59:47.000000000 +0200 @@ -1,6 +1,6 @@ [tool.poetry] name = "poetry" -version = "1.5.0" +version = "1.5.1" description = "Python dependency management and packaging made easy." authors = ["Sébastien Eustace <sebast...@eustace.io>"] maintainers = [ @@ -32,8 +32,8 @@ [tool.poetry.dependencies] python = "^3.7" -poetry-core = "1.6.0" -poetry-plugin-export = "^1.3.1" +poetry-core = "1.6.1" +poetry-plugin-export = "^1.4.0" "backports.cached-property" = { version = "^1.0.2", python = "<3.8" } build = "^0.10.0" cachecontrol = { version = "^0.12.9", extras = ["filecache"] } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-1.5.0/src/poetry/installation/wheel_installer.py new/poetry-1.5.1/src/poetry/installation/wheel_installer.py --- old/poetry-1.5.0/src/poetry/installation/wheel_installer.py 2023-05-19 14:17:06.000000000 +0200 +++ new/poetry-1.5.1/src/poetry/installation/wheel_installer.py 2023-05-29 17:59:47.000000000 +0200 @@ -101,7 +101,10 @@ def install(self, wheel: Path) -> None: with WheelFile.open(wheel) as source: try: - source.validate_record() + # Content validation is temporarily disabled because of + # pypa/installer's out of memory issues with big wheels. See + # https://github.com/python-poetry/poetry/issues/7983 + source.validate_record(validate_contents=False) except _WheelFileValidationError as e: self.invalid_wheels[wheel] = e.issues install( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-1.5.0/src/poetry/mixology/version_solver.py new/poetry-1.5.1/src/poetry/mixology/version_solver.py --- old/poetry-1.5.0/src/poetry/mixology/version_solver.py 2023-05-19 14:17:06.000000000 +0200 +++ new/poetry-1.5.1/src/poetry/mixology/version_solver.py 2023-05-29 17:59:47.000000000 +0200 @@ -1,9 +1,12 @@ from __future__ import annotations +import collections import functools import time from typing import TYPE_CHECKING +from typing import Optional +from typing import Tuple from poetry.core.packages.dependency import Dependency @@ -28,6 +31,11 @@ _conflict = object() +DependencyCacheKey = Tuple[ + str, Optional[str], Optional[str], Optional[str], Optional[str] +] + + class DependencyCache: """ A cache of the valid dependencies. @@ -38,15 +46,58 @@ """ def __init__(self, provider: Provider) -> None: - self.provider = provider - self.cache: dict[ - tuple[str, str | None, str | None, str | None, str | None], - list[DependencyPackage], - ] = {} + self._provider = provider + + # self._cache maps a package name to a stack of cached package lists, + # ordered by the decision level which added them to the cache. This is + # done so that when backtracking we can maintain cache entries from + # previous decision levels, while clearing cache entries from only the + # rolled back levels. + # + # In order to maintain the integrity of the cache, `clear_level()` + # needs to be called in descending order as decision levels are + # backtracked so that the correct items can be popped from the stack. + self._cache: dict[DependencyCacheKey, list[list[DependencyPackage]]] = ( + collections.defaultdict(list) + ) + self._cached_dependencies_by_level: dict[int, list[DependencyCacheKey]] = ( + collections.defaultdict(list) + ) + + self._search_for_cached = functools.lru_cache(maxsize=128)(self._search_for) + + def _search_for( + self, + dependency: Dependency, + key: DependencyCacheKey, + ) -> list[DependencyPackage]: + cache_entries = self._cache[key] + if cache_entries: + packages = [ + p + for p in cache_entries[-1] + if dependency.constraint.allows(p.package.version) + ] + else: + packages = None - self.search_for = functools.lru_cache(maxsize=128)(self._search_for) + # provider.search_for() normally does not include pre-release packages + # (unless requested), but will include them if there are no other + # eligible package versions for a version constraint. + # + # Therefore, if the eligible versions have been filtered down to + # nothing, we need to call provider.search_for() again as it may return + # additional results this time. + if not packages: + packages = self._provider.search_for(dependency) - def _search_for(self, dependency: Dependency) -> list[DependencyPackage]: + return packages + + def search_for( + self, + dependency: Dependency, + decision_level: int, + ) -> list[DependencyPackage]: key = ( dependency.complete_name, dependency.source_type, @@ -55,20 +106,18 @@ dependency.source_subdirectory, ) - packages = self.cache.get(key) - if packages is None: - packages = self.provider.search_for(dependency) - else: - packages = [ - p for p in packages if dependency.constraint.allows(p.package.version) - ] - - self.cache[key] = packages + packages = self._search_for_cached(dependency, key) + if not self._cache[key] or self._cache[key][-1] is not packages: + self._cache[key].append(packages) + self._cached_dependencies_by_level[decision_level].append(key) return packages - def clear(self) -> None: - self.cache.clear() + def clear_level(self, level: int) -> None: + if level in self._cached_dependencies_by_level: + self._search_for_cached.cache_clear() + for key in self._cached_dependencies_by_level.pop(level): + self._cache[key].pop() class VersionSolver: @@ -86,6 +135,9 @@ self._dependency_cache = DependencyCache(provider) self._incompatibilities: dict[str, list[Incompatibility]] = {} self._contradicted_incompatibilities: set[Incompatibility] = set() + self._contradicted_incompatibilities_by_level: dict[ + int, set[Incompatibility] + ] = collections.defaultdict(set) self._solution = PartialSolution() @property @@ -184,6 +236,9 @@ # incompatibility is contradicted as well and there's nothing new we # can deduce from it. self._contradicted_incompatibilities.add(incompatibility) + self._contradicted_incompatibilities_by_level[ + self._solution.decision_level + ].add(incompatibility) return None elif relation == SetRelation.OVERLAPPING: # If more than one term is inconclusive, we can't deduce anything about @@ -202,6 +257,9 @@ return _conflict self._contradicted_incompatibilities.add(incompatibility) + self._contradicted_incompatibilities_by_level[ + self._solution.decision_level + ].add(incompatibility) adverb = "not " if unsatisfied.is_positive() else "" self._log(f"derived: {adverb}{unsatisfied.dependency}") @@ -295,9 +353,16 @@ previous_satisfier_level < most_recent_satisfier.decision_level or most_recent_satisfier.cause is None ): + for level in range( + self._solution.decision_level, previous_satisfier_level, -1 + ): + if level in self._contradicted_incompatibilities_by_level: + self._contradicted_incompatibilities.difference_update( + self._contradicted_incompatibilities_by_level.pop(level), + ) + self._dependency_cache.clear_level(level) + self._solution.backtrack(previous_satisfier_level) - self._contradicted_incompatibilities.clear() - self._dependency_cache.clear() if new_incompatibility: self._add_incompatibility(incompatibility) @@ -395,7 +460,11 @@ if locked: return is_specific_marker, Preference.LOCKED, 1 - num_packages = len(self._dependency_cache.search_for(dependency)) + num_packages = len( + self._dependency_cache.search_for( + dependency, self._solution.decision_level + ) + ) if num_packages < 2: preference = Preference.NO_CHOICE @@ -412,7 +481,9 @@ locked = self._provider.get_locked(dependency) if locked is None: - packages = self._dependency_cache.search_for(dependency) + packages = self._dependency_cache.search_for( + dependency, self._solution.decision_level + ) package = next(iter(packages), None) if package is None: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-1.5.0/src/poetry/puzzle/provider.py new/poetry-1.5.1/src/poetry/puzzle/provider.py --- old/poetry-1.5.0/src/poetry/puzzle/provider.py 2023-05-19 14:17:06.000000000 +0200 +++ new/poetry-1.5.1/src/poetry/puzzle/provider.py 2023-05-29 17:59:47.000000000 +0200 @@ -280,12 +280,8 @@ # # We rely on the VersionSolver resolving direct-origin dependencies first. direct_origin_package = self._direct_origin_packages.get(dependency.name) - if direct_origin_package is not None: - packages = ( - [direct_origin_package] - if dependency.constraint.allows(direct_origin_package.version) - else [] - ) + if direct_origin_package and direct_origin_package.satisfies(dependency): + packages = [direct_origin_package] return PackageCollection(dependency, packages) packages = self._pool.find_packages(dependency) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-1.5.0/src/poetry/repositories/http_repository.py new/poetry-1.5.1/src/poetry/repositories/http_repository.py --- old/poetry-1.5.0/src/poetry/repositories/http_repository.py 2023-05-19 14:17:06.000000000 +0200 +++ new/poetry-1.5.1/src/poetry/repositories/http_repository.py 2023-05-29 17:59:47.000000000 +0200 @@ -76,15 +76,11 @@ @contextmanager def _cached_or_downloaded_file(self, link: Link) -> Iterator[Path]: - filepath = self._authenticator.get_cached_file_for_url(link.url) - if filepath: + self._log(f"Downloading: {link.url}", level="debug") + with temporary_directory() as temp_dir: + filepath = Path(temp_dir) / link.filename + self._download(link.url, filepath) yield filepath - else: - self._log(f"Downloading: {link.url}", level="debug") - with temporary_directory() as temp_dir: - filepath = Path(temp_dir) / link.filename - self._download(link.url, filepath) - yield filepath def _get_info_from_wheel(self, url: str) -> PackageInfo: from poetry.inspection.info import PackageInfo diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-1.5.0/src/poetry/utils/authenticator.py new/poetry-1.5.1/src/poetry/utils/authenticator.py --- old/poetry-1.5.0/src/poetry/utils/authenticator.py 2023-05-19 14:17:06.000000000 +0200 +++ new/poetry-1.5.1/src/poetry/utils/authenticator.py 2023-05-29 17:59:47.000000000 +0200 @@ -19,7 +19,6 @@ from cachecontrol import CacheControlAdapter from cachecontrol.caches import FileCache -from cachecontrol.caches.file_cache import url_to_file_path from filelock import FileLock from poetry.config.config import Config @@ -464,13 +463,6 @@ return selected.certs(config=self._config) return RepositoryCertificateConfig() - def get_cached_file_for_url(self, url: str) -> Path | None: - if self._cache_control is None: - return None - - path = Path(url_to_file_path(url, self._cache_control)) - return path if path.exists() else None - _authenticator: Authenticator | None = None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-1.5.0/src/poetry/utils/env.py new/poetry-1.5.1/src/poetry/utils/env.py --- old/poetry-1.5.0/src/poetry/utils/env.py 2023-05-19 14:17:06.000000000 +0200 +++ new/poetry-1.5.1/src/poetry/utils/env.py 2023-05-29 17:59:47.000000000 +0200 @@ -698,7 +698,7 @@ if not in_venv or env is not None: # Checking if a local virtualenv exists - if self.use_in_project_venv(): + if self.in_project_venv_exists(): venv = self.in_project_venv return VirtualEnv(venv) @@ -736,7 +736,7 @@ venv_path = self._poetry.config.virtualenvs_path env_list = [VirtualEnv(p) for p in sorted(venv_path.glob(f"{venv_name}-py*"))] - if self.use_in_project_venv(): + if self.in_project_venv_exists(): venv = self.in_project_venv env_list.insert(0, VirtualEnv(venv)) return env_list @@ -855,6 +855,14 @@ in_project: bool | None = self._poetry.config.get("virtualenvs.in-project") if in_project is not None: return in_project + + return self.in_project_venv.is_dir() + + def in_project_venv_exists(self) -> bool: + in_project: bool | None = self._poetry.config.get("virtualenvs.in-project") + if in_project is False: + return False + return self.in_project_venv.is_dir() def create_venv( @@ -1246,7 +1254,7 @@ """ Path to current python executable """ - return self._bin(self._executable) + return Path(self._bin(self._executable)) @property def marker_env(self) -> dict[str, Any]: @@ -1314,7 +1322,7 @@ """ # we do not use as_posix() here due to issues with windows pathlib2 # implementation - path = self._bin(self._pip_executable) + path = Path(self._bin(self._pip_executable)) if not path.exists(): return self.pip_embedded return path @@ -1434,7 +1442,7 @@ raise NotImplementedError() def get_pip_command(self, embedded: bool = False) -> list[str]: - if embedded or not self._bin(self._pip_executable).exists(): + if embedded or not Path(self._bin(self._pip_executable)).exists(): return [str(self.python), str(self.pip_embedded)] # run as module so that pip can update itself on Windows return [str(self.python), "-m", "pip"] @@ -1464,7 +1472,7 @@ # embedded pip when pip is not available in the environment return self.get_pip_command() - return [str(self._bin(bin))] + return [self._bin(bin)] def run(self, bin: str, *args: str, **kwargs: Any) -> str: cmd = self.get_command_from_bin(bin) + list(args) @@ -1544,7 +1552,7 @@ self._script_dirs.append(self.userbase / self._script_dirs[0].name) return self._script_dirs - def _bin(self, bin: str) -> Path: + def _bin(self, bin: str) -> str: """ Return path to the given executable. """ @@ -1565,11 +1573,11 @@ bin_path = self._path / bin if bin_path.exists(): - return bin_path + return str(bin_path) - return Path(bin) + return bin - return bin_path + return str(bin_path) def __eq__(self, other: object) -> bool: if not isinstance(other, Env): @@ -1883,8 +1891,8 @@ return super().execute(bin, *args, **kwargs) return 0 - def _bin(self, bin: str) -> Path: - return Path(bin) + def _bin(self, bin: str) -> str: + return bin @contextmanager diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-1.5.0/src/poetry/utils/helpers.py new/poetry-1.5.1/src/poetry/utils/helpers.py --- old/poetry-1.5.0/src/poetry/utils/helpers.py 2023-05-19 14:17:06.000000000 +0200 +++ new/poetry-1.5.1/src/poetry/utils/helpers.py 2023-05-29 17:59:47.000000000 +0200 @@ -121,7 +121,7 @@ # but skip the updating set_indicator = total_size > 1024 * 1024 - with dest.open("wb") as f: + with atomic_open(dest) as f: for chunk in response.iter_content(chunk_size=chunk_size): if chunk: f.write(chunk) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-1.5.0/tests/installation/test_executor.py new/poetry-1.5.1/tests/installation/test_executor.py --- old/poetry-1.5.0/tests/installation/test_executor.py 2023-05-19 14:17:06.000000000 +0200 +++ new/poetry-1.5.1/tests/installation/test_executor.py 2023-05-29 17:59:47.000000000 +0200 @@ -335,6 +335,7 @@ assert error.count("yanked") == 0 +@pytest.mark.skip(reason="https://github.com/python-poetry/poetry/issues/7983") def test_execute_prints_warning_for_invalid_wheels( config: Config, pool: RepositoryPool, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-1.5.0/tests/installation/test_installer.py new/poetry-1.5.1/tests/installation/test_installer.py --- old/poetry-1.5.0/tests/installation/test_installer.py 2023-05-19 14:17:06.000000000 +0200 +++ new/poetry-1.5.1/tests/installation/test_installer.py 2023-05-29 17:59:47.000000000 +0200 @@ -2659,3 +2659,74 @@ source_url=source_url, source_reference=source_reference, ) + + +@pytest.mark.parametrize("env_platform", ["darwin", "linux"]) +def test_explicit_source_dependency_with_direct_origin_dependency( + pool: RepositoryPool, + locker: Locker, + installed: CustomInstalledRepository, + config: Config, + repo: Repository, + package: ProjectPackage, + env_platform: str, +) -> None: + """ + A dependency with explicit source should not be satisfied by + a direct origin dependency even if there is a version match. + """ + package.add_dependency( + Factory.create_dependency( + "demo", + { + "markers": "sys_platform != 'darwin'", + "url": "https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl", + }, + ) + ) + package.add_dependency( + Factory.create_dependency( + "demo", + { + "version": "0.1.0", + "markers": "sys_platform == 'darwin'", + "source": "repo", + }, + ) + ) + # The url demo dependency depends on pendulum. + repo.add_package(get_package("pendulum", "1.4.4")) + repo.add_package(get_package("demo", "0.1.0")) + + installer = Installer( + NullIO(), + MockEnv(platform=env_platform), + package, + locker, + pool, + config, + installed=installed, + executor=Executor( + MockEnv(platform=env_platform), + pool, + config, + NullIO(), + ), + ) + + result = installer.run() + + assert result == 0 + assert isinstance(installer.executor, Executor) + if env_platform == "linux": + assert installer.executor.installations == [ + Package("pendulum", "1.4.4"), + Package( + "demo", + "0.1.0", + source_type="url", + source_url="https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl", + ), + ] + else: + assert installer.executor.installations == [Package("demo", "0.1.0")] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-1.5.0/tests/mixology/version_solver/test_dependency_cache.py new/poetry-1.5.1/tests/mixology/version_solver/test_dependency_cache.py --- old/poetry-1.5.0/tests/mixology/version_solver/test_dependency_cache.py 2023-05-19 14:17:06.000000000 +0200 +++ new/poetry-1.5.1/tests/mixology/version_solver/test_dependency_cache.py 2023-05-29 17:59:47.000000000 +0200 @@ -2,6 +2,7 @@ from copy import deepcopy from typing import TYPE_CHECKING +from unittest import mock from poetry.factory import Factory from poetry.mixology.version_solver import DependencyCache @@ -29,20 +30,20 @@ add_to_repo(repo, "demo", "1.0.0") cache = DependencyCache(provider) - cache.search_for.cache_clear() + cache._search_for_cached.cache_clear() # ensure cache was never hit for both calls - cache.search_for(dependency_pypi) - cache.search_for(dependency_git) - assert not cache.search_for.cache_info().hits + cache.search_for(dependency_pypi, 0) + cache.search_for(dependency_git, 0) + assert not cache._search_for_cached.cache_info().hits # increase test coverage by searching for copies # (when searching for the exact same object, __eq__ is never called) - packages_pypi = cache.search_for(deepcopy(dependency_pypi)) - packages_git = cache.search_for(deepcopy(dependency_git)) + packages_pypi = cache.search_for(deepcopy(dependency_pypi), 0) + packages_git = cache.search_for(deepcopy(dependency_git), 0) - assert cache.search_for.cache_info().hits == 2 - assert cache.search_for.cache_info().currsize == 2 + assert cache._search_for_cached.cache_info().hits == 2 + assert cache._search_for_cached.cache_info().currsize == 2 assert len(packages_pypi) == len(packages_git) == 1 assert packages_pypi != packages_git @@ -60,6 +61,65 @@ assert package_git.package.source_resolved_reference == MOCK_DEFAULT_GIT_REVISION +def test_solver_dependency_cache_pulls_from_prior_level_cache( + root: ProjectPackage, provider: Provider, repo: Repository +) -> None: + dependency_pypi = Factory.create_dependency("demo", ">=0.1.0") + dependency_pypi_constrained = Factory.create_dependency("demo", ">=0.1.0,<2.0.0") + root.add_dependency(dependency_pypi) + root.add_dependency(dependency_pypi_constrained) + add_to_repo(repo, "demo", "1.0.0") + + wrapped_provider = mock.Mock(wraps=provider) + cache = DependencyCache(wrapped_provider) + cache._search_for_cached.cache_clear() + + # On first call, provider.search_for() should be called and the cache + # populated. + cache.search_for(dependency_pypi, 0) + assert len(wrapped_provider.search_for.mock_calls) == 1 + assert ("demo", None, None, None, None) in cache._cache + assert ("demo", None, None, None, None) in cache._cached_dependencies_by_level[0] + assert cache._search_for_cached.cache_info().hits == 0 + assert cache._search_for_cached.cache_info().misses == 1 + + # On second call at level 1, neither provider.search_for() nor + # cache._search_for_cached() should have been called again, and the cache + # should remain the same. + cache.search_for(dependency_pypi, 1) + assert len(wrapped_provider.search_for.mock_calls) == 1 + assert ("demo", None, None, None, None) in cache._cache + assert ("demo", None, None, None, None) in cache._cached_dependencies_by_level[0] + assert set(cache._cached_dependencies_by_level.keys()) == {0} + assert cache._search_for_cached.cache_info().hits == 1 + assert cache._search_for_cached.cache_info().misses == 1 + + # On third call at level 2 with an updated constraint for the `demo` + # package should not call provider.search_for(), but should call + # cache._search_for_cached() and update the cache. + cache.search_for(dependency_pypi_constrained, 2) + assert len(wrapped_provider.search_for.mock_calls) == 1 + assert ("demo", None, None, None, None) in cache._cache + assert ("demo", None, None, None, None) in cache._cached_dependencies_by_level[0] + assert ("demo", None, None, None, None) in cache._cached_dependencies_by_level[2] + assert set(cache._cached_dependencies_by_level.keys()) == {0, 2} + assert cache._search_for_cached.cache_info().hits == 1 + assert cache._search_for_cached.cache_info().misses == 2 + + # Clearing the level 2 and level 1 caches should invalidate the lru_cache + # on cache.search_for and wipe out the level 2 cache while preserving the + # level 0 cache. + cache.clear_level(2) + cache.clear_level(1) + cache.search_for(dependency_pypi, 0) + assert len(wrapped_provider.search_for.mock_calls) == 1 + assert ("demo", None, None, None, None) in cache._cache + assert ("demo", None, None, None, None) in cache._cached_dependencies_by_level[0] + assert set(cache._cached_dependencies_by_level.keys()) == {0} + assert cache._search_for_cached.cache_info().hits == 0 + assert cache._search_for_cached.cache_info().misses == 1 + + def test_solver_dependency_cache_respects_subdirectories( root: ProjectPackage, provider: Provider, repo: Repository ) -> None: @@ -84,20 +144,20 @@ root.add_dependency(dependency_one_copy) cache = DependencyCache(provider) - cache.search_for.cache_clear() + cache._search_for_cached.cache_clear() # ensure cache was never hit for both calls - cache.search_for(dependency_one) - cache.search_for(dependency_one_copy) - assert not cache.search_for.cache_info().hits + cache.search_for(dependency_one, 0) + cache.search_for(dependency_one_copy, 0) + assert not cache._search_for_cached.cache_info().hits # increase test coverage by searching for copies # (when searching for the exact same object, __eq__ is never called) - packages_one = cache.search_for(deepcopy(dependency_one)) - packages_one_copy = cache.search_for(deepcopy(dependency_one_copy)) + packages_one = cache.search_for(deepcopy(dependency_one), 0) + packages_one_copy = cache.search_for(deepcopy(dependency_one_copy), 0) - assert cache.search_for.cache_info().hits == 2 - assert cache.search_for.cache_info().currsize == 2 + assert cache._search_for_cached.cache_info().hits == 2 + assert cache._search_for_cached.cache_info().currsize == 2 assert len(packages_one) == len(packages_one_copy) == 1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-1.5.0/tests/puzzle/test_provider.py new/poetry-1.5.1/tests/puzzle/test_provider.py --- old/poetry-1.5.0/tests/puzzle/test_provider.py 2023-05-19 14:17:06.000000000 +0200 +++ new/poetry-1.5.1/tests/puzzle/test_provider.py 2023-05-29 17:59:47.000000000 +0200 @@ -113,7 +113,7 @@ Dependency("foo", ">=2"), URLDependency("foo", SOME_URL), [Package("foo", "3")], - [], + [Package("foo", "3")], ), ( Dependency("foo", ">=1", extras=["bar"]), @@ -722,3 +722,38 @@ spy.assert_called() else: spy.assert_not_called() + + +def test_source_dependency_is_satisfied_by_direct_origin( + provider: Provider, repository: Repository +) -> None: + direct_origin_package = Package("foo", "1.1", source_type="url") + repository.add_package(Package("foo", "1.0")) + provider._direct_origin_packages = {"foo": direct_origin_package} + dep = Dependency("foo", ">=1") + + assert provider.search_for(dep) == [direct_origin_package] + + +def test_explicit_source_dependency_is_not_satisfied_by_direct_origin( + provider: Provider, repository: Repository +) -> None: + repo_package = Package("foo", "1.0") + repository.add_package(repo_package) + provider._direct_origin_packages = {"foo": Package("foo", "1.1", source_type="url")} + dep = Dependency("foo", ">=1") + dep.source_name = repository.name + + assert provider.search_for(dep) == [repo_package] + + +def test_source_dependency_is_not_satisfied_by_incompatible_direct_origin( + provider: Provider, repository: Repository +) -> None: + repo_package = Package("foo", "2.0") + repository.add_package(repo_package) + provider._direct_origin_packages = {"foo": Package("foo", "1.0", source_type="url")} + dep = Dependency("foo", ">=2") + dep.source_name = repository.name + + assert provider.search_for(dep) == [repo_package] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-1.5.0/tests/puzzle/test_solver.py new/poetry-1.5.1/tests/puzzle/test_solver.py --- old/poetry-1.5.0/tests/puzzle/test_solver.py 2023-05-19 14:17:06.000000000 +0200 +++ new/poetry-1.5.1/tests/puzzle/test_solver.py 2023-05-29 17:59:47.000000000 +0200 @@ -1183,6 +1183,64 @@ ) +def test_solver_with_dependency_and_prerelease_sub_dependencies_increasing_constraints( + solver: Solver, + repo: Repository, + package: ProjectPackage, + mocker: MockerFixture, +) -> None: + """Regression test to ensure the solver eventually uses pre-release + dependencies if the package is progressively constrained enough. + + This is different from test_solver_with_dependency_and_prerelease_sub_dependencies + above because it also has a wildcard dependency on B at the root level. + This causes the solver to first narrow B's candidate versions down to + {0.9.0} at an early level, then eventually down to the empty set once A's + dependencies are processed at a later level. + + Once the candidate version set is narrowed down to the empty set, the + solver should re-evaluate available candidate versions from the source, but + include pre-release versions this time as there are no other options. + """ + # Note: The order matters here; B must be added before A or the solver + # evaluates A first and we don't encounter the issue. This is a bit + # fragile, but the mock call assertions ensure this ordering is maintained. + package.add_dependency(Factory.create_dependency("B", "*")) + package.add_dependency(Factory.create_dependency("A", "*")) + + package_a = get_package("A", "1.0") + package_a.add_dependency(Factory.create_dependency("B", ">0.9.0")) + + repo.add_package(package_a) + repo.add_package(get_package("B", "0.9.0")) + package_b = get_package("B", "1.0.0.dev4") + repo.add_package(package_b) + + search_for_spy = mocker.spy(solver._provider, "search_for") + transaction = solver.solve() + + check_solver_result( + transaction, + [ + {"job": "install", "package": package_b}, + {"job": "install", "package": package_a}, + ], + ) + + # The assertions below aren't really the point of this test, but are just + # being used to ensure the dependency resolution ordering remains the same. + search_calls = [ + call[1][0] + for call in search_for_spy.mock_calls + if call[1][0].name in ("a", "b") + ] + assert search_calls == [ + Dependency("a", "*"), + Dependency("b", "*"), + Dependency("b", ">0.9.0"), + ] + + def test_solver_circular_dependency( solver: Solver, repo: Repository, package: ProjectPackage ) -> None: @@ -3379,6 +3437,69 @@ solver.solve() +@pytest.mark.parametrize("explicit_source", [True, False]) +def test_solver_cannot_choose_url_dependency_for_explicit_source( + solver: Solver, + repo: Repository, + package: ProjectPackage, + explicit_source: bool, +) -> None: + """A direct origin dependency cannot satisfy a version dependency with an explicit + source. (It can satisfy a version dependency without an explicit source.) + """ + package.add_dependency( + Factory.create_dependency( + "demo", + { + "markers": "sys_platform != 'darwin'", + "url": "https://foo.bar/distributions/demo-0.1.0-py2.py3-none-any.whl", + }, + ) + ) + package.add_dependency( + Factory.create_dependency( + "demo", + { + "version": "0.1.0", + "markers": "sys_platform == 'darwin'", + "source": "repo" if explicit_source else None, + }, + ) + ) + + package_pendulum = get_package("pendulum", "1.4.4") + package_demo = get_package("demo", "0.1.0") + package_demo_url = Package( + "demo", + "0.1.0", + source_type="url", + source_url="https://foo.bar/distributions/demo-0.1.0-py2.py3-none-any.whl", + ) + # The url demo dependency depends on pendulum. + repo.add_package(package_pendulum) + repo.add_package(package_demo) + + transaction = solver.solve() + + if explicit_source: + # direct origin cannot satisfy explicit source + # -> package_demo MUST be included + expected = [ + {"job": "install", "package": package_pendulum}, + {"job": "install", "package": package_demo_url}, + {"job": "install", "package": package_demo}, + ] + else: + # direct origin can satisfy dependency without source + # -> package_demo NEED NOT (but could) be included + expected = [ + {"job": "install", "package": package_pendulum}, + {"job": "install", "package": package_demo_url}, + ] + + check_solver_result(transaction, expected) + + def test_solver_should_not_update_same_version_packages_if_installed_has_no_source_type( package: ProjectPackage, repo: Repository, pool: RepositoryPool, io: NullIO ) -> None: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-1.5.0/tests/utils/test_authenticator.py new/poetry-1.5.1/tests/utils/test_authenticator.py --- old/poetry-1.5.0/tests/utils/test_authenticator.py 2023-05-19 14:17:06.000000000 +0200 +++ new/poetry-1.5.1/tests/utils/test_authenticator.py 2023-05-29 17:59:47.000000000 +0200 @@ -627,22 +627,6 @@ assert not three.password -def test_authenticator_get_cached_file_for_url__cache_miss(config: Config) -> None: - authenticator = Authenticator(config, NullIO()) - assert ( - authenticator.get_cached_file_for_url("https://foo.bar/cache/miss.whl") is None - ) - - -def test_authenticator_get_cached_file_for_url__cache_hit(config: Config) -> None: - authenticator = Authenticator(config, NullIO()) - url = "https://foo.bar/files/foo-0.1.0.tar.gz" - - authenticator._cache_control.set(url, b"hello") - - assert authenticator.get_cached_file_for_url(url) - - @pytest.mark.parametrize( ("ca_cert", "client_cert", "result"), [ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/poetry-1.5.0/tests/utils/test_env.py new/poetry-1.5.1/tests/utils/test_env.py --- old/poetry-1.5.0/tests/utils/test_env.py 2023-05-19 14:17:06.000000000 +0200 +++ new/poetry-1.5.1/tests/utils/test_env.py 2023-05-29 17:59:47.000000000 +0200 @@ -1828,3 +1828,10 @@ active_python = EnvManager(poetry)._detect_active_python() assert active_python == wrapped_python + + +def test_command_from_bin_preserves_relative_path(manager: EnvManager) -> None: + # https://github.com/python-poetry/poetry/issues/7959 + env = manager.get() + command = env.get_command_from_bin("./foo.py") + assert command == ["./foo.py"]