Repository: pyodide/pyodide

Issue:
micropip.install of builtin packages should be case insensitive 
PyPI distribution names are case insensitive per [PEP-0426](https://www.python.org/dev/peps/pep-0426/#name). However micropip seems to be case sensitive when it comes to distributions in builtin_packages. This is problematic when installing a distribution that depends on one of the builtin_packages but refers to it with different casing, since micropip will instead try to install the dependency from PyPI.

To workaround you can set the name with the casing used, e.g.:
```python
import micropip
micropip.PACKAGE_MANAGER.builtin_packages['jinja2'] = micropip.PACKAGE_MANAGER.builtin_packages['Jinja2']
```

I came across this wanting to install the altair charting library in jupyterlite. While display is not working yet I was able to install altair using:
```python
import micropip
await micropip.install('Jinja2')
micropip.PACKAGE_MANAGER.builtin_packages['jinja2'] = micropip.PACKAGE_MANAGER.builtin_packages['Jinja2']
# Last jsonschema version before pyrsistent dependency added (native, not wheel)
await micropip.install("https://files.pythonhosted.org/packages/77/de/47e35a97b2b05c2fadbec67d44cfcdcd09b8086951b331d82de90d2912da/jsonschema-2.6.0-py2.py3-none-any.whl")
await micropip.install("altair")
import sys
sys.setrecursionlimit(255)
```


Discussion:

Resolving patch:
diff --git a/packages/micropip/micropip/micropip.py b/packages/micropip/micropip/micropip.py
--- a/packages/micropip/micropip/micropip.py
+++ b/packages/micropip/micropip/micropip.py
@@ -186,10 +186,12 @@ async def add_requirement(self, requirement: str, ctx, transaction):
         if requirement.endswith(".whl"):
             # custom download location
             name, wheel, version = _parse_wheel_url(requirement)
+            name = name.lower()
             transaction["wheels"].append((name, wheel, version))
             return
 
         req = Requirement(requirement)
+        req.name = req.name.lower()
 
         # If there's a Pyodide package that matches the version constraint, use
         # the Pyodide package instead of the one on PyPI
diff --git a/pyodide-build/pyodide_build/buildall.py b/pyodide-build/pyodide_build/buildall.py
--- a/pyodide-build/pyodide_build/buildall.py
+++ b/pyodide-build/pyodide_build/buildall.py
@@ -253,6 +253,7 @@ def build_packages(packages_dir: Path, outputdir: Path, args) -> None:
         "import_name_to_package_name": {},
         "shared_library": {},
         "versions": {},
+        "orig_case": {},
     }
 
     libraries = [pkg.name for pkg in pkg_map.values() if pkg.library]
@@ -261,13 +262,14 @@ def build_packages(packages_dir: Path, outputdir: Path, args) -> None:
         if pkg.library:
             continue
         if pkg.shared_library:
-            package_data["shared_library"][name] = True
-        package_data["dependencies"][name] = [
-            x for x in pkg.dependencies if x not in libraries
+            package_data["shared_library"][name.lower()] = True
+        package_data["dependencies"][name.lower()] = [
+            x.lower() for x in pkg.dependencies if x not in libraries
         ]
-        package_data["versions"][name] = pkg.version
+        package_data["versions"][name.lower()] = pkg.version
         for imp in pkg.meta.get("test", {}).get("imports", [name]):
-            package_data["import_name_to_package_name"][imp] = name
+            package_data["import_name_to_package_name"][imp] = name.lower()
+        package_data["orig_case"][name.lower()] = name
 
     # Hack for 0.17.0 release
     # TODO: FIXME!!


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
@@ -228,3 +228,18 @@ def test_install_different_version2(selenium_standalone_micropip):
         `);
         """
     )
+
+
+@pytest.mark.parametrize("jinja2", ["jinja2", "Jinja2"])
+def test_install_mixed_case2(selenium_standalone_micropip, jinja2):
+    selenium = selenium_standalone_micropip
+    selenium.run_js(
+        f"""
+        await pyodide.loadPackage("micropip");
+        await pyodide.runPythonAsync(`
+            import micropip
+            await micropip.install("{jinja2}")
+            import jinja2
+        `)
+        """
+    )
diff --git a/src/tests/test_package_loading.py b/src/tests/test_package_loading.py
--- a/src/tests/test_package_loading.py
+++ b/src/tests/test_package_loading.py
@@ -181,3 +181,16 @@ def test_js_load_package_from_python(selenium_standalone):
     selenium.run(f"import js ; js.pyodide.loadPackage(['{to_load}'])")
     assert f"Loading {to_load}" in selenium.logs
     assert selenium.run_js("return Object.keys(pyodide.loadedPackages)") == [to_load]
+
+
+@pytest.mark.parametrize("jinja2", ["jinja2", "Jinja2"])
+def test_load_package_mixed_case(selenium_standalone, jinja2):
+    selenium = selenium_standalone
+    selenium.run_js(
+        f"""
+        await pyodide.loadPackage("{jinja2}");
+        pyodide.runPython(`
+            import jinja2
+        `)
+        """
+    )