Repository: pyodide/pyodide

Issue:
Micropip doesn't handle post releases correctly
`await micropip.install("xo-gd==3.2.2.post4")` gives 
```
  File "/lib/python3.10/site-packages/micropip/_micropip.py", line 345, in find_wheel
    release = releases[str(ver)]
KeyError: '3.2.2.post4'
```
it looks like the data is actually stored under the key `3.2.2-4`.


Discussion:
Does `xo-gd==3.2.2.post4` actually exist? I think I can't install it locally with `pip`.

I was able to install some other packages with `.post` release.

```py
import micropip
await micropip.install("pytorch-lightning-bolts==0.3.2.post1", deps=False)
```
Huh. `await micropip.install("xo-gd")` tries to install 3.2.2.post4
Okay, it seem like `packaging.version.Version` converts `3.2.2-4` to `3.2.2.post4`...

```py
>>> from packaging.version import Version
>>> Version("3.2.2-4")
<Version('3.2.2.post4')>
```
Seems like maybe the problem here is that you shouldn't name your versions like that, so we should close this as wont-fix.
I think I can fix that, I'll open a PR.
Is this a valid version number though?
No it is not. `pip` is not possible to install `xo-gd==3.2.2-4`. It seems like it has invalid metadata...

```sh
$ pip install xo-gd==3.2.2-4
ERROR: Could not find a version that satisfies the requirement xo-gd==3.2.2-4 (from versions: 3.1.4.1, 3.1.4.2, 3.1.4.3, 3.1.4.11, 3.1.4.12, 3.1.4.35, 3.1.5, 3.1.5.1, 3.1.5.3, 3.1.5.4, 3.1.5.5, 3.1.5.6, 3.2.1, 3.2.1.1, 3.2.2.post5, 3.2.3, 3.2.3.2, 3.2.4, 3.2.5, 3.2.6, 3.2.7, 3.2.8)
ERROR: No matching distribution found for xo-gd==3.2.2-4
```

```sh
$ pip install xo-gd==3.2.2.post5
Collecting xo-gd==3.2.2.post5
  Using cached xo-gd-3.2.2-5.tar.gz (31 kB)
  Preparing metadata (setup.py) ... done
  WARNING: Requested xo-gd==3.2.2.post5 from https://files.pythonhosted.org/packages/17/66/b21ac5a35d14328f8369fc60fcb43a5584282b618cf3627251649769b1b5/xo-gd-3.2.2-5.tar.gz#sha256=21d5feee32f6f510ff6fbc3f02f0d02d1cadaab1b46bf14c5ee5884977273a0c, but installing version 3.2.2-4
Discarding https://files.pythonhosted.org/packages/17/66/b21ac5a35d14328f8369fc60fcb43a5584282b618cf3627251649769b1b5/xo-gd-3.2.2-5.tar.gz#sha256=21d5feee32f6f510ff6fbc3f02f0d02d1cadaab1b46bf14c5ee5884977273a0c (from https://pypi.org/simple/xo-gd/): Requested xo-gd==3.2.2.post5 from https://files.pythonhosted.org/packages/17/66/b21ac5a35d14328f8369fc60fcb43a5584282b618cf3627251649769b1b5/xo-gd-3.2.2-5.tar.gz#sha256=21d5feee32f6f510ff6fbc3f02f0d02d1cadaab1b46bf14c5ee5884977273a0c has inconsistent version: filename has '3.2.2.post5', but metadata has '3.2.2.post4'
ERROR: Could not find a version that satisfies the requirement xo-gd==3.2.2.post5 (from versions: 3.1.4.1, 3.1.4.2, 3.1.4.3, 3.1.4.11, 3.1.4.12, 3.1.4.35, 3.1.5, 3.1.5.1, 3.1.5.3, 3.1.5.4, 3.1.5.5, 3.1.5.6, 3.2.1, 3.2.1.1, 3.2.2.post5, 3.2.3, 3.2.3.2, 3.2.4, 3.2.5, 3.2.6, 3.2.7, 3.2.8)
ERROR: No matching distribution found for xo-gd==3.2.2.post5
```

So, I think we should discard that version if it has an invalid version string and look for other versions, as people can make a mistake anytime.

Resolving patch:
diff --git a/packages/micropip/src/micropip/_micropip.py b/packages/micropip/src/micropip/_micropip.py
--- a/packages/micropip/src/micropip/_micropip.py
+++ b/packages/micropip/src/micropip/_micropip.py
@@ -2,6 +2,7 @@
 import hashlib
 import importlib
 import json
+import warnings
 from asyncio import gather
 from dataclasses import dataclass, field
 from importlib.metadata import PackageNotFoundError
@@ -202,6 +203,13 @@ def find_wheel(metadata: dict[str, Any], req: Requirement) -> WheelInfo:
         reverse=True,
     )
     for ver in candidate_versions:
+        if str(ver) not in releases:
+            pkg_name = metadata.get("info", {}).get("name", "UNKNOWN")
+            warnings.warn(
+                f"The package '{pkg_name}' contains an invalid version: '{ver}'. This version will be skipped"
+            )
+            continue
+
         release = releases[str(ver)]
         for fileinfo in release:
             url = fileinfo["url"]


Test patch:
diff --git a/packages/micropip/test_micropip.py b/packages/micropip/test_micropip.py
--- a/packages/micropip/test_micropip.py
+++ b/packages/micropip/test_micropip.py
@@ -543,6 +543,27 @@ async def test_install_pre(
     assert micropip.list()[dummy].version == version_should_select
 
 
+@pytest.mark.asyncio
+@pytest.mark.filterwarnings("ignore::Warning")
+@pytest.mark.parametrize("version_invalid", ["1.2.3-1", "2.3.1-post1", "3.2.1-pre1"])
+async def test_install_version_invalid_pep440(
+    mock_fetch: mock_fetch_cls,
+    version_invalid: str,
+) -> None:
+    # Micropip should skip package versions which do not follow PEP 440.
+    #
+    #     [N!]N(.N)*[{a|b|rc}N][.postN][.devN]
+    #
+
+    dummy = "dummy"
+    version_stable = "1.0.0"
+
+    mock_fetch.add_pkg_version(dummy, version_stable)
+    mock_fetch.add_pkg_version(dummy, version_invalid)
+    await micropip.install(dummy)
+    assert micropip.list()[dummy].version == version_stable
+
+
 @pytest.mark.asyncio
 async def test_fetch_wheel_fail(monkeypatch, wheel_base):
     pytest.importorskip("packaging")