diff --git a/poetry/mixology/version_solver.py b/poetry/mixology/version_solver.py index e19fe577d17..728f730aa9d 100755 --- a/poetry/mixology/version_solver.py +++ b/poetry/mixology/version_solver.py @@ -12,6 +12,7 @@ from poetry.core.packages import ProjectPackage from poetry.core.semver import Version from poetry.core.semver import VersionRange +from poetry.packages.dependency_package import DependencyPackage from .failure import SolveFailure from .incompatibility import Incompatibility @@ -386,6 +387,16 @@ def _get_min(dependency): except IndexError: version = None + if dependency.name not in self._use_latest: + # prefer locked version of compatible (not exact same) dependency; + # required in order to not unnecessarily update dependencies with + # extras, e.g. "coverage" vs. "coverage[toml]" + locked = self._get_locked(dependency, allow_similar=True) + if locked is not None: + version = next( + (p for p in packages if p.version == locked.version), None + ) + if version is None: # If there are no versions that satisfy the constraint, # add an incompatibility that indicates that. @@ -458,7 +469,9 @@ def _add_incompatibility(self, incompatibility): # type: (Incompatibility) -> N incompatibility ) - def _get_locked(self, dependency): # type: (Dependency) -> Union[Package, None] + def _get_locked( + self, dependency, allow_similar: bool = False + ): # type: (Dependency, bool) -> Union[DependencyPackage, None] if dependency.name in self._use_latest: return @@ -466,10 +479,10 @@ def _get_locked(self, dependency): # type: (Dependency) -> Union[Package, None] if not locked: return - if not dependency.is_same_package_as(locked): + if not (allow_similar or dependency.is_same_package_as(locked)): return - return locked + return DependencyPackage(dependency, locked) def _log(self, text): self._provider.debug(text, self._solution.attempted_solutions) diff --git a/poetry/packages/package_collection.py b/poetry/packages/package_collection.py index e10ea635bca..7fab8823d80 100644 --- a/poetry/packages/package_collection.py +++ b/poetry/packages/package_collection.py @@ -1,3 +1,7 @@ +from typing import Union + +from poetry.core.packages import Package + from .dependency_package import DependencyPackage @@ -13,7 +17,7 @@ def __init__(self, dependency, packages=None): for package in packages: self.append(package) - def append(self, package): + def append(self, package): # type: (Union[Package, DependencyPackage]) -> None if isinstance(package, DependencyPackage): package = package.package diff --git a/poetry/puzzle/provider.py b/poetry/puzzle/provider.py index c05efbd6844..b5f9d012823 100755 --- a/poetry/puzzle/provider.py +++ b/poetry/puzzle/provider.py @@ -8,6 +8,7 @@ from typing import Any from typing import List from typing import Optional +from typing import cast from clikit.ui.components import ProgressIndicator @@ -95,7 +96,7 @@ def use_environment(self, env): # type: (Env) -> Provider self._env = original_env self._python_constraint = original_python_constraint - def search_for(self, dependency): # type: (Dependency) -> List[Package] + def search_for(self, dependency): # type: (Dependency) -> List[DependencyPackage] """ Search for the specifications that match the given dependency. @@ -128,13 +129,13 @@ def search_for(self, dependency): # type: (Dependency) -> List[Package] return PackageCollection(dependency, packages) if dependency.is_vcs(): - packages = self.search_for_vcs(dependency) + packages = self.search_for_vcs(cast(VCSDependency, dependency)) elif dependency.is_file(): - packages = self.search_for_file(dependency) + packages = self.search_for_file(cast(FileDependency, dependency)) elif dependency.is_directory(): - packages = self.search_for_directory(dependency) + packages = self.search_for_directory(cast(DirectoryDependency, dependency)) elif dependency.is_url(): - packages = self.search_for_url(dependency) + packages = self.search_for_url(cast(URLDependency, dependency)) else: packages = self._pool.find_packages(dependency) @@ -150,6 +151,23 @@ def search_for(self, dependency): # type: (Dependency) -> List[Package] return PackageCollection(dependency, packages) + def _get_from_deferred_cache( + self, dependency + ): # type (Dependency) -> Optional[Package] + if dependency not in self._deferred_cache: + return None + + package = self._deferred_cache[dependency] + dependency._constraint = package.version + dependency._pretty_constraint = package.version.text + + if not dependency._source_reference: + dependency._source_reference = package.source_reference + if not dependency._source_resolved_reference: + dependency._source_resolved_reference = package.source_resolved_reference + + return package + def search_for_vcs(self, dependency): # type: (VCSDependency) -> List[Package] """ Search for the specifications that match the given VCS dependency. @@ -157,8 +175,9 @@ def search_for_vcs(self, dependency): # type: (VCSDependency) -> List[Package] Basically, we clone the repository in a temporary directory and get the information we need by checking out the specified reference. """ - if dependency in self._deferred_cache: - return [self._deferred_cache[dependency]] + cached = self._get_from_deferred_cache(dependency) + if cached is not None: + return [cached] package = self.get_package_from_vcs( dependency.vcs, @@ -180,7 +199,7 @@ def search_for_vcs(self, dependency): # type: (VCSDependency) -> List[Package] @classmethod def get_package_from_vcs( cls, vcs, url, branch=None, tag=None, rev=None, name=None - ): # type: (str, str, Optional[str], Optional[str]) -> Package + ): # type: (str, str, Optional[str], Optional[str], Optional[str], Optional[str]) -> Package if vcs != "git": raise ValueError("Unsupported VCS dependency {}".format(vcs)) @@ -212,8 +231,9 @@ def get_package_from_vcs( return package def search_for_file(self, dependency): # type: (FileDependency) -> List[Package] - if dependency in self._deferred_cache: - dependency, _package = self._deferred_cache[dependency] + cached = self._get_from_deferred_cache(dependency) + if cached is not None: + dependency, _package = cached package = _package.clone() else: @@ -257,8 +277,9 @@ def get_package_from_file(cls, file_path): # type: (Path) -> Package def search_for_directory( self, dependency ): # type: (DirectoryDependency) -> List[Package] - if dependency in self._deferred_cache: - dependency, _package = self._deferred_cache[dependency] + cached = self._get_from_deferred_cache(dependency) + if cached is not None: + dependency, _package = cached package = _package.clone() else: @@ -297,8 +318,9 @@ def get_package_from_directory( return package def search_for_url(self, dependency): # type: (URLDependency) -> List[Package] - if dependency in self._deferred_cache: - return [self._deferred_cache[dependency]] + cached = self._get_from_deferred_cache(dependency) + if cached is not None: + return [cached] package = self.get_package_from_url(dependency.url) diff --git a/poetry/repositories/base_repository.py b/poetry/repositories/base_repository.py index 46422ca0edc..260c725da6c 100644 --- a/poetry/repositories/base_repository.py +++ b/poetry/repositories/base_repository.py @@ -1,15 +1,23 @@ +from typing import List +from typing import Optional + +from poetry.core.packages.package import Package + + class BaseRepository(object): def __init__(self): - self._packages = [] + self._packages = [] # type: List[Package] @property - def packages(self): + def packages(self): # type: () -> List[Package] return self._packages def has_package(self, package): raise NotImplementedError() - def package(self, name, version, extras=None): + def package( + self, name, version, extras=None + ): # type: (str, str, Optional[List[str]]) -> Package raise NotImplementedError() def find_packages(self, dependency): diff --git a/poetry/repositories/pool.py b/poetry/repositories/pool.py index d9b7f8e1dab..422fa8a90c0 100755 --- a/poetry/repositories/pool.py +++ b/poetry/repositories/pool.py @@ -118,7 +118,7 @@ def has_package(self, package): def package( self, name, version, extras=None, repository=None - ): # type: (str, str, List[str], str) -> Package + ): # type: (str, str, Optional[List[str]], Optional[str]) -> Package if repository is not None: repository = repository.lower() diff --git a/poetry/repositories/repository.py b/poetry/repositories/repository.py index 1ebe702bb9c..711d6cf87a3 100755 --- a/poetry/repositories/repository.py +++ b/poetry/repositories/repository.py @@ -1,3 +1,7 @@ +from typing import List +from typing import Optional + +from poetry.core.packages.package import Package from poetry.core.semver import VersionConstraint from poetry.core.semver import VersionRange from poetry.core.semver import parse_constraint @@ -21,7 +25,9 @@ def __init__(self, packages=None, name=None): def name(self): return self._name - def package(self, name, version, extras=None): + def package( + self, name, version, extras=None + ): # type: (str, str, Optional[List[str]]) -> Package name = name.lower() for package in self.packages: