Repository: python-poetry/poetry
Pull request: Fix wheel_file_re RegExp
# Pull Request Check List

Resolves: #9769

<!-- This is just a reminder about the most common mistakes. Please make sure that you tick all *appropriate* boxes.  But please read our [contribution guide](https://python-poetry.org/docs/contributing/) at least once, it will save you unnecessary review cycles! -->

- [x] Added **tests** for changed code.
- [x] Updated **documentation** for changed code.

<!-- If you have *any* questions to *any* of the points above, just **submit and ask**!  This checklist is here to *help* you, not to deter you from contributing! -->

- For performance POC check related issue
- All interfaces are the same
- It covers two problems but I believe that they are close enough (few characters in the single RE)


Problem statement:
Problems with wheel_file_re
### Description

Hello.

I found that performance of the current `wheel_file_old` may be really bad O(N^4) on some inputs.  It may be not a serious problem but for end user it will be almost impossible to find and troubleshoot.

After performance fix I also found `.dist-info` extension is working as expected.

We can verify performance with this POC

```python
import re
import time
from contextlib import contextmanager
import sys

wheel_file_old = re.compile(
    r"^(?P<namever>(?P<name>.+?)-(?P<ver>\d.*?))"
    r"(-(?P<build>\d.*?))?"
    r"-(?P<pyver>.+?)"
    r"-(?P<abi>.+?)"
    r"-(?P<plat>.+?)"
    r"\.whl|\.dist-info$",
    re.VERBOSE,
)
wheel_file_re_new= re.compile(
    r"^(?P<namever>(?P<name>[^-]+)-(?P<ver>[^-]*))"
    r"(-(?P<build>\d[^-]*))?"
    r"-(?P<pyver>[^-]+)"
    r"-(?P<abi>[^-]+)"
    r"-(?P<plat>[^-]+)"
    r"(?:\.whl|\.dist-info)$",
    re.VERBOSE,
)


@contextmanager
def duration(text):
    begin_at = time.monotonic()
    yield
    delta = time.monotonic() - begin_at
    print(f"{text:20} takes {delta:0.6f}s")


pattern = '0-0' + '-' * int(sys.argv[1])

with duration("Current (old) re"):
    wheel_file_old.match(pattern)
with duration("New re"):
    wheel_file_re_new.match(pattern)
```

Example output
```
➜ python poetry-poc.py 250
Current (old) re     takes 0.942238s
New re               takes 0.000003s
```

### Workarounds

- Performance issues should not be a problem for almost all valid wheel names
- Do not use wheels with `.dist-info` extension

### Poetry Installation Method

pip

### Operating System

Ubtuntu

### Poetry Version

2.0.0.dev0

### Poetry Configuration

```bash session
cache-dir = "/home/alex/.cache/pypoetry"
experimental.system-git-client = false
installer.max-workers = null
installer.no-binary = null
installer.only-binary = null
installer.parallel = true
keyring.enabled = true
requests.max-retries = 0
solver.lazy-wheel = true
virtualenvs.create = true
virtualenvs.in-project = null
virtualenvs.options.always-copy = false
virtualenvs.options.no-pip = false
virtualenvs.options.system-site-packages = false
virtualenvs.path = "{cache-dir}/virtualenvs"  # /home/alex/.cache/pypoetry/virtualenvs
virtualenvs.prefer-active-python = false
virtualenvs.prompt = "{project_name}-py{python_version}"
```


### Python Sysconfig

```bash session
any
```


### Example pyproject.toml

```TOML
any
```


### Poetry Runtime Logs

```bash session
any
```

Hints from issue discussion:

Commit message: Merge branch 'main' into fix_wheel_file_re

Patch to review:
diff --git a/src/poetry/utils/patterns.py b/src/poetry/utils/patterns.py
index 717a569ddcd..5127e70ac7a 100644
--- a/src/poetry/utils/patterns.py
+++ b/src/poetry/utils/patterns.py
@@ -4,12 +4,12 @@
 
 
 wheel_file_re = re.compile(
-    r"^(?P<namever>(?P<name>.+?)-(?P<ver>\d.*?))"
-    r"(-(?P<build>\d.*?))?"
-    r"-(?P<pyver>.+?)"
-    r"-(?P<abi>.+?)"
-    r"-(?P<plat>.+?)"
-    r"\.whl|\.dist-info$",
+    r"^(?P<namever>(?P<name>.+?)-(?P<ver>\d[^-]*))"
+    r"(-(?P<build>\d[^-]*))?"
+    r"-(?P<pyver>[^-]+)"
+    r"-(?P<abi>[^-]+)"
+    r"-(?P<plat>[^-]+)"
+    r"\.whl$",
     re.VERBOSE,
 )
 
diff --git a/tests/utils/test_patterns.py b/tests/utils/test_patterns.py
index f3df7d52be6..01048563a1d 100644
--- a/tests/utils/test_patterns.py
+++ b/tests/utils/test_patterns.py
@@ -32,6 +32,18 @@
                 "plat": "manylinux2010_x86_64",
             },
         ),
+        (
+            "isort-metadata-4.3.4-py2-none-any.whl",
+            {
+                "namever": "isort-metadata-4.3.4",
+                "name": "isort-metadata",
+                "ver": "4.3.4",
+                "build": None,
+                "pyver": "py2",
+                "abi": "none",
+                "plat": "any",
+            },
+        ),
     ],
 )
 def test_wheel_file_re(filename: str, expected: dict[str, str | None]) -> None:
@@ -42,6 +54,12 @@ def test_wheel_file_re(filename: str, expected: dict[str, str | None]) -> None:
     assert groups == expected
 
 
+@pytest.mark.parametrize("filename", [".dist-info"])
+def test_wheel_file_re_not_matches(filename: str) -> None:
+    match = patterns.wheel_file_re.match(filename)
+    assert match is None
+
+
 @pytest.mark.parametrize(
     ["filename", "expected"],
     [


Code review comments:
diff_hunk: @@ -42,6 +54,12 @@ def test_wheel_file_re(filename: str, expected: dict[str, str | None]) -> None:
     assert groups == expected
 
 
+@pytest.mark.parametrize("filename", [".dist-info"])
+def test_wheel_file_re_not_matches(filename: str) -> None:
+    match = patterns.wheel_file_re.match(filename)
+    assert match is None
+
+
path: tests/utils/test_patterns.py
text: @user1:
This test is no longer of much use, is it? Thus, I think we can remove it.

@author:
Yes, agree. Test has been removed.