Repository: pdm-project/pdm
Pull request #2102 (2023-07-13 07:00:05)

fix: tolerate local version in specifiers
Signed-off-by: Frost Ming <me@frostming.com>

## Pull Request Checklist

- [ ] A news fragment is added in `news/` describing what is new.
- [ ] Test cases added for changed code.

## Describe what you have changed in this PR.


codecov-commenter: ## [Codecov](https://app.codecov.io/gh/pdm-project/pdm/pull/2102?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project) Report
Patch coverage: **`92.30`**% and project coverage change: **`+0.01`** :tada:
> Comparison is base [(`fa34573`)](https://app.codecov.io/gh/pdm-project/pdm/commit/fa34573ebf94a06dc42eb164908ef5b5ad435336?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project) 84.46% compared to head [(`31cd7e3`)](https://app.codecov.io/gh/pdm-project/pdm/pull/2102?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project) 84.48%.

:exclamation: Your organization is not using the GitHub App Integration. As a result you may experience degraded service beginning May 15th. Please [install the Github App Integration](https://github.com/apps/codecov) for your organization. [Read more](https://about.codecov.io/blog/codecov-is-updating-its-github-integration/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project).

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #2102      +/-   ##
==========================================
+ Coverage   84.46%   84.48%   +0.01%     
==========================================
  Files         100      100              
  Lines        9669     9675       +6     
  Branches     2122     2124       +2     
==========================================
+ Hits         8167     8174       +7     
  Misses       1040     1040              
+ Partials      462      461       -1     
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `84.24% <92.30%> (+<0.01%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://app.codecov.io/gh/pdm-project/pdm/pull/2102?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project) | Coverage Δ | |
|---|---|---|
| [src/pdm/models/specifiers.py](https://app.codecov.io/gh/pdm-project/pdm/pull/2102?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project#diff-c3JjL3BkbS9tb2RlbHMvc3BlY2lmaWVycy5weQ==) | `78.43% <91.66%> (+0.43%)` | :arrow_up: |
| [src/pdm/core.py](https://app.codecov.io/gh/pdm-project/pdm/pull/2102?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project#diff-c3JjL3BkbS9jb3JlLnB5) | `91.61% <100.00%> (ø)` | |

... and [1 file with indirect coverage changes](https://app.codecov.io/gh/pdm-project/pdm/pull/2102/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project)


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/pdm-project/pdm/pull/2102?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project).


Patch:
diff --git a/news/2102.bugfix.md b/news/2102.bugfix.md
new file mode 100644
index 00000000..7aec233b
--- /dev/null
+++ b/news/2102.bugfix.md
@@ -0,0 +1,1 @@
+Tolerate and actually ignore the local versions in version specifiers.
diff --git a/src/pdm/core.py b/src/pdm/core.py
index 83eceef8..594f3564 100644
--- a/src/pdm/core.py
+++ b/src/pdm/core.py
@@ -83,7 +83,7 @@ class Core:
         ignore_python_option.add_to_parser(self.parser)
         pep582_option.add_to_parser(self.parser)
 
-        self.subparsers = self.parser.add_subparsers(parser_class=ArgumentParser, metavar="__root__")
+        self.subparsers = self.parser.add_subparsers(parser_class=ArgumentParser, dest="fooo")
         for _, name, _ in pkgutil.iter_modules(COMMANDS_MODULE_PATH):
             module = importlib.import_module(f"pdm.cli.commands.{name}", __name__)
             try:
diff --git a/src/pdm/models/specifiers.py b/src/pdm/models/specifiers.py
index cbe3cc6d..39f9ccfc 100644
--- a/src/pdm/models/specifiers.py
+++ b/src/pdm/models/specifiers.py
@@ -36,16 +36,22 @@ def fix_legacy_specifier(specifier: str) -> str:
     """Since packaging 22.0, legacy specifiers like '>=4.*' are no longer
     supported. We try to normalize them to the new format.
     """
+    from pdm.utils import deprecation_warning
 
     def fix_wildcard(match: Match[str]) -> str:
         operator, _, version = match.groups()
-        if ".*" not in version or operator in ("==", "!="):
+        if operator in ("==", "!="):
             return match.group(0)
-        version = version.replace(".*", ".0")
-        if operator in ("<", "<="):  # <4.* and <=4.* are equivalent to <4.0
-            operator = "<"
-        elif operator in (">", ">="):  # >4.* and >=4.* are equivalent to >=4.0
-            operator = ">="
+        if ".*" in version:
+            deprecation_warning(".* suffix can only be used with `==` or `!=` operators", stacklevel=4)
+            version = version.replace(".*", ".0")
+            if operator in ("<", "<="):  # <4.* and <=4.* are equivalent to <4.0
+                operator = "<"
+            elif operator in (">", ">="):  # >4.* and >=4.* are equivalent to >=4.0
+                operator = ">="
+        elif "+" in version:  # Drop the local version
+            deprecation_warning("Local version label can only be used with `==` or `!=` operators", stacklevel=4)
+            version = version.split("+")[0]
         return f"{operator}{version}"
 
     return _legacy_specifier_re.sub(fix_wildcard, specifier)


Test patch:
diff --git a/tests/models/test_candidates.py b/tests/models/test_candidates.py
index b57bd330..08b0d751 100644
--- a/tests/models/test_candidates.py
+++ b/tests/models/test_candidates.py
@@ -289,6 +289,7 @@ def test_legacy_pep345_tag_link(project):
     assert candidate.requires_python == ">=3,<4"
 
 
+@pytest.mark.filterwarnings("ignore::FutureWarning")
 def test_ignore_invalid_py_version(project):
     project.project_config["pypi.url"] = "https://my.pypi.org/simple"
     req = parse_requirement("wheel")
diff --git a/tests/models/test_requirements.py b/tests/models/test_requirements.py
index a1ecc622..687a4ca2 100644
--- a/tests/models/test_requirements.py
+++ b/tests/models/test_requirements.py
@@ -61,9 +61,15 @@ REQUIREMENTS = [
         "foo<5.0,>=4.0",
         marks=pytest.mark.skipif(not PACKAGING_22, reason="packaging 22+ required"),
     ),
+    pytest.param(
+        "foo>=3.0+g1234; python_version>='3.6'",
+        'foo>=3.0; python_version >= "3.6"',
+        marks=pytest.mark.skipif(not PACKAGING_22, reason="packaging 22+ required"),
+    ),
 ]
 
 
+@pytest.mark.filterwarnings("ignore::FutureWarning")
 @pytest.mark.parametrize("req, result", REQUIREMENTS)
 def test_convert_req_dict_to_req_line(req, result):
     r = parse_requirement(req)
diff --git a/tests/models/test_specifiers.py b/tests/models/test_specifiers.py
index a0bfb0f3..2034c752 100644
--- a/tests/models/test_specifiers.py
+++ b/tests/models/test_specifiers.py
@@ -3,6 +3,7 @@ import pytest
 from pdm.models.specifiers import PySpecSet
 
 
+@pytest.mark.filterwarnings("ignore::FutureWarning")
 @pytest.mark.parametrize(
     "original,normalized",
     [
@@ -23,6 +24,8 @@ from pdm.models.specifiers import PySpecSet
         (">3.4.*", ">=3.4"),
         ("<=3.4.*", "<3.4"),
         ("<3.4.*", "<3.4"),
+        (">=3.0+g1234", ">=3.0"),
+        ("<3.0+g1234", "<3.0"),
         ("<3.10.0a6", "<3.10.0a6"),
         ("<3.10.2a3", "<3.10.2a3"),
     ],
@@ -79,6 +82,7 @@ def test_impossible_pyspec():
     assert str(spec_copy) == "impossible"
 
 
+@pytest.mark.filterwarnings("ignore::FutureWarning")
 @pytest.mark.parametrize(
     "left,right",
     [