Skip to content

Commit f2e9b51

Browse files
Support extras in custom urls [integration] (#257)
* Support deps in packages * Add test * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * typo * pr number * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Ignore unused import * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix test * lint * Fix url * [integration] * Fix param * Set release date [integration] * Fix integration test [integration] --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 227285e commit f2e9b51

File tree

3 files changed

+64
-17
lines changed

3 files changed

+64
-17
lines changed

CHANGELOG.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,22 @@ 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
9+
## [0.11.0] - 2025/10/18
10+
11+
### Added
12+
13+
- `micropip.install` now supports extras in custom download locations through the `pkg[extras] @ https://example.com/pkg-1.0.0-py3-none-any.whl` syntax.
14+
[#257](https://github.com/pyodide/micropip/pull/257)
15+
16+
## [0.10.1] - 2025/07/05
1017

1118
### Fixed
1219

1320
- `micropip.freeze()` now updates the URLs inside the lockfile to absolute URLs.
1421
This behavior is consistent with the `lockfileURL` behavior change in Pyodide 0.28.0.
1522
[#241](https://github.com/pyodide/micropip/pull/241)
1623

17-
## [0.10.0] - 2024/07/02
24+
## [0.10.0] - 2025/07/02
1825

1926
### Added
2027

@@ -35,7 +42,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3542
when one index URL fails to find a package, and will fallback to the next index URL.
3643
[#225](https://github.com/pyodide/micropip/pull/225)
3744

38-
## [0.9.0] - 2024/02/01
45+
## [0.9.0] - 2025/02/01
3946

4047
### Fixed
4148

micropip/transaction.py

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -79,22 +79,28 @@ async def add_requirement(self, req: str | Requirement) -> None:
7979

8080
try:
8181
as_req = constrain_requirement(Requirement(req), self.constrained_reqs)
82-
except InvalidRequirement:
83-
as_req = None
8482

85-
if as_req:
86-
if as_req.name and len(as_req.specifier):
87-
return await self.add_requirement_inner(as_req)
88-
if as_req.url:
89-
req = as_req.url
83+
if as_req.name.endswith(".whl"):
84+
# This happens when the requirement is passed as a relative URL:
85+
# For instance, micropip.install("pkg-1.0.0-py3-none-any.whl")
86+
# packaging cannot distinguish this is a relative URL or a very weird wheel name ... sigh ...
87+
# But we want to treat it as a custom download location.
88+
return await self.add_requirement_from_url(req)
89+
90+
return await self.add_requirement_inner(as_req)
91+
except InvalidRequirement:
92+
if not urlparse(req).path.endswith(".whl"):
93+
raise
9094

91-
if urlparse(req).path.endswith(".whl"):
92-
# custom download location
93-
wheel = WheelInfo.from_url(req)
94-
check_compatible(wheel.filename)
95-
return await self.add_wheel(wheel, extras=set(), specifier="")
95+
# custom download location, for instance, micropip.install("https://example.com/pkg-1.0.0-py3-none-any.whl")
96+
return await self.add_requirement_from_url(req)
9697

97-
return await self.add_requirement_inner(Requirement(req))
98+
async def add_requirement_from_url(
99+
self, req: str, extras: set[str] | None = None
100+
) -> None:
101+
wheel = WheelInfo.from_url(req)
102+
check_compatible(wheel.filename)
103+
return await self.add_wheel(wheel, extras=extras or set(), specifier="")
98104

99105
def check_version_satisfied(
100106
self, req: Requirement, *, allow_reinstall: bool = False
@@ -138,7 +144,7 @@ def check_version_satisfied(
138144
"or micropip.uninstall(...) to uninstall the package first."
139145
)
140146

141-
async def add_requirement_inner(
147+
async def add_requirement_inner( # noqa: C901
142148
self,
143149
req: Requirement,
144150
) -> None:
@@ -201,6 +207,11 @@ def eval_marker(e: dict[str, str]) -> bool:
201207
logger.info("Requirement already satisfied: %s (%s)", req, ver)
202208
return
203209

210+
if req.url:
211+
# custom download location, for instance, micropip.install("pkg @ https://example.com/pkg-1.0.0-py3-none-any.whl")
212+
# in this case, we don't need to search the index_urls or pyodide lock file.
213+
return await self.add_requirement_from_url(req.url, extras=req.extras)
214+
204215
try:
205216
if self.search_pyodide_lock_first:
206217
if await self._add_requirement_from_pyodide_lock(req):

tests/integration/test_integration.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,32 @@ async def _run(selenium):
183183
assert (dist_dir / "PYODIDE_SHA256").exists()
184184

185185
_run(selenium_standalone_micropip)
186+
187+
188+
@integration_test_only
189+
def test_install_url_based_wheel(selenium_standalone_micropip, pytestconfig):
190+
# Dependencies of URL based wheels are fetched from PyPI.
191+
# It is tricky to test this without accessing PyPI, hence integration test
192+
@run_in_pyodide
193+
async def _run(selenium, url):
194+
import micropip
195+
196+
await micropip.install(f"typer @ {url}")
197+
198+
try:
199+
import rich
200+
except ModuleNotFoundError:
201+
pass
202+
else:
203+
raise Exception("Should raise!")
204+
205+
micropip.uninstall("typer")
206+
207+
await micropip.install(f"typer[all] @ {url}")
208+
209+
import rich # noqa: F401
210+
import typer # noqa: F401
211+
212+
# typer 0.10.0 has "[all]" dependency that comes with colorama, shellingham, and rich
213+
typer_0_10_0_url = "https://files.pythonhosted.org/packages/d9/07/8100c125307a26f03c305764f22cd995ae1878071ddf1df3588add73b53c/typer-0.10.0-py3-none-any.whl"
214+
_run(selenium_standalone_micropip, typer_0_10_0_url)

0 commit comments

Comments
 (0)