Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Porting over acceted changes from 1.1.3 #3205

Merged
merged 12 commits into from
Oct 14, 2020
45 changes: 44 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,45 @@
# Change Log

## [1.1.3] - 2020-10-14

### Changed

- Python version support deprecation warning is now written to `stderr`. ([#3131](https://github.com/python-poetry/poetry/pull/3131))

### Fixed

- Fixed `KeyError` when `PATH` is not defined in environment variables. ([#3159](https://github.com/python-poetry/poetry/pull/3159))
- Fixed error when using `config` command in a directory with an existing `pyproject.toml` without any Poetry configuration. ([#3172](https://github.com/python-poetry/poetry/pull/3172))
- Fixed incorrect inspection of package requirements when same dependency is specified multiple times with unique markers. ([#3147](https://github.com/python-poetry/poetry/pull/3147))
- Fixed `show` command to use already resolved package metadata. ([#3117](https://github.com/python-poetry/poetry/pull/3117))
- Fixed multiple issues with `export` command output when using `requirements.txt` format. ([#3119](https://github.com/python-poetry/poetry/pull/3119))

## [1.1.2] - 2020-10-06

### Changed
- Dependency installation of editable packages and all uninstall operations are now performed serially within their corresponding priority groups. ([#3099](https://github.com/python-poetry/poetry/pull/3099))
- Improved package metadata inspection of nested poetry projects within project path dependencies. ([#3105](https://github.com/python-poetry/poetry/pull/3105))

### Fixed

- Fixed export of `requirements.txt` when project dependency contains git dependencies. ([#3100](https://github.com/python-poetry/poetry/pull/3100))

## [1.1.1] - 2020-10-05

### Added

- Added `--no-update` option to `lock` command. ([#3034](https://github.com/python-poetry/poetry/pull/3034))

### Fixed

- Fixed resolution of packages with missing required extras. ([#3035](https://github.com/python-poetry/poetry/pull/3035))
- Fixed export of `requirements.txt` dependencies to include development dependencies. ([#3024](https://github.com/python-poetry/poetry/pull/3024))
- Fixed incorrect selection of unsupported binary distribution formats when selecting a package artifact to install. ([#3058](https://github.com/python-poetry/poetry/pull/3058))
- Fixed incorrect use of system executable when building package distributions via `build` command. ([#3056](https://github.com/python-poetry/poetry/pull/3056))
- Fixed errors in `init` command when specifying `--dependency` in non-interactive mode when a `pyproject.toml` file already exists. ([#3076](https://github.com/python-poetry/poetry/pull/3076))
- Fixed incorrect selection of configured source url when a publish repository url configuration with the same name already exists. ([#3047](https://github.com/python-poetry/poetry/pull/3047))
- Fixed dependency resolution issues when the same package is specified in multiple dependency extras. ([#3046](https://github.com/python-poetry/poetry/pull/3046))

## [1.1.0] - 2020-10-01

### Changed
Expand Down Expand Up @@ -1023,7 +1063,10 @@ Initial release



[Unreleased]: https://github.com/python-poetry/poetry/compare/1.1.0...master
[Unreleased]: https://github.com/python-poetry/poetry/compare/1.1.3...master
[1.1.3]: https://github.com/python-poetry/poetry/compare/1.1.3
[1.1.2]: https://github.com/python-poetry/poetry/releases/tag/1.1.2
[1.1.1]: https://github.com/python-poetry/poetry/releases/tag/1.1.1
[1.1.0]: https://github.com/python-poetry/poetry/releases/tag/1.1.0
[1.1.0rc1]: https://github.com/python-poetry/poetry/releases/tag/1.1.0rc1
[1.1.0b4]: https://github.com/python-poetry/poetry/releases/tag/1.1.0b4
Expand Down
2 changes: 1 addition & 1 deletion poetry/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.1.0"
__version__ = "1.2.0a0"
2 changes: 1 addition & 1 deletion poetry/console/commands/show.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def handle(self):
table = self.table(style="compact")
# table.style.line_vc_char = ""
locked_packages = locked_repo.packages
pool = Pool()
pool = Pool(ignore_repository_names=True)
pool.add_repository(locked_repo)
solver = Solver(
self.poetry.package,
Expand Down
7 changes: 6 additions & 1 deletion poetry/inspection/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ def to_package(
package.requires = poetry_package.requires
return package

seen_requirements = set()

for req in self.requires_dist or []:
try:
# Attempt to parse the PEP-508 requirement string
Expand All @@ -191,8 +193,11 @@ def to_package(

package.extras[extra].append(dependency)

if dependency not in package.requires:
req = dependency.to_pep_508(with_extras=True)

if req not in seen_requirements:
package.requires.append(dependency)
seen_requirements.add(req)

return package

Expand Down
231 changes: 172 additions & 59 deletions poetry/packages/locker.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import itertools
import json
import logging
import os
import re

from copy import deepcopy
from hashlib import sha256
from typing import Any
from typing import Dict
from typing import Iterable
from typing import Iterator
from typing import List
from typing import Optional
from typing import Sequence
from typing import Set
from typing import Tuple
from typing import Union

from tomlkit import array
from tomlkit import document
Expand All @@ -25,8 +30,10 @@
from poetry.core.semver.version import Version
from poetry.core.toml.file import TOMLFile
from poetry.core.version.markers import parse_marker
from poetry.packages import DependencyPackage
from poetry.utils._compat import OrderedDict
from poetry.utils._compat import Path
from poetry.utils.extras import get_extra_package_names


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -181,87 +188,193 @@ def locked_repository(

return packages

def get_project_dependencies(
self, project_requires, pinned_versions=False, with_nested=False, with_dev=False
): # type: (List[Dependency], bool, bool, bool) -> Any
packages = self.locked_repository(with_dev).packages
@staticmethod
def __get_locked_package(
_dependency, packages_by_name
): # type: (Dependency, Dict[str, List[Package]]) -> Optional[Package]
"""
Internal helper to identify corresponding locked package using dependency
version constraints.
"""
for _package in packages_by_name.get(_dependency.name, []):
if _dependency.constraint.allows(_package.version):
return _package
return None

@classmethod
def __walk_dependency_level(
cls,
dependencies,
level,
pinned_versions,
packages_by_name,
project_level_dependencies,
nested_dependencies,
): # type: (List[Dependency], int, bool, Dict[str, List[Package]], Set[str], Dict[Tuple[str, str], Dependency]) -> Dict[Tuple[str, str], Dependency]
if not dependencies:
return nested_dependencies

next_level_dependencies = []

for requirement in dependencies:
locked_package = cls.__get_locked_package(requirement, packages_by_name)

if locked_package:
for require in locked_package.requires:
if require.marker.is_empty():
require.marker = requirement.marker
else:
require.marker = require.marker.intersect(requirement.marker)

require.marker = require.marker.intersect(locked_package.marker)
next_level_dependencies.append(require)

if requirement.name in project_level_dependencies and level == 0:
# project level dependencies take precedence
continue

if locked_package:
# create dependency from locked package to retain dependency metadata
# if this is not done, we can end-up with incorrect nested dependencies
marker = requirement.marker
requirement = locked_package.to_dependency()
requirement.marker = requirement.marker.intersect(marker)
else:
# we make a copy to avoid any side-effects
requirement = deepcopy(requirement)

if pinned_versions:
requirement.set_constraint(
cls.__get_locked_package(requirement, packages_by_name)
.to_dependency()
.constraint
)

# group packages entries by name, this is required because requirement might use
# different constraints
# dependencies use extra to indicate that it was activated via parent
# package's extras, this is not required for nested exports as we assume
# the resolver already selected this dependency
requirement.marker = requirement.marker.without_extras()

key = (requirement.name, requirement.pretty_constraint)
if key not in nested_dependencies:
nested_dependencies[key] = requirement
else:
nested_dependencies[key].marker = nested_dependencies[
key
].marker.intersect(requirement.marker)

return cls.__walk_dependency_level(
dependencies=next_level_dependencies,
level=level + 1,
pinned_versions=pinned_versions,
packages_by_name=packages_by_name,
project_level_dependencies=project_level_dependencies,
nested_dependencies=nested_dependencies,
)

@classmethod
def get_project_dependencies(
cls, project_requires, locked_packages, pinned_versions=False, with_nested=False
): # type: (List[Dependency], List[Package], bool, bool) -> Iterable[Dependency]
# group packages entries by name, this is required because requirement might use different constraints
packages_by_name = {}
for pkg in packages:
for pkg in locked_packages:
if pkg.name not in packages_by_name:
packages_by_name[pkg.name] = []
packages_by_name[pkg.name].append(pkg)

def __get_locked_package(
_dependency,
): # type: (Dependency) -> Optional[Package]
"""
Internal helper to identify corresponding locked package using dependency
version constraints.
"""
for _package in packages_by_name.get(_dependency.name, []):
if _dependency.constraint.allows(_package.version):
return _package
return None

project_level_dependencies = set()
dependencies = []

for dependency in project_requires:
dependency = deepcopy(dependency)
if pinned_versions:
locked_package = __get_locked_package(dependency)
if locked_package:
dependency.set_constraint(locked_package.to_dependency().constraint)
locked_package = cls.__get_locked_package(dependency, packages_by_name)
if locked_package:
locked_dependency = locked_package.to_dependency()
locked_dependency.marker = dependency.marker.intersect(
locked_package.marker
)

if not pinned_versions:
locked_dependency.set_constraint(dependency.constraint)

dependency = locked_dependency

project_level_dependencies.add(dependency.name)
dependencies.append(dependency)

if not with_nested:
# return only with project level dependencies
return dependencies

nested_dependencies = list()
nested_dependencies = cls.__walk_dependency_level(
dependencies=dependencies,
level=0,
pinned_versions=pinned_versions,
packages_by_name=packages_by_name,
project_level_dependencies=project_level_dependencies,
nested_dependencies=dict(),
)

for pkg in packages: # type: Package
for requirement in pkg.requires: # type: Dependency
if requirement.name in project_level_dependencies:
# project level dependencies take precedence
continue
# Merge same dependencies using marker union
for requirement in dependencies:
key = (requirement.name, requirement.pretty_constraint)
if key not in nested_dependencies:
nested_dependencies[key] = requirement
else:
nested_dependencies[key].marker = nested_dependencies[key].marker.union(
requirement.marker
)

# we make a copy to avoid any side-effects
requirement = deepcopy(requirement)
requirement._category = pkg.category
return sorted(nested_dependencies.values(), key=lambda x: x.name.lower())

if pinned_versions:
requirement.set_constraint(
__get_locked_package(requirement).to_dependency().constraint
)
def get_project_dependency_packages(
self, project_requires, dev=False, extras=None
): # type: (List[Dependency], bool, Optional[Union[bool, Sequence[str]]]) -> Iterator[DependencyPackage]
repository = self.locked_repository(with_dev_reqs=dev)

# dependencies use extra to indicate that it was activated via parent
# package's extras
marker = requirement.marker.without_extras()
for project_requirement in project_requires:
if (
pkg.name == project_requirement.name
and project_requirement.constraint.allows(pkg.version)
):
requirement.marker = marker.intersect(
project_requirement.marker
)
break
else:
# this dependency was not from a project requirement
requirement.marker = marker.intersect(pkg.marker)
# Build a set of all packages required by our selected extras
extra_package_names = (
None if (isinstance(extras, bool) and extras is True) else ()
)

if requirement not in nested_dependencies:
nested_dependencies.append(requirement)
if extra_package_names is not None:
extra_package_names = set(
get_extra_package_names(
repository.packages, self.lock_data.get("extras", {}), extras or (),
)
)

return sorted(
itertools.chain(dependencies, nested_dependencies),
key=lambda x: x.name.lower(),
)
# If a package is optional and we haven't opted in to it, do not select
selected = []
for dependency in project_requires:
try:
package = repository.find_packages(dependency=dependency)[0]
except IndexError:
continue

if extra_package_names is not None and (
package.optional and package.name not in extra_package_names
):
# a package is locked as optional, but is not activated via extras
continue

selected.append(dependency)

for dependency in self.get_project_dependencies(
project_requires=selected,
locked_packages=repository.packages,
with_nested=True,
):
try:
package = repository.find_packages(dependency=dependency)[0]
except IndexError:
continue

for extra in dependency.extras:
package.requires_extras.append(extra)

yield DependencyPackage(dependency=dependency, package=package)

def set_lock_data(self, root, packages): # type: (...) -> bool
files = table()
Expand Down
Loading