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
4b8384cd
Unverified
Commit
4b8384cd
authored
Jul 05, 2021
by
Sébastien Eustace
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update the add and remove commands to support groups
parent
26f13f78
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
347 additions
and
85 deletions
+347
-85
poetry/console/commands/add.py
+73
-48
poetry/console/commands/remove.py
+81
-30
tests/console/commands/test_add.py
+45
-6
tests/console/commands/test_remove.py
+148
-0
tests/utils/test_exporter.py
+0
-1
No files found.
poetry/console/commands/add.py
View file @
4b8384cd
# -*- coding: utf-8 -*-
from
typing
import
Dict
from
typing
import
List
...
...
@@ -16,6 +15,13 @@ class AddCommand(InstallerCommand, InitCommand):
arguments
=
[
argument
(
"name"
,
"The packages to add."
,
multiple
=
True
)]
options
=
[
option
(
"group"
,
"-G"
,
"The group to add the dependency to."
,
flag
=
False
,
default
=
"default"
,
),
option
(
"dev"
,
"D"
,
"Add as a development dependency."
),
option
(
"editable"
,
"e"
,
"Add vcs/path dependencies as editable."
),
option
(
...
...
@@ -71,31 +77,55 @@ class AddCommand(InstallerCommand, InitCommand):
def
handle
(
self
)
->
int
:
from
tomlkit
import
inline_table
from
tomlkit
import
parse
as
parse_toml
from
tomlkit
import
table
from
poetry.core.semver.helpers
import
parse_constraint
from
poetry.factory
import
Factory
packages
=
self
.
argument
(
"name"
)
is_dev
=
self
.
option
(
"dev"
)
if
self
.
option
(
"dev"
):
self
.
line
(
"<warning>The --dev option is deprecated, "
"use the `--group dev` notation instead.</warning>"
)
self
.
line
(
""
)
group
=
"dev"
else
:
group
=
self
.
option
(
"group"
)
if
self
.
option
(
"extras"
)
and
len
(
packages
)
>
1
:
raise
ValueError
(
"You can only specify one package
"
"
when using the --extras option"
"You can only specify one package when using the --extras option"
)
section
=
"dependencies"
if
is_dev
:
section
=
"dev-dependencies"
original_content
=
self
.
poetry
.
file
.
read
()
content
=
self
.
poetry
.
file
.
read
()
poetry_content
=
content
[
"tool"
][
"poetry"
]
if
section
not
in
poetry_content
:
poetry_content
[
section
]
=
{}
if
group
==
"default"
:
if
"dependencies"
not
in
poetry_content
:
poetry_content
[
"dependencies"
]
=
table
()
existing_packages
=
self
.
get_existing_packages_from_input
(
packages
,
poetry_content
,
section
)
section
=
poetry_content
[
"dependencies"
]
else
:
if
"group"
not
in
poetry_content
:
group_table
=
table
()
group_table
.
_is_super_table
=
True
poetry_content
.
value
.
_insert_after
(
"dependencies"
,
"group"
,
group_table
)
groups
=
poetry_content
[
"group"
]
if
group
not
in
groups
:
group_table
=
parse_toml
(
f
"[tool.poetry.group.{group}.dependencies]
\n\n
"
)[
"tool"
][
"poetry"
][
"group"
][
group
]
poetry_content
[
"group"
][
group
]
=
group_table
if
"dependencies"
not
in
poetry_content
[
"group"
][
group
]:
poetry_content
[
"group"
][
group
][
"dependencies"
]
=
table
()
section
=
poetry_content
[
"group"
][
group
][
"dependencies"
]
existing_packages
=
self
.
get_existing_packages_from_input
(
packages
,
section
)
if
existing_packages
:
self
.
notify_about_existing_packages
(
existing_packages
)
...
...
@@ -165,53 +195,48 @@ class AddCommand(InstallerCommand, InitCommand):
if
len
(
constraint
)
==
1
and
"version"
in
constraint
:
constraint
=
constraint
[
"version"
]
poetry_content
[
section
][
_constraint
[
"name"
]]
=
constraint
section
[
_constraint
[
"name"
]]
=
constraint
self
.
poetry
.
package
.
add_dependency
(
Factory
.
create_dependency
(
_constraint
[
"name"
],
constraint
,
groups
=
[
group
],
root_dir
=
self
.
poetry
.
file
.
parent
,
)
)
try
:
# Write new content
self
.
poetry
.
file
.
write
(
content
)
# Refresh the locker
self
.
poetry
.
set_locker
(
self
.
poetry
.
locker
.
__class__
(
self
.
poetry
.
locker
.
lock
.
path
,
poetry_content
)
)
self
.
_installer
.
set_locker
(
self
.
poetry
.
locker
)
# Cosmetic new line
self
.
line
(
""
)
# Cosmetic new line
self
.
line
(
""
)
# Update packages
self
.
reset_poetry
()
self
.
_installer
.
set_package
(
self
.
poetry
.
package
)
self
.
_installer
.
dry_run
(
self
.
option
(
"dry-run"
))
self
.
_installer
.
verbose
(
self
.
_io
.
is_verbose
())
self
.
_installer
.
update
(
True
)
if
self
.
option
(
"lock"
):
self
.
_installer
.
lock
()
self
.
_installer
.
whitelist
([
r
[
"name"
]
for
r
in
requirements
])
status
=
self
.
_installer
.
run
()
except
BaseException
:
# Using BaseException here as some exceptions, eg: KeyboardInterrupt, do not inherit from Exception
self
.
poetry
.
file
.
write
(
original_content
)
raise
if
status
!=
0
or
self
.
option
(
"dry-run"
):
# Revert changes
if
not
self
.
option
(
"dry-run"
):
self
.
line_error
(
"
\n
"
"<error>Failed to add packages, reverting the pyproject.toml file "
"to its original content.</error>"
)
self
.
_installer
.
set_package
(
self
.
poetry
.
package
)
self
.
_installer
.
dry_run
(
self
.
option
(
"dry-run"
))
self
.
_installer
.
verbose
(
self
.
_io
.
is_verbose
())
self
.
_installer
.
update
(
True
)
if
self
.
option
(
"lock"
):
self
.
_installer
.
lock
()
self
.
_installer
.
whitelist
([
r
[
"name"
]
for
r
in
requirements
])
self
.
poetry
.
file
.
write
(
original_content
)
status
=
self
.
_installer
.
run
()
if
status
==
0
and
not
self
.
option
(
"dry-run"
):
self
.
poetry
.
file
.
write
(
content
)
return
status
def
get_existing_packages_from_input
(
self
,
packages
:
List
[
str
],
poetry_content
:
Dict
,
target_section
:
str
self
,
packages
:
List
[
str
],
section
:
Dict
)
->
List
[
str
]:
existing_packages
=
[]
for
name
in
packages
:
for
key
in
poetry_content
[
target_section
]
:
for
key
in
section
:
if
key
.
lower
()
==
name
.
lower
():
existing_packages
.
append
(
name
)
...
...
poetry/console/commands/remove.py
View file @
4b8384cd
from
typing
import
Any
from
typing
import
Dict
from
typing
import
List
from
cleo.helpers
import
argument
from
cleo.helpers
import
option
from
...utils.helpers
import
canonicalize_name
from
.installer_command
import
InstallerCommand
...
...
@@ -12,6 +15,7 @@ class RemoveCommand(InstallerCommand):
arguments
=
[
argument
(
"packages"
,
"The packages to remove."
,
multiple
=
True
)]
options
=
[
option
(
"group"
,
"G"
,
"The group to remove the dependency from."
,
flag
=
False
),
option
(
"dev"
,
"D"
,
"Remove a package from the development dependencies."
),
option
(
"dry-run"
,
...
...
@@ -30,39 +34,70 @@ list of installed packages
def
handle
(
self
)
->
int
:
packages
=
self
.
argument
(
"packages"
)
is_dev
=
self
.
option
(
"dev"
)
if
self
.
option
(
"dev"
):
self
.
line
(
"<warning>The --dev option is deprecated, "
"use the `--group dev` notation instead.</warning>"
)
self
.
line
(
""
)
group
=
"dev"
else
:
group
=
self
.
option
(
"group"
)
content
=
self
.
poetry
.
file
.
read
()
poetry_content
=
content
[
"tool"
][
"poetry"
]
section
=
"dependencies"
if
is_dev
:
section
=
"dev-dependencies"
# Deleting entries
requirements
=
{}
for
name
in
packages
:
found
=
False
for
key
in
poetry_content
[
section
]:
if
key
.
lower
()
==
name
.
lower
():
found
=
True
requirements
[
key
]
=
poetry_content
[
section
][
key
]
break
if
not
found
:
raise
ValueError
(
"Package {} not found"
.
format
(
name
))
for
key
in
requirements
:
del
poetry_content
[
section
][
key
]
dependencies
=
(
self
.
poetry
.
package
.
requires
if
section
==
"dependencies"
else
self
.
poetry
.
package
.
dev_requires
if
group
is
None
:
removed
=
[]
group_sections
=
[]
for
group_name
,
group_section
in
poetry_content
.
get
(
"group"
,
{})
.
items
():
group_sections
.
append
(
(
group_name
,
group_section
.
get
(
"dependencies"
,
{}))
)
for
group_name
,
section
in
[
(
"default"
,
poetry_content
[
"dependencies"
])
]
+
group_sections
:
removed
+=
self
.
_remove_packages
(
packages
,
section
,
group_name
)
if
group_name
!=
"default"
:
if
not
section
:
del
poetry_content
[
"group"
][
group_name
]
else
:
poetry_content
[
"group"
][
group_name
][
"dependencies"
]
=
section
elif
group
==
"dev"
and
"dev-dependencies"
in
poetry_content
:
# We need to account for the old `dev-dependencies` section
removed
=
self
.
_remove_packages
(
packages
,
poetry_content
[
"dev-dependencies"
],
"dev"
)
if
not
poetry_content
[
"dev-dependencies"
]:
del
poetry_content
[
"dev-dependencies"
]
else
:
removed
=
self
.
_remove_packages
(
packages
,
poetry_content
[
"group"
][
group
]
.
get
(
"dependencies"
,
{}),
group
)
for
i
,
dependency
in
enumerate
(
reversed
(
dependencies
)):
if
dependency
.
name
==
canonicalize_name
(
key
):
del
dependencies
[
-
i
]
if
not
poetry_content
[
"group"
][
group
]:
del
poetry_content
[
"group"
][
group
]
if
"group"
in
poetry_content
and
not
poetry_content
[
"group"
]:
del
poetry_content
[
"group"
]
removed
=
set
(
removed
)
not_found
=
set
(
packages
)
.
difference
(
removed
)
if
not_found
:
raise
ValueError
(
"The following packages were not found: {}"
.
format
(
", "
.
join
(
sorted
(
not_found
))
)
)
# Refresh the locker
self
.
poetry
.
set_locker
(
self
.
poetry
.
locker
.
__class__
(
self
.
poetry
.
locker
.
lock
.
path
,
poetry_content
)
)
self
.
_installer
.
set_locker
(
self
.
poetry
.
locker
)
# Update packages
self
.
_installer
.
use_executor
(
...
...
@@ -72,7 +107,7 @@ list of installed packages
self
.
_installer
.
dry_run
(
self
.
option
(
"dry-run"
))
self
.
_installer
.
verbose
(
self
.
_io
.
is_verbose
())
self
.
_installer
.
update
(
True
)
self
.
_installer
.
whitelist
(
re
quirements
)
self
.
_installer
.
whitelist
(
re
moved
)
status
=
self
.
_installer
.
run
()
...
...
@@ -80,3 +115,19 @@ list of installed packages
self
.
poetry
.
file
.
write
(
content
)
return
status
def
_remove_packages
(
self
,
packages
:
List
[
str
],
section
:
Dict
[
str
,
Any
],
group_name
:
str
)
->
List
[
str
]:
removed
=
[]
group
=
self
.
poetry
.
package
.
dependency_group
(
group_name
)
section_keys
=
list
(
section
.
keys
())
for
package
in
packages
:
for
existing_package
in
section_keys
:
if
existing_package
.
lower
()
==
package
.
lower
():
del
section
[
existing_package
]
removed
.
append
(
package
)
group
.
remove_dependency
(
package
)
return
removed
tests/console/commands/test_add.py
View file @
4b8384cd
...
...
@@ -667,13 +667,52 @@ def test_add_constraint_not_found_with_source(app, poetry, mocker, tester):
assert
"Could not find a matching version of package cachy"
==
str
(
e
.
value
)
def
test_add_to_section_that_does_no_exist_yet
(
app
,
repo
,
tester
):
def
test_add_to_section_that_does_not_exist_yet
(
app
,
repo
,
tester
):
repo
.
add_package
(
get_package
(
"cachy"
,
"0.1.0"
))
repo
.
add_package
(
get_package
(
"cachy"
,
"0.2.0"
))
tester
.
execute
(
"cachy --group dev"
)
expected
=
"""
\
Using version ^0.2.0 for cachy
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
• Installing cachy (0.2.0)
"""
assert
expected
==
tester
.
io
.
fetch_output
()
assert
1
==
tester
.
command
.
installer
.
executor
.
installations_count
content
=
app
.
poetry
.
file
.
read
()[
"tool"
][
"poetry"
]
assert
"cachy"
in
content
[
"group"
][
"dev"
][
"dependencies"
]
assert
content
[
"group"
][
"dev"
][
"dependencies"
][
"cachy"
]
==
"^0.2.0"
expected
=
"""
\
[tool.poetry.group.dev.dependencies]
cachy = "^0.2.0"
"""
assert
expected
in
content
.
as_string
()
def
test_add_to_dev_section_deprecated
(
app
,
repo
,
tester
):
repo
.
add_package
(
get_package
(
"cachy"
,
"0.1.0"
))
repo
.
add_package
(
get_package
(
"cachy"
,
"0.2.0"
))
tester
.
execute
(
"cachy --dev"
)
expected
=
"""
\
The --dev option is deprecated, use the `--group dev` notation instead.
Using version ^0.2.0 for cachy
Updating dependencies
...
...
@@ -691,8 +730,8 @@ Package operations: 1 install, 0 updates, 0 removals
content
=
app
.
poetry
.
file
.
read
()[
"tool"
][
"poetry"
]
assert
"cachy"
in
content
[
"
dev-
dependencies"
]
assert
content
[
"
dev-
dependencies"
][
"cachy"
]
==
"^0.2.0"
assert
"cachy"
in
content
[
"
group"
][
"dev"
][
"
dependencies"
]
assert
content
[
"
group"
][
"dev"
][
"
dependencies"
][
"cachy"
]
==
"^0.2.0"
def
test_add_should_not_select_prereleases
(
app
,
repo
,
tester
):
...
...
@@ -1487,7 +1526,7 @@ def test_add_to_section_that_does_no_exist_yet_old_installer(
repo
.
add_package
(
get_package
(
"cachy"
,
"0.1.0"
))
repo
.
add_package
(
get_package
(
"cachy"
,
"0.2.0"
))
old_tester
.
execute
(
"cachy --dev"
)
old_tester
.
execute
(
"cachy --
group
dev"
)
expected
=
"""
\
Using version ^0.2.0 for cachy
...
...
@@ -1508,8 +1547,8 @@ Package operations: 1 install, 0 updates, 0 removals
content
=
app
.
poetry
.
file
.
read
()[
"tool"
][
"poetry"
]
assert
"cachy"
in
content
[
"
dev-
dependencies"
]
assert
content
[
"
dev-
dependencies"
][
"cachy"
]
==
"^0.2.0"
assert
"cachy"
in
content
[
"
group"
][
"dev"
][
"
dependencies"
]
assert
content
[
"
group"
][
"dev"
][
"
dependencies"
][
"cachy"
]
==
"^0.2.0"
def
test_add_should_not_select_prereleases_old_installer
(
...
...
tests/console/commands/test_remove.py
View file @
4b8384cd
import
pytest
import
tomlkit
from
poetry.core.packages.package
import
Package
from
poetry.factory
import
Factory
@pytest.fixture
()
...
...
@@ -8,6 +10,152 @@ def tester(command_tester_factory):
return
command_tester_factory
(
"remove"
)
def
test_remove_without_specific_group_removes_from_all_groups
(
tester
,
app
,
repo
,
command_tester_factory
,
installed
):
"""
Removing without specifying a group removes packages from all groups.
"""
installed
.
add_package
(
Package
(
"foo"
,
"2.0.0"
))
repo
.
add_package
(
Package
(
"foo"
,
"2.0.0"
))
repo
.
add_package
(
Package
(
"baz"
,
"1.0.0"
))
content
=
app
.
poetry
.
file
.
read
()
groups_content
=
tomlkit
.
parse
(
"""
\
[tool.poetry.group.bar.dependencies]
foo = "^2.0.0"
baz = "^1.0.0"
"""
)
content
[
"tool"
][
"poetry"
][
"dependencies"
][
"foo"
]
=
"^2.0.0"
content
[
"tool"
][
"poetry"
]
.
value
.
_insert_after
(
"dependencies"
,
"group"
,
groups_content
[
"tool"
][
"poetry"
][
"group"
]
)
app
.
poetry
.
file
.
write
(
content
)
app
.
poetry
.
package
.
add_dependency
(
Factory
.
create_dependency
(
"foo"
,
"^2.0.0"
))
app
.
poetry
.
package
.
add_dependency
(
Factory
.
create_dependency
(
"foo"
,
"^2.0.0"
,
groups
=
[
"bar"
])
)
app
.
poetry
.
package
.
add_dependency
(
Factory
.
create_dependency
(
"baz"
,
"^1.0.0"
,
groups
=
[
"bar"
])
)
tester
.
execute
(
"foo"
)
content
=
app
.
poetry
.
file
.
read
()[
"tool"
][
"poetry"
]
assert
"foo"
not
in
content
[
"dependencies"
]
assert
"foo"
not
in
content
[
"group"
][
"bar"
][
"dependencies"
]
assert
"baz"
in
content
[
"group"
][
"bar"
][
"dependencies"
]
expected
=
"""
\
[tool.poetry.group.bar.dependencies]
baz = "^1.0.0"
"""
assert
expected
in
content
.
as_string
()
def
test_remove_without_specific_group_removes_from_specific_groups
(
tester
,
app
,
repo
,
command_tester_factory
,
installed
):
"""
Removing with a specific group given removes packages only from this group.
"""
installed
.
add_package
(
Package
(
"foo"
,
"2.0.0"
))
repo
.
add_package
(
Package
(
"foo"
,
"2.0.0"
))
repo
.
add_package
(
Package
(
"baz"
,
"1.0.0"
))
content
=
app
.
poetry
.
file
.
read
()
groups_content
=
tomlkit
.
parse
(
"""
\
[tool.poetry.group.bar.dependencies]
foo = "^2.0.0"
baz = "^1.0.0"
"""
)
content
[
"tool"
][
"poetry"
][
"dependencies"
][
"foo"
]
=
"^2.0.0"
content
[
"tool"
][
"poetry"
]
.
value
.
_insert_after
(
"dependencies"
,
"group"
,
groups_content
[
"tool"
][
"poetry"
][
"group"
]
)
app
.
poetry
.
file
.
write
(
content
)
app
.
poetry
.
package
.
add_dependency
(
Factory
.
create_dependency
(
"foo"
,
"^2.0.0"
))
app
.
poetry
.
package
.
add_dependency
(
Factory
.
create_dependency
(
"foo"
,
"^2.0.0"
,
groups
=
[
"bar"
])
)
app
.
poetry
.
package
.
add_dependency
(
Factory
.
create_dependency
(
"baz"
,
"^1.0.0"
,
groups
=
[
"bar"
])
)
tester
.
execute
(
"foo --group bar"
)
content
=
app
.
poetry
.
file
.
read
()[
"tool"
][
"poetry"
]
assert
"foo"
in
content
[
"dependencies"
]
assert
"foo"
not
in
content
[
"group"
][
"bar"
][
"dependencies"
]
assert
"baz"
in
content
[
"group"
][
"bar"
][
"dependencies"
]
expected
=
"""
\
[tool.poetry.group.bar.dependencies]
baz = "^1.0.0"
"""
assert
expected
in
content
.
as_string
()
def
test_remove_does_not_live_empty_groups
(
tester
,
app
,
repo
,
command_tester_factory
,
installed
):
"""
Empty groups are automatically discarded after package removal.
"""
installed
.
add_package
(
Package
(
"foo"
,
"2.0.0"
))
repo
.
add_package
(
Package
(
"foo"
,
"2.0.0"
))
repo
.
add_package
(
Package
(
"baz"
,
"1.0.0"
))
content
=
app
.
poetry
.
file
.
read
()
groups_content
=
tomlkit
.
parse
(
"""
\
[tool.poetry.group.bar.dependencies]
foo = "^2.0.0"
baz = "^1.0.0"
"""
)
content
[
"tool"
][
"poetry"
][
"dependencies"
][
"foo"
]
=
"^2.0.0"
content
[
"tool"
][
"poetry"
]
.
value
.
_insert_after
(
"dependencies"
,
"group"
,
groups_content
[
"tool"
][
"poetry"
][
"group"
]
)
app
.
poetry
.
file
.
write
(
content
)
app
.
poetry
.
package
.
add_dependency
(
Factory
.
create_dependency
(
"foo"
,
"^2.0.0"
))
app
.
poetry
.
package
.
add_dependency
(
Factory
.
create_dependency
(
"foo"
,
"^2.0.0"
,
groups
=
[
"bar"
])
)
app
.
poetry
.
package
.
add_dependency
(
Factory
.
create_dependency
(
"baz"
,
"^1.0.0"
,
groups
=
[
"bar"
])
)
tester
.
execute
(
"foo baz --group bar"
)
content
=
app
.
poetry
.
file
.
read
()[
"tool"
][
"poetry"
]
assert
"foo"
in
content
[
"dependencies"
]
assert
"foo"
not
in
content
[
"group"
][
"bar"
][
"dependencies"
]
assert
"baz"
not
in
content
[
"group"
][
"bar"
][
"dependencies"
]
assert
"[tool.poetry.group.bar]"
not
in
content
.
as_string
()
assert
"[tool.poetry.group]"
not
in
content
.
as_string
()
def
test_remove_command_should_not_write_changes_upon_installer_errors
(
tester
,
app
,
repo
,
command_tester_factory
,
mocker
):
...
...
tests/utils/test_exporter.py
View file @
4b8384cd
...
...
@@ -823,7 +823,6 @@ def test_exporter_can_export_requirements_txt_with_git_packages(tmp_dir, poetry)
}
)
set_package_requires
(
poetry
)
print
(
poetry
.
package
.
all_requires
)
exporter
=
Exporter
(
poetry
)
...
...
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