Skip to content
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/3301.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add one `safe_compatible` version specifiers saving strategy.
7 changes: 7 additions & 0 deletions src/pdm/cli/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,13 @@ def no_isolation_option(
const="compatible",
help="Save compatible version specifiers",
)
_save_sub_group.add_argument(
"--save-safe-compatible",
action="store_const",
dest="save_strategy",
const="safe_compatible",
help="Save safe compatible version specifiers",
)
_save_sub_group.add_argument(
"--save-wildcard",
action="store_const",
Expand Down
9 changes: 6 additions & 3 deletions src/pdm/cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ def save_version_specifiers(

:param requirements: the requirements to be updated
:param resolved: the resolved mapping
:param save_strategy: compatible/wildcard/exact
:param save_strategy: compatible/safe_compatible/wildcard/exact
"""

def candidate_version(candidates: list[Candidate]) -> Version | None:
Expand All @@ -566,11 +566,14 @@ def candidate_version(candidates: list[Candidate]) -> Version | None:
continue
if save_strategy == "exact":
r.specifier = get_specifier(f"=={version}")
elif save_strategy == "compatible":
elif save_strategy in ["compatible", "safe_compatible"]:
if version.is_prerelease or version.is_devrelease:
r.specifier = get_specifier(f">={version},<{version.major + 1}")
else:
r.specifier = get_specifier(f"~={version.major}.{version.minor}")
if save_strategy == "compatible":
r.specifier = get_specifier(f"~={version.major}.{version.minor}")
else:
r.specifier = get_specifier(f"~={version}")
elif save_strategy == "minimum":
r.specifier = get_specifier(f">={version}")

Expand Down
8 changes: 8 additions & 0 deletions tests/cli/test_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ def test_update_ignore_constraints(project, repository, pdm):
assert project.pyproject.metadata["dependencies"] == ["pytz~=2020.2"]
assert project.get_locked_repository().candidates["pytz"].version == "2020.2"

pdm(["add", "chardet"], obj=project, strict=True)
assert "chardet~=3.0" in project.pyproject.metadata["dependencies"]
assert project.get_locked_repository().candidates["chardet"].version == "3.0.4"
repository.add_candidate("chardet", "3.0.6")

pdm(["update", "chardet", "--unconstrained", "--save-safe-compatible"], obj=project, strict=True)
assert "chardet~=3.0.6" in project.pyproject.metadata["dependencies"]


@pytest.mark.usefixtures("working_set")
@pytest.mark.parametrize("strategy", ["reuse", "all"])
Expand Down