Skip to content

Commit e50f58c

Browse files
committed
apply constraints in transaction
1 parent 64bbbe8 commit e50f58c

File tree

2 files changed

+48
-7
lines changed

2 files changed

+48
-7
lines changed

docs/project/usage.md

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,17 +94,31 @@ await micropip.install("pkg", deps=False)
9494

9595
### Constraining indirect dependencies
9696

97-
Dependency resolution can be further customized with optional `constraints`: these
98-
provide the versions (or URLs of wheels)
97+
Dependency resolution can be further customized with optional `constraints`: as
98+
described in the [`pip`](https://pip.pypa.io/en/stable/user_guide/#constraints-files)
99+
documentation, these must provide a name and version (or URL), and may not request
100+
`[extras]`.
99101

100102
```python
101-
await micropip.install("pkg", constraints=["other-pkg ==0.1.1"])
103+
await micropip.install(
104+
"pkg",
105+
constraints=[
106+
"other-pkg ==0.1.1",
107+
"some-other-pkg <2",
108+
"yet-another-pkg@https://example.com/yet_another_pkg-0.1.2-py3-none-any.whl",
109+
# invalid examples # why?
110+
# yet_another_pkg-0.1.2-py3-none-any.whl # missing name
111+
# something-completely[different] ==0.1.1 # extras
112+
# package-with-no-version # missing version or URL
113+
]
114+
)
102115
```
103116

104-
Default `constraints` may be provided to be used by all subsequent calls to
105-
`micropip.install`:
117+
`micropip.set_constraints` replaces any default constraints for all subsequent
118+
calls to `micropip.install` that don't specify constraints:
106119

107120
```python
108121
micropip.set_constraints = ["other-pkg ==0.1.1"]
109-
await micropip.install("pkg")
122+
await micropip.install("pkg") # uses defaults
123+
await micropip.install("another-pkg", constraints=[]) # ignores defaults
110124
```

micropip/transaction.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,29 @@ class Transaction:
3838
verbose: bool | int | None = None
3939
constraints: list[str] | None = None
4040

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

48+
self.constrained_reqs: dict[str, Requirement] = {}
49+
50+
for constraint in self.constraints or []:
51+
con = Requirement(constraint)
52+
if not con.name:
53+
logger.debug("Transaction: discarding nameless constraint: %s", con)
54+
continue
55+
if con.extras:
56+
logger.debug("Transaction: discarding [extras] constraint: %s", con)
57+
continue
58+
if not con.url or len(con.specifier):
59+
logger.debug("Transaction: discarding versionless constraint: %s", con)
60+
continue
61+
con.name = canonicalize_name(con.name)
62+
self.constrained_reqs[con.name] = con
63+
4864
async def gather_requirements(
4965
self,
5066
requirements: list[str] | list[Requirement],
@@ -88,6 +104,14 @@ def check_version_satisfied(self, req: Requirement) -> tuple[bool, str]:
88104
f"Requested '{req}', " f"but {req.name}=={ver} is already installed"
89105
)
90106

107+
def constrain_requirement(self, req: Requirement) -> Requirement:
108+
"""Provide a constrained requirement, if available, or the original."""
109+
constrained_req = self.constrained_reqs.get(canonicalize_name(req.name))
110+
if constrained_req:
111+
logger.debug("Transaction: %s constrained to %s", req, constrained_req)
112+
return constrained_req
113+
return req
114+
91115
async def add_requirement_inner(
92116
self,
93117
req: Requirement,
@@ -100,6 +124,8 @@ async def add_requirement_inner(
100124
for e in req.extras:
101125
self.ctx_extras.append({"extra": e})
102126

127+
req = self.constrain_requirement(req)
128+
103129
if self.pre:
104130
req.specifier.prereleases = True
105131

@@ -136,6 +162,7 @@ def eval_marker(e: dict[str, str]) -> bool:
136162
eval_marker(e) for e in self.ctx_extras
137163
):
138164
return
165+
139166
# Is some version of this package is already installed?
140167
req.name = canonicalize_name(req.name)
141168

0 commit comments

Comments
 (0)