Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
python-poetry
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
open
python-poetry
Commits
fb13b3a6
Unverified
Commit
fb13b3a6
authored
Apr 03, 2022
by
David Hotham
Committed by
GitHub
Apr 03, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
locker: refactor dependency walk logic
Resolves: #5141
parent
eb27f816
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
586 additions
and
193 deletions
+586
-193
src/poetry/packages/locker.py
+78
-104
src/poetry/utils/exporter.py
+14
-14
tests/console/commands/test_export.py
+15
-5
tests/utils/test_exporter.py
+479
-70
No files found.
src/poetry/packages/locker.py
View file @
fb13b3a6
...
...
@@ -32,6 +32,8 @@ from poetry.utils.extras import get_extra_package_names
if
TYPE_CHECKING
:
from
poetry.core.semver.version_constraint
import
VersionConstraint
from
poetry.core.version.markers
import
BaseMarker
from
tomlkit.items
import
InlineTable
from
tomlkit.toml_document
import
TOMLDocument
...
...
@@ -203,69 +205,80 @@ class Locker:
@staticmethod
def
__get_locked_package
(
_dependency
:
Dependency
,
packages_by_name
:
dict
[
str
,
list
[
Package
]]
dependency
:
Dependency
,
packages_by_name
:
dict
[
str
,
list
[
Package
]],
decided
:
dict
[
Package
,
Dependency
]
|
None
=
None
,
)
->
Package
|
None
:
"""
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
decided
=
decided
or
{}
# Get the packages that are consistent with this dependency.
packages
=
[
package
for
package
in
packages_by_name
.
get
(
dependency
.
name
,
[])
if
package
.
python_constraint
.
allows_all
(
dependency
.
python_constraint
)
and
dependency
.
constraint
.
allows
(
package
.
version
)
]
# If we've previously made a choice that is compatible with the current
# requirement, stick with it.
for
package
in
packages
:
old_decision
=
decided
.
get
(
package
)
if
(
old_decision
is
not
None
and
not
old_decision
.
marker
.
intersect
(
dependency
.
marker
)
.
is_empty
()
):
return
package
return
next
(
iter
(
packages
),
None
)
@classmethod
def
__walk_dependenc
y_level
(
def
__walk_dependenc
ies
(
cls
,
dependencies
:
list
[
Dependency
],
level
:
int
,
pinned_versions
:
bool
,
packages_by_name
:
dict
[
str
,
list
[
Package
]],
project_level_dependencies
:
set
[
str
],
nested_dependencies
:
dict
[
tuple
[
str
,
str
],
Dependency
],
)
->
dict
[
tuple
[
str
,
str
],
Dependency
]:
if
not
dependencies
:
return
nested_dependencies
next_level_dependencies
=
[]
)
->
dict
[
Package
,
Dependency
]:
nested_dependencies
:
dict
[
Package
,
Dependency
]
=
{}
for
requirement
in
dependencies
:
key
=
(
requirement
.
name
,
requirement
.
pretty_constraint
)
locked_package
=
cls
.
__get_locked_package
(
requirement
,
packages_by_name
)
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
constraint
=
requirement
.
constraint
pretty_constraint
=
requirement
.
pretty_constraint
marker
=
requirement
.
marker
requirement
=
locked_package
.
to_dependency
()
requirement
.
marker
=
requirement
.
marker
.
intersect
(
marker
)
key
=
(
requirement
.
name
,
pretty_constraint
)
visited
:
set
[
tuple
[
Dependency
,
BaseMarker
]]
=
set
()
while
dependencies
:
requirement
=
dependencies
.
pop
(
0
)
if
(
requirement
,
requirement
.
marker
)
in
visited
:
continue
visited
.
add
((
requirement
,
requirement
.
marker
))
if
not
pinned_versions
:
requirement
.
set_constraint
(
constraint
)
locked_package
=
cls
.
__get_locked_package
(
requirement
,
packages_by_name
,
nested_dependencies
)
for
require
in
locked_package
.
requires
:
if
require
.
marker
.
is_empty
():
require
.
marker
=
requirement
.
marker
else
:
require
.
marker
=
require
.
marker
.
intersect
(
requirement
.
marker
)
if
not
locked_package
:
raise
RuntimeError
(
f
"Dependency walk failed at {requirement}"
)
require
.
marker
=
require
.
marker
.
intersect
(
locked_package
.
marker
)
# create dependency from locked package to retain dependency metadata
# if this is not done, we can end-up with incorrect nested dependencies
constraint
=
requirement
.
constraint
marker
=
requirement
.
marker
extras
=
requirement
.
extras
requirement
=
locked_package
.
to_dependency
()
requirement
.
marker
=
requirement
.
marker
.
intersect
(
marker
)
if
key
not
in
nested_dependencies
:
next_level_dependencies
.
append
(
require
)
requirement
.
set_constraint
(
constraint
)
if
requirement
.
name
in
project_level_dependencies
and
level
==
0
:
# project level dependencies take precedence
continue
for
require
in
locked_package
.
requires
:
if
require
.
in_extras
and
extras
.
isdisjoint
(
require
.
in_extras
):
continue
if
not
locked_package
:
# we make a copy to avoid any side-effects
requirement
=
deepcopy
(
requirement
)
require
=
deepcopy
(
require
)
require
.
marker
=
require
.
marker
.
intersect
(
requirement
.
marker
.
without_extras
()
)
if
not
require
.
marker
.
is_empty
():
dependencies
.
append
(
require
)
key
=
locked_package
if
key
not
in
nested_dependencies
:
nested_dependencies
[
key
]
=
requirement
else
:
...
...
@@ -273,82 +286,49 @@ class Locker:
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
,
)
return
nested_dependencies
@classmethod
def
get_project_dependencies
(
cls
,
project_requires
:
list
[
Dependency
],
locked_packages
:
list
[
Package
],
pinned_versions
:
bool
=
False
,
with_nested
:
bool
=
False
,
)
->
Iterable
[
Dependency
]:
)
->
Iterable
[
tuple
[
Package
,
Dependency
]]:
# group packages entries by name, this is required because requirement might use
# different constraints
# different constraints
.
packages_by_name
:
dict
[
str
,
list
[
Package
]]
=
{}
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
)
project_level_dependencies
=
set
()
dependencies
=
[]
for
dependency
in
project_requires
:
dependency
=
deepcopy
(
dependency
)
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
# Put higher versions first so that we prefer them.
for
packages
in
packages_by_name
.
values
():
packages
.
sort
(
key
=
lambda
package
:
package
.
version
,
reverse
=
True
)
nested_dependencies
=
cls
.
__walk_dependency_level
(
dependencies
=
dependencies
,
level
=
0
,
pinned_versions
=
pinned_versions
,
nested_dependencies
=
cls
.
__walk_dependencies
(
dependencies
=
project_requires
,
packages_by_name
=
packages_by_name
,
project_level_dependencies
=
project_level_dependencies
,
nested_dependencies
=
{},
)
# 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
)
return
sorted
(
nested_dependencies
.
values
(),
key
=
lambda
x
:
x
.
name
.
lower
())
return
nested_dependencies
.
items
()
def
get_project_dependency_packages
(
self
,
project_requires
:
list
[
Dependency
],
project_python_marker
:
VersionConstraint
|
None
=
None
,
dev
:
bool
=
False
,
extras
:
bool
|
Sequence
[
str
]
|
None
=
None
,
)
->
Iterator
[
DependencyPackage
]:
# Apply the project python marker to all requirements.
if
project_python_marker
is
not
None
:
marked_requires
:
list
[
Dependency
]
=
[]
for
require
in
project_requires
:
require
=
deepcopy
(
require
)
require
.
marker
=
require
.
marker
.
intersect
(
project_python_marker
)
marked_requires
.
append
(
require
)
project_requires
=
marked_requires
repository
=
self
.
locked_repository
(
with_dev_reqs
=
dev
)
# Build a set of all packages required by our selected extras
...
...
@@ -379,16 +359,10 @@ class Locker:
selected
.
append
(
dependency
)
for
dependency
in
self
.
get_project_dependencies
(
for
package
,
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
)
...
...
src/poetry/utils/exporter.py
View file @
fb13b3a6
from
__future__
import
annotations
import
itertools
import
urllib.parse
from
typing
import
TYPE_CHECKING
...
...
@@ -70,21 +69,22 @@ class Exporter:
content
=
""
dependency_lines
=
set
()
for
package
,
groups
in
itertools
.
groupby
(
self
.
_poetry
.
locker
.
get_project_dependency_packages
(
project_requires
=
self
.
_poetry
.
package
.
all_requires
,
dev
=
dev
,
extras
=
extras
,
),
lambda
dependency_package
:
dependency_package
.
package
,
# Get project dependencies.
root_package
=
(
self
.
_poetry
.
package
.
clone
()
if
dev
else
self
.
_poetry
.
package
.
with_dependency_groups
([
"default"
],
only
=
True
)
)
for
dependency_package
in
self
.
_poetry
.
locker
.
get_project_dependency_packages
(
project_requires
=
root_package
.
all_requires
,
project_python_marker
=
root_package
.
python_marker
,
dev
=
dev
,
extras
=
extras
,
):
line
=
""
dependency_packages
=
list
(
groups
)
dependency
=
dependency_packages
[
0
]
.
dependency
marker
=
dependency
.
marker
for
dep_package
in
dependency_packages
[
1
:]:
marker
=
marker
.
union
(
dep_package
.
dependency
.
marker
)
dependency
.
marker
=
marker
dependency
=
dependency_package
.
dependency
package
=
dependency_package
.
package
if
package
.
develop
:
line
+=
"-e "
...
...
tests/console/commands/test_export.py
View file @
fb13b3a6
...
...
@@ -84,7 +84,9 @@ def _export_requirements(tester: CommandTester, poetry: Poetry) -> None:
assert
poetry
.
locker
.
lock
.
exists
()
expected
=
"""
\
foo==1.0.0
foo==1.0.0 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.4" and python_version < "4.0"
"""
assert
content
==
expected
...
...
@@ -113,7 +115,9 @@ def test_export_fails_on_invalid_format(tester: CommandTester, do_lock: None):
def
test_export_prints_to_stdout_by_default
(
tester
:
CommandTester
,
do_lock
:
None
):
tester
.
execute
(
"--format requirements.txt"
)
expected
=
"""
\
foo==1.0.0
foo==1.0.0 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.4" and python_version < "4.0"
"""
assert
tester
.
io
.
fetch_output
()
==
expected
...
...
@@ -123,7 +127,9 @@ def test_export_uses_requirements_txt_format_by_default(
):
tester
.
execute
()
expected
=
"""
\
foo==1.0.0
foo==1.0.0 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.4" and python_version < "4.0"
"""
assert
tester
.
io
.
fetch_output
()
==
expected
...
...
@@ -131,8 +137,12 @@ foo==1.0.0
def
test_export_includes_extras_by_flag
(
tester
:
CommandTester
,
do_lock
:
None
):
tester
.
execute
(
"--format requirements.txt --extras feature_bar"
)
expected
=
"""
\
bar==1.1.0
foo==1.0.0
bar==1.1.0 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.4" and python_version < "4.0"
foo==1.0.0 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.4" and python_version < "4.0"
"""
assert
tester
.
io
.
fetch_output
()
==
expected
...
...
tests/utils/test_exporter.py
View file @
fb13b3a6
from
__future__
import
annotations
import
sys
import
textwrap
from
pathlib
import
Path
from
typing
import
TYPE_CHECKING
...
...
@@ -126,8 +125,12 @@ def test_exporter_can_export_requirements_txt_with_standard_packages(
content
=
f
.
read
()
expected
=
"""
\
bar==4.5.6
foo==1.2.3
bar==4.5.6 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
foo==1.2.3 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
"""
assert
content
==
expected
...
...
@@ -181,9 +184,15 @@ def test_exporter_can_export_requirements_txt_with_standard_packages_and_markers
content
=
f
.
read
()
expected
=
"""
\
bar==4.5.6
baz==7.8.9 ; sys_platform == "win32"
foo==1.2.3 ; python_version < "3.7"
bar==4.5.6 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
baz==7.8.9 ;
\
python_version >= "2.7" and python_version < "2.8" and sys_platform == "win32" or
\
python_version >= "3.6" and python_version < "4.0" and sys_platform == "win32"
foo==1.2.3 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "3.7"
"""
assert
content
==
expected
...
...
@@ -286,17 +295,29 @@ def test_exporter_can_export_requirements_txt_poetry(tmp_dir: str, poetry: Poetr
# │ ├── cryptography >=2.0
# │ │ └── six >=1.4.1
# │ └── jeepney >=0.6 (circular dependency aborted here)
python27
=
'python_version >= "2.7" and python_version < "2.8"'
python36
=
'python_version >= "3.6" and python_version < "4.0"'
linux
=
'sys_platform=="linux"'
expected
=
{
"poetry"
:
Dependency
.
create_from_pep_508
(
"poetry==1.1.4"
),
"junit-xml"
:
Dependency
.
create_from_pep_508
(
"junit-xml==1.9"
),
"keyring"
:
Dependency
.
create_from_pep_508
(
"keyring==21.8.0"
),
"poetry"
:
Dependency
.
create_from_pep_508
(
f
"poetry==1.1.4; {python27} or {python36}"
),
"junit-xml"
:
Dependency
.
create_from_pep_508
(
f
"junit-xml==1.9 ; {python27} or {python36}"
),
"keyring"
:
Dependency
.
create_from_pep_508
(
f
"keyring==21.8.0 ; {python27} or {python36}"
),
"secretstorage"
:
Dependency
.
create_from_pep_508
(
"secretstorage==3.3.0 ; sys_platform=='linux'
"
f
"secretstorage==3.3.0 ; {python27} and {linux} or {python36} and {linux}
"
),
"cryptography"
:
Dependency
.
create_from_pep_508
(
"cryptography==3.2 ; sys_platform=='linux'"
f
"cryptography==3.2 ; {python27} and {linux} or {python36} and {linux}"
),
"six"
:
Dependency
.
create_from_pep_508
(
f
"six==1.15.0 ; {python27} or {python36} or {python27} and {linux} or"
f
" {python36} and {linux}"
),
"six"
:
Dependency
.
create_from_pep_508
(
"six==1.15.0"
),
}
for
line
in
content
.
strip
()
.
split
(
"
\n
"
):
...
...
@@ -368,11 +389,19 @@ def test_exporter_can_export_requirements_txt_pyinstaller(tmp_dir: str, poetry:
# ├── altgraph * dependencies into a single package.
# ├── macholib >=1.8 -- only on Darwin
# │ └── altgraph >=0.15
python27
=
'python_version >= "2.7" and python_version < "2.8"'
python36
=
'python_version >= "3.6" and python_version < "4.0"'
darwin
=
'sys_platform=="darwin"'
expected
=
{
"pyinstaller"
:
Dependency
.
create_from_pep_508
(
"pyinstaller==4.0"
),
"altgraph"
:
Dependency
.
create_from_pep_508
(
"altgraph==0.17"
),
"pyinstaller"
:
Dependency
.
create_from_pep_508
(
f
"pyinstaller==4.0 ; {python27} or {python36}"
),
"altgraph"
:
Dependency
.
create_from_pep_508
(
f
"altgraph==0.17 ; {python27} or {python36} or {python27} and {darwin} or"
f
" {python36} and {darwin}"
),
"macholib"
:
Dependency
.
create_from_pep_508
(
"macholib==1.8 ; sys_platform == 'darwin'
"
f
"macholib==1.8 ; {python27} and {darwin} or {python36} and {darwin}
"
),
}
...
...
@@ -441,17 +470,21 @@ def test_exporter_can_export_requirements_txt_with_nested_packages_and_markers(
with
(
Path
(
tmp_dir
)
/
"requirements.txt"
)
.
open
(
encoding
=
"utf-8"
)
as
f
:
content
=
f
.
read
()
python27
=
'python_version >= "2.7" and python_version < "2.8"'
python36
=
'python_version >= "3.6" and python_version < "3.7"'
windows
=
'platform_system == "Windows"'
win32
=
'sys_platform == "win32"'
expected
=
{
"a"
:
Dependency
.
create_from_pep_508
(
"a==1.2.3 ; python_version < '3.7'
"
),
"a"
:
Dependency
.
create_from_pep_508
(
f
"a==1.2.3 ; {python27} or {python36}
"
),
"b"
:
Dependency
.
create_from_pep_508
(
"b==4.5.6 ; platform_system == 'Windows' and python_version < '3.7'
"
f
"b==4.5.6 ; {python27} and {windows} or {python36} and {windows}
"
),
"c"
:
Dependency
.
create_from_pep_508
(
"c==7.8.9 ; sys_platform == 'win32' and python_version < '3.7'
"
f
"c==7.8.9 ; {python27} and {win32} or {python36} and {win32}
"
),
"d"
:
Dependency
.
create_from_pep_508
(
"d==0.0.1 ; platform_system == 'Windows' and python_version < '3.7'
or"
" sys_platform == 'win32' and python_version < '3.7'
"
f
"d==0.0.1 ; {python27} and {windows} or {python36} and {windows}
or"
f
" {python27} and {win32} or {python36} and {win32}
"
),
}
...
...
@@ -467,7 +500,25 @@ def test_exporter_can_export_requirements_txt_with_nested_packages_and_markers(
@pytest.mark.parametrize
(
[
"dev"
,
"lines"
],
[(
False
,
[
'a==1.2.3 ; python_version < "3.8"'
]),
(
True
,
[
"a==1.2.3"
,
"b==4.5.6"
])],
[
(
False
,
[
'a==1.2.3 ; python_version >= "2.7" and python_version < "2.8" or'
' python_version >= "3.6" and python_version < "3.8"'
],
),
(
True
,
[
'a==1.2.3 ; python_version >= "2.7" and python_version < "2.8" or'
' python_version >= "3.6" and python_version < "3.8" or python_version'
' >= "3.6" and python_version < "4.0"'
,
'b==4.5.6 ; python_version >= "2.7" and python_version < "2.8" or'
' python_version >= "3.6" and python_version < "4.0"'
,
],
),
],
)
def
test_exporter_can_export_requirements_txt_with_nested_packages_and_markers_any
(
tmp_dir
:
str
,
poetry
:
Poetry
,
dev
:
bool
,
lines
:
list
[
str
]
...
...
@@ -560,9 +611,13 @@ def test_exporter_can_export_requirements_txt_with_standard_packages_and_hashes(
content
=
f
.
read
()
expected
=
"""
\
bar==4.5.6
\\
bar==4.5.6 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
\\
--hash=sha256:67890
foo==1.2.3
\\
foo==1.2.3 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
\\
--hash=sha256:12345
"""
...
...
@@ -609,8 +664,12 @@ def test_exporter_can_export_requirements_txt_with_standard_packages_and_hashes_
content
=
f
.
read
()
expected
=
"""
\
bar==4.5.6
foo==1.2.3
bar==4.5.6 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
foo==1.2.3 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
"""
assert
content
==
expected
...
...
@@ -654,7 +713,9 @@ def test_exporter_exports_requirements_txt_without_dev_packages_by_default(
content
=
f
.
read
()
expected
=
"""
\
foo==1.2.3
\\
foo==1.2.3 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
\\
--hash=sha256:12345
"""
...
...
@@ -699,9 +760,13 @@ def test_exporter_exports_requirements_txt_with_dev_packages_if_opted_in(
content
=
f
.
read
()
expected
=
"""
\
bar==4.5.6
\\
bar==4.5.6 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
\\
--hash=sha256:67890
foo==1.2.3
\\
foo==1.2.3 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
\\
--hash=sha256:12345
"""
...
...
@@ -746,7 +811,9 @@ def test_exporter_exports_requirements_txt_without_optional_packages(
content
=
f
.
read
()
expected
=
"""
\
foo==1.2.3
\\
foo==1.2.3 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
\\
--hash=sha256:12345
"""
...
...
@@ -756,10 +823,42 @@ foo==1.2.3 \\
@pytest.mark.parametrize
(
[
"extras"
,
"lines"
],
[
(
None
,
[
"foo==1.2.3"
]),
(
False
,
[
"foo==1.2.3"
]),
(
True
,
[
"bar==4.5.6"
,
"foo==1.2.3"
,
"spam==0.1.0"
]),
([
"feature_bar"
],
[
"bar==4.5.6"
,
"foo==1.2.3"
,
"spam==0.1.0"
]),
(
None
,
[
'foo==1.2.3 ; python_version >= "2.7" and python_version < "2.8" or'
' python_version >= "3.6" and python_version < "4.0"'
],
),
(
False
,
[
'foo==1.2.3 ; python_version >= "2.7" and python_version < "2.8" or'
' python_version >= "3.6" and python_version < "4.0"'
],
),
(
True
,
[
'bar==4.5.6 ; python_version >= "2.7" and python_version < "2.8" or'
' python_version >= "3.6" and python_version < "4.0"'
,
'foo==1.2.3 ; python_version >= "2.7" and python_version < "2.8" or'
' python_version >= "3.6" and python_version < "4.0"'
,
'spam==0.1.0 ; python_version >= "2.7" and python_version < "2.8" or'
' python_version >= "3.6" and python_version < "4.0"'
,
],
),
(
[
"feature_bar"
],
[
'bar==4.5.6 ; python_version >= "2.7" and python_version < "2.8" or'
' python_version >= "3.6" and python_version < "4.0"'
,
'foo==1.2.3 ; python_version >= "2.7" and python_version < "2.8" or'
' python_version >= "3.6" and python_version < "4.0"'
,
'spam==0.1.0 ; python_version >= "2.7" and python_version < "2.8" or'
' python_version >= "3.6" and python_version < "4.0"'
,
],
),
],
)
def
test_exporter_exports_requirements_txt_with_optional_packages
(
...
...
@@ -859,7 +958,9 @@ def test_exporter_can_export_requirements_txt_with_git_packages(
content
=
f
.
read
()
expected
=
"""
\
foo @ git+https://github.com/foo/foo.git@123456
foo @ git+https://github.com/foo/foo.git@123456 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
"""
assert
content
==
expected
...
...
@@ -909,8 +1010,12 @@ def test_exporter_can_export_requirements_txt_with_nested_packages(
content
=
f
.
read
()
expected
=
"""
\
bar==4.5.6
foo @ git+https://github.com/foo/foo.git@123456
bar==4.5.6 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
foo @ git+https://github.com/foo/foo.git@123456 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
"""
assert
content
==
expected
...
...
@@ -964,9 +1069,15 @@ def test_exporter_can_export_requirements_txt_with_nested_packages_cyclic(
content
=
f
.
read
()
expected
=
"""
\
bar==4.5.6
baz==7.8.9
foo==1.2.3
bar==4.5.6 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
baz==7.8.9 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
foo==1.2.3 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
"""
assert
content
==
expected
...
...
@@ -1036,13 +1147,19 @@ def test_exporter_can_export_requirements_txt_with_nested_packages_and_multiple_
with
(
Path
(
tmp_dir
)
/
"requirements.txt"
)
.
open
(
encoding
=
"utf-8"
)
as
f
:
content
=
f
.
read
()
expected
=
textwrap
.
dedent
(
"""
\
bar==7.8.9
baz==10.11.13 ; platform_system == "Windows"
foo==1.2.3
"""
)
expected
=
"""
\
bar==7.8.9 ;
\
python_version >= "2.7" and python_version < "2.8" and platform_system != "Windows" or
\
python_version >= "3.6" and python_version < "4.0" and platform_system != "Windows" or
\
python_version >= "2.7" and python_version < "2.8" and platform_system == "Windows" or
\
python_version >= "3.6" and python_version < "4.0" and platform_system == "Windows"
baz==10.11.13 ;
\
python_version >= "2.7" and python_version < "2.8" and platform_system == "Windows" or
\
python_version >= "3.6" and python_version < "4.0" and platform_system == "Windows"
foo==1.2.3 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
"""
assert
content
==
expected
...
...
@@ -1084,7 +1201,9 @@ def test_exporter_can_export_requirements_txt_with_git_packages_and_markers(
content
=
f
.
read
()
expected
=
"""
\
foo @ git+https://github.com/foo/foo.git@123456 ; python_version < "3.7"
foo @ git+https://github.com/foo/foo.git@123456 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "3.7"
"""
assert
content
==
expected
...
...
@@ -1126,7 +1245,9 @@ def test_exporter_can_export_requirements_txt_with_directory_packages(
content
=
f
.
read
()
expected
=
f
"""
\
foo @ {working_directory.as_uri()}/tests/fixtures/sample_project
foo @ {working_directory.as_uri()}/tests/fixtures/sample_project ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
"""
assert
content
==
expected
...
...
@@ -1192,9 +1313,15 @@ def test_exporter_can_export_requirements_txt_with_nested_directory_packages(
content
=
f
.
read
()
expected
=
f
"""
\
bar @ {working_directory.as_uri()}/tests/fixtures/project_with_nested_local/bar
baz @ {working_directory.as_uri()}/tests/fixtures/project_with_nested_local
foo @ {working_directory.as_uri()}/tests/fixtures/sample_project
bar @ {working_directory.as_uri()}/tests/fixtures/project_with_nested_local/bar ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
baz @ {working_directory.as_uri()}/tests/fixtures/project_with_nested_local ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
foo @ {working_directory.as_uri()}/tests/fixtures/sample_project ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
"""
assert
content
==
expected
...
...
@@ -1237,8 +1364,9 @@ def test_exporter_can_export_requirements_txt_with_directory_packages_and_marker
content
=
f
.
read
()
expected
=
f
"""
\
foo @ {working_directory.as_uri()}/tests/fixtures/sample_project
\
; python_version < "3.7"
foo @ {working_directory.as_uri()}/tests/fixtures/sample_project ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "3.7"
"""
assert
content
==
expected
...
...
@@ -1280,7 +1408,9 @@ def test_exporter_can_export_requirements_txt_with_file_packages(
content
=
f
.
read
()
expected
=
f
"""
\
foo @ {working_directory.as_uri()}/tests/fixtures/distributions/demo-0.1.0.tar.gz
foo @ {working_directory.as_uri()}/tests/fixtures/distributions/demo-0.1.0.tar.gz ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
"""
assert
content
==
expected
...
...
@@ -1323,8 +1453,9 @@ def test_exporter_can_export_requirements_txt_with_file_packages_and_markers(
content
=
f
.
read
()
expected
=
f
"""
\
foo @ {working_directory.as_uri()}/tests/fixtures/distributions/demo-0.1.0.tar.gz
\
; python_version < "3.7"
foo @ {working_directory.as_uri()}/tests/fixtures/distributions/demo-0.1.0.tar.gz ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "3.7"
"""
assert
content
==
expected
...
...
@@ -1381,9 +1512,13 @@ def test_exporter_exports_requirements_txt_with_legacy_packages(
expected
=
"""
\
--extra-index-url https://example.com/simple
bar==4.5.6
\\
bar==4.5.6 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
\\
--hash=sha256:67890
foo==1.2.3
\\
foo==1.2.3 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
\\
--hash=sha256:12345
"""
...
...
@@ -1439,9 +1574,13 @@ def test_exporter_exports_requirements_txt_with_url_false(tmp_dir: str, poetry:
content
=
f
.
read
()
expected
=
"""
\
bar==4.5.6
\\
bar==4.5.6 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
\\
--hash=sha256:67890
foo==1.2.3
\\
foo==1.2.3 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
\\
--hash=sha256:12345
"""
...
...
@@ -1492,7 +1631,9 @@ def test_exporter_exports_requirements_txt_with_legacy_packages_trusted_host(
--trusted-host example.com
--extra-index-url http://example.com/simple
bar==4.5.6
\\
bar==4.5.6 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
\\
--hash=sha256:67890
"""
...
...
@@ -1502,8 +1643,26 @@ bar==4.5.6 \\
@pytest.mark.parametrize
(
[
"dev"
,
"expected"
],
[
(
True
,
[
"bar==1.2.2"
,
"baz==1.2.3"
,
"foo==1.2.1"
]),
(
False
,
[
"bar==1.2.2"
,
"foo==1.2.1"
]),
(
True
,
[
'bar==1.2.2 ; python_version >= "2.7" and python_version < "2.8" or'
' python_version >= "3.6" and python_version < "4.0"'
,
'baz==1.2.3 ; python_version >= "2.7" and python_version < "2.8" or'
' python_version >= "3.6" and python_version < "4.0"'
,
'foo==1.2.1 ; python_version >= "2.7" and python_version < "2.8" or'
' python_version >= "3.6" and python_version < "4.0"'
,
],
),
(
False
,
[
'bar==1.2.2 ; python_version >= "2.7" and python_version < "2.8" or'
' python_version >= "3.6" and python_version < "4.0"'
,
'foo==1.2.1 ; python_version >= "2.7" and python_version < "2.8" or'
' python_version >= "3.6" and python_version < "4.0"'
,
],
),
],
)
def
test_exporter_exports_requirements_txt_with_dev_extras
(
...
...
@@ -1636,11 +1795,17 @@ def test_exporter_exports_requirements_txt_with_legacy_packages_and_duplicate_so
--extra-index-url https://example.com/simple
--extra-index-url https://foobaz.com/simple
bar==4.5.6
\\
bar==4.5.6 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
\\
--hash=sha256:67890
baz==7.8.9
\\
baz==7.8.9 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
\\
--hash=sha256:24680
foo==1.2.3
\\
foo==1.2.3 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
\\
--hash=sha256:12345
"""
...
...
@@ -1707,9 +1872,13 @@ def test_exporter_exports_requirements_txt_with_legacy_packages_and_credentials(
expected
=
"""
\
--extra-index-url https://foo:bar@example.com/simple
bar==4.5.6
\\
bar==4.5.6 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
\\
--hash=sha256:67890
foo==1.2.3
\\
foo==1.2.3 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
\\
--hash=sha256:12345
"""
...
...
@@ -1752,8 +1921,248 @@ def test_exporter_exports_requirements_txt_to_standard_output(
out
,
err
=
capsys
.
readouterr
()
expected
=
"""
\
bar==4.5.6
foo==1.2.3
bar==4.5.6 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
foo==1.2.3 ;
\
python_version >= "2.7" and python_version < "2.8" or
\
python_version >= "3.6" and python_version < "4.0"
"""
assert
out
==
expected
def
test_exporter_doesnt_confuse_repeated_packages
(
tmp_dir
:
str
,
poetry
:
Poetry
,
capsys
:
CaptureFixture
):
# Testcase derived from <https://github.com/python-poetry/poetry/issues/5141>.
poetry
.
locker
.
mock_lock_data
(
{
"package"
:
[
{
"name"
:
"celery"
,
"version"
:
"5.1.2"
,
"category"
:
"main"
,
"optional"
:
False
,
"python-versions"
:
"<3.7"
,
"dependencies"
:
{
"click"
:
">=7.0,<8.0"
,
"click-didyoumean"
:
">=0.0.3"
,
"click-plugins"
:
">=1.1.1"
,
},
},
{
"name"
:
"celery"
,
"version"
:
"5.2.3"
,
"category"
:
"main"
,
"optional"
:
False
,
"python-versions"
:
">=3.7"
,
"dependencies"
:
{
"click"
:
">=8.0.3,<9.0"
,
"click-didyoumean"
:
">=0.0.3"
,
"click-plugins"
:
">=1.1.1"
,
},
},
{
"name"
:
"click"
,
"version"
:
"7.1.2"
,
"category"
:
"main"
,
"optional"
:
False
,
"python-versions"
:
(
">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
),
},
{
"name"
:
"click"
,
"version"
:
"8.0.3"
,
"category"
:
"main"
,
"optional"
:
False
,
"python-versions"
:
">=3.6"
,
"dependencies"
:
{},
},
{
"name"
:
"click-didyoumean"
,
"version"
:
"0.0.3"
,
"category"
:
"main"
,
"optional"
:
False
,
"python-versions"
:
"*"
,
"dependencies"
:
{
"click"
:
"*"
},
},
{
"name"
:
"click-didyoumean"
,
"version"
:
"0.3.0"
,
"category"
:
"main"
,
"optional"
:
False
,
"python-versions"
:
">=3.6.2,<4.0.0"
,
"dependencies"
:
{
"click"
:
">=7"
},
},
{
"name"
:
"click-plugins"
,
"version"
:
"1.1.1"
,
"category"
:
"main"
,
"optional"
:
False
,
"python-versions"
:
"*"
,
"dependencies"
:
{
"click"
:
">=4.0"
},
},
],
"metadata"
:
{
"lock-version"
:
"1.1"
,
"python-versions"
:
"^3.6"
,
"content-hash"
:
(
"832b13a88e5020c27cbcd95faa577bf0dbf054a65c023b45dc9442b640d414e6"
),
"hashes"
:
{
"celery"
:
[],
"click-didyoumean"
:
[],
"click-plugins"
:
[],
"click"
:
[],
},
},
}
)
root
=
poetry
.
package
.
with_dependency_groups
([],
only
=
True
)
root
.
python_versions
=
"^3.6"
root
.
add_dependency
(
Factory
.
create_dependency
(
name
=
"celery"
,
constraint
=
{
"version"
:
"5.1.2"
,
"python"
:
"<3.7"
}
)
)
root
.
add_dependency
(
Factory
.
create_dependency
(
name
=
"celery"
,
constraint
=
{
"version"
:
"5.2.3"
,
"python"
:
">=3.7"
}
)
)
poetry
.
_package
=
root
exporter
=
Exporter
(
poetry
)
exporter
.
export
(
"requirements.txt"
,
Path
(
tmp_dir
),
sys
.
stdout
)
out
,
err
=
capsys
.
readouterr
()
expected
=
"""
\
celery==5.1.2 ; python_version >= "3.6" and python_version < "3.7"
celery==5.2.3 ; python_version >= "3.7" and python_version < "4.0"
click-didyoumean==0.0.3 ; python_version >= "3.6" and python_version < "3.7"
click-didyoumean==0.3.0 ; python_version >= "3.7" and python_full_version < "4.0.0"
click-plugins==1.1.1 ;
\
python_version >= "3.6" and python_version < "3.7" or
\
python_version >= "3.7" and python_version < "4.0"
click==7.1.2 ; python_version >= "3.6" and python_version < "3.7"
click==8.0.3 ;
\
python_version >= "3.7" and python_version < "4.0" or
\
python_version >= "3.7" and python_full_version < "4.0.0"
"""
assert
out
==
expected
def
test_exporter_handles_extras_next_to_non_extras
(
tmp_dir
:
str
,
poetry
:
Poetry
,
capsys
:
CaptureFixture
):
# Testcase similar to the solver testcase added at #5305.
poetry
.
locker
.
mock_lock_data
(
{
"package"
:
[
{
"name"
:
"localstack"
,
"python-versions"
:
"*"
,
"version"
:
"1.0.0"
,
"category"
:
"main"
,
"optional"
:
False
,
"dependencies"
:
{
"localstack-ext"
:
[
{
"version"
:
">=1.0.0"
},
{
"version"
:
">=1.0.0"
,
"extras"
:
[
"bar"
],
"markers"
:
'extra == "foo"'
,
},
]
},
"extras"
:
{
"foo"
:
[
"localstack-ext (>=1.0.0)"
]},
},
{
"name"
:
"localstack-ext"
,
"python-versions"
:
"*"
,
"version"
:
"1.0.0"
,
"category"
:
"main"
,
"optional"
:
False
,
"dependencies"
:
{
"something"
:
"*"
,
"something-else"
:
{
"version"
:
">=1.0.0"
,
"markers"
:
'extra == "bar"'
,
},
"another-thing"
:
{
"version"
:
">=1.0.0"
,
"markers"
:
'extra == "baz"'
,
},
},
"extras"
:
{
"bar"
:
[
"something-else (>=1.0.0)"
],
"baz"
:
[
"another-thing (>=1.0.0)"
],
},
},
{
"name"
:
"something"
,
"python-versions"
:
"*"
,
"version"
:
"1.0.0"
,
"category"
:
"main"
,
"optional"
:
False
,
"dependencies"
:
{},
},
{
"name"
:
"something-else"
,
"python-versions"
:
"*"
,
"version"
:
"1.0.0"
,
"category"
:
"main"
,
"optional"
:
False
,
"dependencies"
:
{},
},
{
"name"
:
"another-thing"
,
"python-versions"
:
"*"
,
"version"
:
"1.0.0"
,
"category"
:
"main"
,
"optional"
:
False
,
"dependencies"
:
{},
},
],
"metadata"
:
{
"lock-version"
:
"1.1"
,
"python-versions"
:
"^3.6"
,
"content-hash"
:
(
"832b13a88e5020c27cbcd95faa577bf0dbf054a65c023b45dc9442b640d414e6"
),
"hashes"
:
{
"localstack"
:
[],
"localstack-ext"
:
[],
"something"
:
[],
"something-else"
:
[],
"another-thing"
:
[],
},
},
}
)
root
=
poetry
.
package
.
with_dependency_groups
([],
only
=
True
)
root
.
python_versions
=
"^3.6"
root
.
add_dependency
(
Factory
.
create_dependency
(
name
=
"localstack"
,
constraint
=
{
"version"
:
"^1.0.0"
,
"extras"
:
[
"foo"
]}
)
)
poetry
.
_package
=
root
exporter
=
Exporter
(
poetry
)
exporter
.
export
(
"requirements.txt"
,
Path
(
tmp_dir
),
sys
.
stdout
)
out
,
err
=
capsys
.
readouterr
()
expected
=
"""
\
localstack-ext==1.0.0 ; python_version >= "3.6" and python_version < "4.0"
localstack==1.0.0 ; python_version >= "3.6" and python_version < "4.0"
something-else==1.0.0 ; python_version >= "3.6" and python_version < "4.0"
something==1.0.0 ; python_version >= "3.6" and python_version < "4.0"
"""
assert
out
==
expected
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment