Skip to content

Commit 0273f37

Browse files
Replace relative URLs to absolute URL when freezing the lockfile (#241)
* Use 0.28.0 for testing * Replace relative paths to absolute path when freezing the lockfile * Add test * Pass base url * Fix order * Fix test * fix test * syntax * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * changelog --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 08dbf7e commit 0273f37

File tree

8 files changed

+116
-5
lines changed

8 files changed

+116
-5
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## Unreleased
88

9+
## [0.10.1] - 2024/07/05
10+
11+
### Fixed
12+
13+
- `micropip.freeze()` now updates the URLs inside the lockfile to absolute URLs.
14+
This behavior is consistent with the `lockfileURL` behavior change in Pyodide 0.28.0.
15+
[#241](https://github.com/pyodide/micropip/pull/241)
16+
917
## [0.10.0] - 2024/07/02
1018

1119
### Added

micropip/_compat/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
LOCKFILE_PACKAGES = compatibility_layer.lockfile_packages
2323

24+
lockfile_base_url = compatibility_layer.lockfile_base_url
25+
2426
fetch_bytes = compatibility_layer.fetch_bytes
2527

2628
fetch_string_and_headers = compatibility_layer.fetch_string_and_headers
@@ -40,4 +42,5 @@
4042
"loadedPackages",
4143
"loadPackage",
4244
"to_js",
45+
"lockfile_base_url",
4346
]

micropip/_compat/_compat_in_pyodide.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
try:
1010
import pyodide_js
11-
from pyodide_js import loadedPackages, loadPackage
11+
from pyodide_js import loadedPackages, loadPackage, lockfileBaseUrl
1212
from pyodide_js._api import ( # type: ignore[import]
1313
install,
1414
loadBinaryFile,
@@ -58,3 +58,5 @@ async def fetch_string_and_headers(
5858
lockfile_info = LOCKFILE_INFO
5959

6060
lockfile_packages = LOCKFILE_PACKAGES
61+
62+
lockfile_base_url = lockfileBaseUrl

micropip/_compat/_compat_not_in_pyodide.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,5 @@ def to_js(
8484
lockfile_info = {}
8585

8686
lockfile_packages = {}
87+
88+
lockfile_base_url = None

micropip/_compat/compatibility_layer.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ def to_py():
1919

2020
lockfile_packages: dict[str, dict[str, Any]]
2121

22+
lockfile_base_url: str | None = None
23+
2224
@staticmethod
2325
@abstractmethod
2426
async def fetch_bytes(url: str, kwargs: dict[str, str]) -> bytes:

micropip/freeze.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,31 @@
44
from copy import deepcopy
55
from importlib.metadata import Distribution
66
from typing import Any
7+
from urllib.parse import urljoin
78

89
from ._utils import get_dist_info
910
from ._vendored.packaging.src.packaging.requirements import Requirement
1011
from ._vendored.packaging.src.packaging.utils import canonicalize_name
1112

1213

1314
def freeze_lockfile(
14-
lockfile_packages: dict[str, dict[str, Any]], lockfile_info: dict[str, str]
15+
lockfile_packages: dict[str, dict[str, Any]],
16+
lockfile_info: dict[str, str],
17+
lockfile_base_url: str | None = None,
1518
) -> str:
16-
return json.dumps(freeze_data(lockfile_packages, lockfile_info))
19+
return json.dumps(freeze_data(lockfile_packages, lockfile_info, lockfile_base_url))
1720

1821

1922
def freeze_data(
20-
lockfile_packages: dict[str, dict[str, Any]], lockfile_info: dict[str, str]
23+
lockfile_packages: dict[str, dict[str, Any]],
24+
lockfile_info: dict[str, str],
25+
lockfile_base_url: str | None = None,
2126
) -> dict[str, Any]:
2227
packages = deepcopy(lockfile_packages)
2328
packages.update(load_pip_packages(lockfile_packages))
29+
if lockfile_base_url is not None:
30+
# Override the base URL for the packages
31+
override_base_url(packages, lockfile_base_url)
2432

2533
# Sort
2634
packages = dict(sorted(packages.items()))
@@ -30,6 +38,19 @@ def freeze_data(
3038
}
3139

3240

41+
def override_base_url(
42+
lockfile_packages: dict[str, dict[str, Any]],
43+
lockfile_base_url: str,
44+
):
45+
"""
46+
Updates the relative URLs in the lockfile packages to absolute URLs by appending the base URL.
47+
This assures that when the generated lockfile is deployed separately from the packages,
48+
the URLs will still point to the correct location.
49+
"""
50+
for pkg in lockfile_packages.values():
51+
pkg["file_name"] = urljoin(lockfile_base_url, pkg["file_name"])
52+
53+
3354
def load_pip_packages(
3455
lockfile_packages: dict[str, dict[str, Any]],
3556
) -> Iterator[tuple[str, dict[str, Any]]]:

micropip/package_manager.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,9 @@ def freeze(self) -> str:
320320
``lockFileURL`` of :js:func:`~globalThis.loadPyodide`.
321321
"""
322322
return freeze_lockfile(
323-
self.compat_layer.lockfile_packages, self.compat_layer.lockfile_info
323+
self.compat_layer.lockfile_packages,
324+
self.compat_layer.lockfile_info,
325+
self.compat_layer.lockfile_base_url,
324326
)
325327

326328
def add_mock_package(

tests/test_freeze.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import pytest
22
from conftest import mock_fetch_cls
3+
from pytest_pyodide import run_in_pyodide
34

45

56
@pytest.mark.asyncio
@@ -102,3 +103,73 @@ def test_freeze_lockfile_compat(
102103
assert package.install_dir == "site"
103104
assert not package.unvendored_tests
104105
assert package.version == wheel.version
106+
107+
108+
def test_override_base_url():
109+
from micropip.freeze import override_base_url
110+
111+
lockfile_packages = {
112+
"pkg1": {"file_name": "pkg1-1.0.0-py3-none-any.whl"},
113+
"pkg2": {"file_name": "pkg2-2.0.0-py3-none-any.whl"},
114+
"pkg3": {"file_name": "https://other.com/pkg3-3.0.0-py3-none-any.whl"},
115+
}
116+
base_url = "https://example.com/packages/"
117+
118+
override_base_url(lockfile_packages, base_url)
119+
120+
assert (
121+
lockfile_packages["pkg1"]["file_name"]
122+
== "https://example.com/packages/pkg1-1.0.0-py3-none-any.whl"
123+
)
124+
assert (
125+
lockfile_packages["pkg2"]["file_name"]
126+
== "https://example.com/packages/pkg2-2.0.0-py3-none-any.whl"
127+
)
128+
assert (
129+
lockfile_packages["pkg3"]["file_name"]
130+
== "https://other.com/pkg3-3.0.0-py3-none-any.whl"
131+
)
132+
133+
134+
def test_url_after_freeze_pyodide(selenium_standalone_micropip):
135+
136+
@run_in_pyodide
137+
def _run(selenium, prefix):
138+
import json
139+
140+
from pyodide_js import lockfileBaseUrl
141+
from pyodide_js._api import lockfile_packages
142+
143+
import micropip
144+
145+
new_lockfile_str = micropip.freeze()
146+
new_lockfile_packages = json.loads(new_lockfile_str)["packages"]
147+
148+
orig_lockfile_packages = lockfile_packages.to_py()
149+
150+
for orig_pkg_name, orig_pkg in orig_lockfile_packages.items():
151+
assert orig_pkg_name in new_lockfile_packages
152+
153+
new_pkg = new_lockfile_packages[orig_pkg_name]
154+
155+
assert new_pkg["name"] == orig_pkg["name"]
156+
assert new_pkg["version"] == orig_pkg["version"]
157+
assert new_pkg["sha256"] == orig_pkg["sha256"]
158+
assert new_pkg["imports"] == orig_pkg["imports"]
159+
assert new_pkg["depends"] == orig_pkg["depends"]
160+
assert new_pkg["install_dir"] == orig_pkg["install_dir"]
161+
assert new_pkg["unvendored_tests"] == orig_pkg["unvendored_tests"]
162+
163+
# original lockfile will have relative URLs
164+
# TODO: this might change later if packages are served from PyPI
165+
assert not orig_pkg["file_name"].startswith(prefix)
166+
167+
# new lockfile should have absolute URLs
168+
assert new_pkg["file_name"].startswith(prefix)
169+
assert new_pkg["file_name"].startswith(lockfileBaseUrl)
170+
171+
assert orig_pkg["file_name"] in new_pkg["file_name"]
172+
173+
selenium = selenium_standalone_micropip
174+
prefix = ("/",) if selenium.browser == "node" else ("http://", "https://")
175+
_run(selenium, prefix)

0 commit comments

Comments
 (0)