Skip to content

Commit

Permalink
Change package query order when custom index URLs are given (#83)
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanking13 authored Sep 19, 2023
1 parent 2663afe commit b607b06
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 14 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

## [0.5.0] - 2023/09/19

### Changed

- When custom index URLs are set by `micropip.set_index_urls` or by `micropip.install(index_urls=...)`,
micropip will now query packages from the custom index first,
and then from pyodide lockfile.
[#83](https://github.com/pyodide/micropip/pull/83)

- Made micropip.freeze correctly list dependencies of manually installed packages.
[#79](https://github.com/pyodide/micropip/pull/79)

Expand Down
2 changes: 1 addition & 1 deletion micropip/_commands/index_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ def set_index_urls(urls: list[str] | str) -> None:
if isinstance(urls, str):
urls = [urls]

package_index.INDEX_URLS = urls
package_index.INDEX_URLS = urls[:]
4 changes: 4 additions & 0 deletions micropip/_commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from packaging.markers import default_environment

from .. import package_index
from .._compat import loadPackage, to_js
from ..constants import FAQ_URLS
from ..logging import setup_logging
Expand Down Expand Up @@ -125,6 +126,9 @@ async def install(

wheel_base = Path(getsitepackages()[0])

if index_urls is None:
index_urls = package_index.INDEX_URLS[:]

transaction = Transaction(
ctx=ctx,
ctx_extras=[],
Expand Down
52 changes: 40 additions & 12 deletions micropip/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,13 @@ class Transaction:

verbose: bool | int = False

def __post_init__(self):
# If index_urls is None, pyodide-lock.json have to be searched first.
# TODO: when PyPI starts to support hosting WASM wheels, this might be deleted.
self.search_pyodide_lock_first = (
self.index_urls == package_index.DEFAULT_INDEX_URLS
)

async def gather_requirements(
self,
requirements: list[str],
Expand Down Expand Up @@ -285,36 +292,57 @@ def eval_marker(e: dict[str, str]) -> bool:
logger.info(f"Requirement already satisfied: {req} ({ver})")
return

# If there's a Pyodide package that matches the version constraint, use
# the Pyodide package instead of the one on PyPI
try:
if self.search_pyodide_lock_first:
if self._add_requirement_from_pyodide_lock(req):
return

await self._add_requirement_from_package_index(req)
else:
try:
await self._add_requirement_from_package_index(req)
except ValueError:
# If the requirement is not found in package index,
# we still have a chance to find it from pyodide lockfile.
if not self._add_requirement_from_pyodide_lock(req):
raise
except ValueError:
self.failed.append(req)
if not self.keep_going:
raise

def _add_requirement_from_pyodide_lock(self, req: Requirement) -> bool:
"""
Find requirement from pyodide-lock.json. If the requirement is found,
add it to the package list and return True. Otherwise, return False.
"""
if req.name in REPODATA_PACKAGES and req.specifier.contains(
REPODATA_PACKAGES[req.name]["version"], prereleases=True
):
version = REPODATA_PACKAGES[req.name]["version"]
self.pyodide_packages.append(
PackageMetadata(name=req.name, version=str(version), source="pyodide")
)
return
return True

return False

async def _add_requirement_from_package_index(self, req: Requirement):
"""
Find requirement from package index. If the requirement is found,
add it to the package list and return True. Otherwise, return False.
"""
metadata = await package_index.query_package(
req.name, self.fetch_kwargs, index_urls=self.index_urls
)

try:
wheel = find_wheel(metadata, req)
except ValueError:
self.failed.append(req)
if not self.keep_going:
raise
else:
return
wheel = find_wheel(metadata, req)

# Maybe while we were downloading pypi_json some other branch
# installed the wheel?
satisfied, ver = self.check_version_satisfied(req)
if satisfied:
logger.info(f"Requirement already satisfied: {req} ({ver})")
return

await self.add_wheel(wheel, req.extras, specifier=str(req.specifier))

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ test = [
"pytest-httpserver",
"pytest-pyodide",
"pytest-cov",
"build<1.0.0",
"build==0.7.0",
]


Expand Down
65 changes: 65 additions & 0 deletions tests/test_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,3 +292,68 @@ def test_last_version_and_best_tag_from_pypi(
wheel = find_wheel(metadata, requirement)

assert str(wheel.version) == new_version


def test_search_pyodide_lock_first():
from micropip import package_index
from micropip.transaction import Transaction

t = Transaction(
ctx={},
ctx_extras=[],
keep_going=True,
deps=True,
pre=True,
fetch_kwargs={},
verbose=False,
index_urls=package_index.DEFAULT_INDEX_URLS,
)
assert t.search_pyodide_lock_first is True

t = Transaction(
ctx={},
ctx_extras=[],
keep_going=True,
deps=True,
pre=True,
fetch_kwargs={},
verbose=False,
index_urls=["https://my.custom.index.com"],
)
assert t.search_pyodide_lock_first is False


@pytest.mark.asyncio
async def test_index_url_priority(
mock_importlib, wheel_base, monkeypatch, mock_package_index_simple_json_api
):
# Test that if the index_urls are provided, package should be searched in
# the index_urls first before searching in Pyodide lock file.
from micropip.transaction import Transaction

# add_wheel is called only when the package is found in the index_urls
add_wheel_called = None

async def mock_add_wheel(self, wheel, extras, *, specifier=""):
nonlocal add_wheel_called
add_wheel_called = wheel

monkeypatch.setattr(Transaction, "add_wheel", mock_add_wheel)

mock_index_url = mock_package_index_simple_json_api(pkgs=["black"])

t = Transaction(
keep_going=True,
deps=False,
pre=False,
ctx={},
ctx_extras=[],
fetch_kwargs={},
index_urls=mock_index_url,
)

await t.add_requirement("black")
assert add_wheel_called is not None
assert add_wheel_called.name == "black"
# 23.7.0 is the latest version of black in the mock index
assert str(add_wheel_called.version) == "23.7.0"

0 comments on commit b607b06

Please sign in to comment.