-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
export: fix exporting extras sub-dependencies
- Loading branch information
1 parent
1caf20a
commit f4ab4af
Showing
5 changed files
with
126 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
from typing import Collection, Iterator, Mapping | ||
|
||
from poetry.packages import Package | ||
from poetry.utils.helpers import canonicalize_name | ||
|
||
|
||
def get_extra_package_names( | ||
packages, # type: Collection[Package] | ||
extras, # type: Mapping[str, Collection[str]] | ||
extra_names, # type:Collection[str] | ||
): # type: (...) -> Iterator[str] | ||
""" | ||
Returns all package names required by the given extras. | ||
:param packages: A collection of packages, such as from Repository.packages | ||
:param extras: A mapping of `extras` names to lists of package names, as defined | ||
in the `extras` section of `poetry.lock`. | ||
:param extra_names: A list of strings specifying names of extra groups to resolve. | ||
""" | ||
if not extra_names: | ||
return [] | ||
|
||
# lookup for packages by name, faster than looping over packages repeatedly | ||
packages_by_name = {package.name: package for package in packages} | ||
|
||
# get and flatten names of packages we've opted into as extras | ||
extra_package_names = [ | ||
canonicalize_name(extra_package_name) | ||
for extra_name in extra_names | ||
for extra_package_name in extras.get(extra_name, ()) | ||
] | ||
|
||
def _extra_packages(package_names): | ||
"""Recursively find dependencies for packages names""" | ||
# for each extra pacakge name | ||
for package_name in package_names: | ||
# Find the actual Package object. A missing key indicates an implicit | ||
# dependency (like setuptools), which should be ignored | ||
package = packages_by_name.get(canonicalize_name(package_name)) | ||
if package: | ||
yield package.name | ||
# Recurse for dependencies | ||
for dependency_package_name in _extra_packages( | ||
dependency.name for dependency in package.requires | ||
): | ||
yield dependency_package_name | ||
|
||
return _extra_packages(extra_package_names) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import pytest | ||
|
||
from poetry.utils.extras import get_extra_package_names | ||
|
||
from poetry.packages import Package | ||
|
||
_PACKAGE_FOO = Package("foo", "0.1.0") | ||
_PACKAGE_SPAM = Package("spam", "0.2.0") | ||
_PACKAGE_BAR = Package("bar", "0.3.0") | ||
_PACKAGE_BAR.add_dependency("foo") | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"packages,extras,extra_names,expected_extra_package_names", | ||
[ | ||
# Empty edge case | ||
([], {}, [], []), | ||
# Selecting no extras is fine | ||
([_PACKAGE_FOO], {}, [], []), | ||
# An empty extras group should return an empty list | ||
([_PACKAGE_FOO], {"group0": []}, ["group0"], []), | ||
# Selecting an extras group should return the contained packages | ||
( | ||
[_PACKAGE_FOO, _PACKAGE_SPAM, _PACKAGE_BAR], | ||
{"group0": ["foo"]}, | ||
["group0"], | ||
["foo"], | ||
), | ||
# If a package has dependencies, we should also get their names | ||
( | ||
[_PACKAGE_FOO, _PACKAGE_SPAM, _PACKAGE_BAR], | ||
{"group0": ["bar"], "group1": ["spam"]}, | ||
["group0"], | ||
["bar", "foo"], | ||
), | ||
# Selecting multpile extras should get us the union of all package names | ||
( | ||
[_PACKAGE_FOO, _PACKAGE_SPAM, _PACKAGE_BAR], | ||
{"group0": ["bar"], "group1": ["spam"]}, | ||
["group0", "group1"], | ||
["bar", "foo", "spam"], | ||
), | ||
], | ||
) | ||
def test_get_extra_package_names( | ||
packages, extras, extra_names, expected_extra_package_names | ||
): | ||
assert expected_extra_package_names == list( | ||
get_extra_package_names(packages, extras, extra_names) | ||
) |