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

feat: allow updating specific sub-deps with --allow-transitive flag #2689

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/2628.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add the boolean flag `--allow-transitive` to the `update` command for allowing updating specific sub-dependencies (i.e., transitive dependencies) in the lock file.
29 changes: 23 additions & 6 deletions src/pdm/cli/commands/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None:
action="store_false",
help="Only update lock file but do not sync packages",
)
parser.add_argument(
"--allow-transitive",
dest="allow_transitives",
default=False,
action="store_true",
help="Allow updating of transitive dependencies",
)
parser.add_argument("packages", nargs="*", help="If packages are given, only update them")
parser.set_defaults(dev=None)

Expand All @@ -85,6 +92,7 @@ def handle(self, project: Project, options: argparse.Namespace) -> None:
prerelease=options.prerelease,
fail_fast=options.fail_fast,
hooks=HookManager(project, options.skip),
allow_transitives=options.allow_transitives,
)

@staticmethod
Expand All @@ -104,6 +112,7 @@ def do_update(
prerelease: bool | None = None,
fail_fast: bool = False,
hooks: HookManager | None = None,
allow_transitives: bool = False,
) -> None:
"""Update specified packages or all packages"""
from itertools import chain
Expand Down Expand Up @@ -135,17 +144,24 @@ def do_update(
raise ProjectError(f"Requested group not in lockfile: {group}")
dependencies = all_dependencies[group]
for name in packages:
matched_name = next(
(k for k in dependencies if normalize_name(strip_extras(k)[0]) == normalize_name(name)),
normalized_name = normalize_name(name)
matched_req = next(
(v for k, v in dependencies.items() if normalize_name(strip_extras(k)[0]) == normalized_name),
None,
)
if not matched_name:
if not matched_req and allow_transitives:
candidates = project.locked_repository.all_candidates
matched_req = next(
(v.req for k, v in candidates.items() if normalize_name(strip_extras(k)[0]) == normalized_name),
None,
)
if not matched_req:
raise ProjectError(
f"[req]{name}[/] does not exist in [primary]{group}[/] "
f"{'dev-' if selection.dev else ''}dependencies."
)
dependencies[matched_name].prerelease = prerelease
updated_deps[group][matched_name] = dependencies[matched_name]
matched_req.prerelease = prerelease
updated_deps[group][normalized_name] = matched_req
project.core.ui.echo(
"Updating packages: {}.".format(
", ".join(f"[req]{v}[/]" for v in chain.from_iterable(updated_deps.values()))
Expand Down Expand Up @@ -183,7 +199,8 @@ def do_update(
if not dry_run:
if unconstrained:
for group, deps in updated_deps.items():
project.add_dependencies(deps, group, selection.dev or False)
direct_deps = {dep: req for dep, req in deps.items() if dep in all_dependencies[group]}
project.add_dependencies(direct_deps, group, selection.dev or False)
project.write_lockfile(project.lockfile._data, False)
if sync or dry_run:
do_sync(
Expand Down
2 changes: 1 addition & 1 deletion src/pdm/cli/completions/pdm.bash
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ _pdm_a919b69078acdf0a_complete()
;;

(update)
opts="--config-setting --dev --fail-fast --frozen-lockfile --global --group --help --lockfile --no-default --no-editable --no-isolation --no-self --no-sync --outdated --prerelease --production --project --quiet --save-compatible --save-exact --save-minimum --save-wildcard --skip --stable --top --unconstrained --update-all --update-eager --update-reuse --update-reuse-installed --venv --verbose --without"
opts="--config-setting --allow-transitive --dev --fail-fast --frozen-lockfile --global --group --help --lockfile --no-default --no-editable --no-isolation --no-self --no-sync --outdated --prerelease --production --project --quiet --save-compatible --save-exact --save-minimum --save-wildcard --skip --stable --top --unconstrained --update-all --update-eager --update-reuse --update-reuse-installed --venv --verbose --without"
;;

(use)
Expand Down
1 change: 1 addition & 0 deletions src/pdm/cli/completions/pdm.fish
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,7 @@ complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l without -d 'Exclude
# update
complete -c pdm -f -n '__fish_pdm_a919b69078acdf0a_complete_no_subcommand' -a update -d 'Update package(s) in pyproject.toml'
complete -c pdm -A -n '__fish_seen_subcommand_from update' -l config-setting -d 'Pass options to the builder. options with a value must be specified after "=": `--config-setting=key(=value)` or `-Ckey(=value)`'
complete -c pdm -A -n '__fish_seen_subcommand_from update' -l allow-transitive -d 'Allow specifying transitive dependencies'
complete -c pdm -A -n '__fish_seen_subcommand_from update' -l dev -d 'Select dev dependencies'
complete -c pdm -A -n '__fish_seen_subcommand_from update' -l fail-fast -d 'Abort on first installation error'
complete -c pdm -A -n '__fish_seen_subcommand_from update' -l frozen-lockfile -d 'Don\'t try to create or update the lockfile. [env var: PDM_FROZEN_LOCKFILE]'
Expand Down
2 changes: 1 addition & 1 deletion src/pdm/cli/completions/pdm.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ function TabExpansion($line, $lastWord) {
"--save-minimum", "--update-eager", "--update-reuse", "--update-all", "-g", "--global", "--dry-run",
"--outdated", "--top", "-u", "--unconstrained", "--no-editable", "--no-self", "--no-isolation",
"--no-sync", "--pre", "--prerelease", "-L", "--lockfile", "--fail-fast", "-x", "--frozen-lockfile",
"-C", "--config-setting", "--update-reuse-installed", "--stable"
"-C", "--config-setting", "--update-reuse-installed", "--stable", "--allow-transitive"
)),
$sectionOption,
$skipOption,
Expand Down
1 change: 1 addition & 0 deletions src/pdm/cli/completions/pdm.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ _pdm() {
{-G+,--group+,--with+}'[Select group of optional-dependencies or dev-dependencies(with -d). Can be supplied multiple times, use ":all" to include all groups under the same species]:group:_pdm_groups'
"--without+[Exclude groups of optional-dependencies or dev-dependencies]:group:_pdm_groups"
{-L,--lockfile}'[Specify another lockfile path, or use `PDM_LOCKFILE` env variable. Default: pdm.lock]:lockfile:_files'
'--allow-transitive[Allow specifying transitive dependencies]'
'--save-compatible[Save compatible version specifiers]'
'--save-wildcard[Save wildcard version specifiers]'
'--save-exact[Save exact version specifiers]'
Expand Down
54 changes: 54 additions & 0 deletions tests/cli/test_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,60 @@ def test_update_specified_packages_eager_mode(project, repository, pdm):
assert locked_candidates["pytz"].version == "2019.3"


@pytest.mark.usefixtures("working_set")
def test_update_transitive(project, repository, pdm):
pdm(["add", "requests", "--no-sync"], obj=project, strict=True)
repository.add_candidate("chardet", "3.0.5")
repository.add_candidate("requests", "2.20.0")
repository.add_dependencies(
"requests",
"2.20.0",
[
"certifi>=2017.4.17",
"chardet<3.1.0,>=3.0.2",
"idna<2.8,>=2.5",
"urllib3<1.24,>=1.21.1",
],
)
pdm(["update", "--allow-transitive", "chardet"], obj=project, strict=True)
locked_candidates = project.locked_repository.all_candidates
assert not any("chardet" in dependency for dependency in project.pyproject.metadata["dependencies"])
assert locked_candidates["chardet"].version == "3.0.5"
assert locked_candidates["requests"].version == "2.19.1"


@pytest.mark.usefixtures("working_set")
def test_update_transitive_nonexistant_dependencies(project, repository, pdm):
pdm(["add", "requests", "--no-sync"], obj=project, strict=True)
result = pdm(["update", "--allow-transitive", "numpy"])
assert "ProjectError" in result.stderr
assert "numpy does not exist in" in result.stderr


@pytest.mark.usefixtures("working_set")
def test_update_transitive_non_transitive_dependencies(project, repository, pdm):
pdm(["add", "requests", "pytz", "--no-sync"], obj=project, strict=True)
repository.add_candidate("pytz", "2019.6")
repository.add_candidate("chardet", "3.0.5")
repository.add_candidate("requests", "2.20.0")
repository.add_dependencies(
"requests",
"2.20.0",
[
"certifi>=2017.4.17",
"chardet<3.1.0,>=3.0.2",
"idna<2.8,>=2.5",
"urllib3<1.24,>=1.21.1",
],
)
pdm(["update", "--allow-transitive", "requests", "chardet", "pytz"], obj=project, strict=True)
locked_candidates = project.locked_repository.all_candidates
assert not any("chardet" in dependency for dependency in project.pyproject.metadata["dependencies"])
assert locked_candidates["requests"].version == "2.20.0"
assert locked_candidates["chardet"].version == "3.0.5"
assert locked_candidates["pytz"].version == "2019.6"


@pytest.mark.usefixtures("working_set")
def test_update_specified_packages_eager_mode_config(project, repository, pdm):
pdm(["add", "requests", "pytz", "--no-sync"], obj=project, strict=True)
Expand Down