2
2
3
3
As of the upstream:
4
4
5
- https://github.com/pyodide/micropip/blob/v0.2 .0/micropip/_micropip .py#L468
5
+ https://github.com/pyodide/micropip/blob/0.10 .0/micropip/package_manager .py#L43-L55
6
6
7
7
.. code:
8
8
9
9
async def install(
10
10
self,
11
- requirements: str | list[str], # -r and [packages ]
11
+ requirements: str | list[str], # -r and [PACKAGES ]
12
12
keep_going: bool = False, # --verbose
13
13
deps: bool = True, # --no-deps
14
14
credentials: str | None = None, # no CLI alias
15
15
pre: bool = False, # --pre
16
16
index_urls: list[str] | str | None = None, # no CLI alias
17
17
*,
18
- verbose: bool | int | None = None,
19
- ):
20
- ```
18
+ constraints: list[str] | None = None, # --constraints
19
+ reinstall: bool = False, # no CLI alias
20
+ verbose: bool | int | None = None, # --verbose
21
+ ) -> None:
21
22
22
23
As this is _not_ really a CLI, it doesn't bother with accurate return codes, and
23
24
failures should not block execution.
24
25
"""
25
26
27
+ from __future__ import annotations
28
+
26
29
import re
27
30
import sys
28
- import typing
31
+ from typing import Any , TYPE_CHECKING
29
32
from argparse import ArgumentParser
30
33
from pathlib import Path
31
34
32
- REQ_FILE_PREFIX = r"^(-r|--requirements)\s*=?\s*(.*)\s*"
35
+ if TYPE_CHECKING :
36
+ from collections .abc import AsyncIterator
37
+
38
+ REQ_FILE_SPEC = r"^(?P<flag>-r|--requirements)\s*=?\s*(?P<path_ref>.+)$"
33
39
34
40
__all__ = ["get_transformed_code" ]
35
41
36
42
37
- def warn (msg ):
43
+ def warn (msg : str ) -> None :
44
+ """Print a warning to stderr."""
38
45
print (msg , file = sys .stderr , flush = True )
39
46
40
47
@@ -44,7 +51,7 @@ def _get_parser() -> ArgumentParser:
44
51
"piplite" ,
45
52
exit_on_error = False ,
46
53
allow_abbrev = False ,
47
- description = "a pip-like wrapper for `piplite` and `micropip`" ,
54
+ description = "a `` pip`` -like wrapper for `` piplite`` and `` micropip` `" ,
48
55
)
49
56
parser .add_argument (
50
57
"--verbose" ,
@@ -64,7 +71,19 @@ def _get_parser() -> ArgumentParser:
64
71
"--requirements" ,
65
72
"-r" ,
66
73
nargs = "*" ,
67
- help = "paths to requirements files" ,
74
+ help = (
75
+ "path to a requirements file; each line should be a PEP 508 spec"
76
+ " or -r to a relative path"
77
+ ),
78
+ )
79
+ parser .add_argument (
80
+ "--constraints" ,
81
+ "-c" ,
82
+ nargs = "*" ,
83
+ help = (
84
+ "path to a constraints file; each line should be a PEP 508 spec"
85
+ " or -r to a relative path"
86
+ ),
68
87
)
69
88
parser .add_argument (
70
89
"--no-deps" ,
@@ -76,6 +95,11 @@ def _get_parser() -> ArgumentParser:
76
95
action = "store_true" ,
77
96
help = "whether pre-release packages should be considered" ,
78
97
)
98
+ parser .add_argument (
99
+ "--force-reinstall" ,
100
+ action = "store_true" ,
101
+ help = "reinstall all packages even if they are already installed" ,
102
+ )
79
103
parser .add_argument (
80
104
"packages" ,
81
105
nargs = "*" ,
@@ -87,21 +111,25 @@ def _get_parser() -> ArgumentParser:
87
111
return parser
88
112
89
113
90
- async def get_transformed_code (argv : list [str ]) -> typing . Optional [ str ] :
114
+ async def get_transformed_code (argv : list [str ]) -> str | None :
91
115
"""Return a string of code for use in in-kernel execution."""
92
116
action , kwargs = await get_action_kwargs (argv )
117
+ code_str : str = "\n "
93
118
94
119
if action == "help" :
95
120
pass
121
+
96
122
if action == "install" :
97
123
if kwargs ["requirements" ]:
98
- return f"""await __import__("piplite").install(**{ kwargs } )\n """
124
+ code_str = f"""await __import__("piplite").install(**{ kwargs } )\n """
99
125
else :
100
126
warn ("piplite needs at least one package to install" )
101
127
128
+ return code_str
102
129
103
- async def get_action_kwargs (argv : list [str ]) -> tuple [typing .Optional [str ], dict ]:
104
- """Get the arguments to `piplite` subcommands from CLI-like tokens."""
130
+
131
+ async def get_action_kwargs (argv : list [str ]) -> tuple [str | None , dict [str , Any ]]:
132
+ """Get the arguments to ``piplite`` subcommands from CLI-like tokens."""
105
133
106
134
parser = _get_parser ()
107
135
@@ -126,51 +154,54 @@ async def get_action_kwargs(argv: list[str]) -> tuple[typing.Optional[str], dict
126
154
if args .verbose :
127
155
kwargs ["keep_going" ] = True
128
156
157
+ if args .reinstall :
158
+ kwargs ["reinstall" ] = True
159
+
129
160
for req_file in args .requirements or []:
130
- kwargs ["requirements" ] += await _packages_from_requirements_file (
131
- Path (req_file )
132
- )
161
+ async for spec in _specs_from_requirements_file (Path (req_file )):
162
+ kwargs ["requirements" ] += [spec ]
133
163
134
- return action , kwargs
164
+ for const_file in args .constraints or []:
165
+ async for spec in _specs_from_requirements_file (Path (const_file )):
166
+ kwargs ["constraints" ] += [spec ]
135
167
168
+ return action , kwargs
136
169
137
- async def _packages_from_requirements_file (req_path : Path ) -> list [str ]:
138
- """Extract (potentially nested) package requirements from a requirements file."""
139
- if not req_path .exists ():
140
- warn (f"piplite could not find requirements file { req_path } " )
141
- return []
142
170
143
- requirements = []
171
+ async def _specs_from_requirements_file (spec_path : Path ) -> AsyncIterator [str ]:
172
+ """Extract package specs from a ``requirements.txt``-style file."""
173
+ if not spec_path .exists ():
174
+ warn (f"piplite could not find requirements file { spec_path } " )
175
+ return
144
176
145
- for line_no , line in enumerate (req_path .read_text (encoding = "utf" ).splitlines ()):
146
- requirements += await _packages_from_requirements_line (
147
- req_path , line_no + 1 , line
148
- )
177
+ for line_no , line in enumerate (spec_path .read_text (encoding = "utf" ).splitlines ()):
178
+ async for spec in _specs_from_requirements_line (spec_path , line_no + 1 , line ):
179
+ yield spec
149
180
150
- return requirements
151
181
182
+ async def _specs_from_requirements_line (
183
+ spec_path : Path , line_no : int , line : str
184
+ ) -> AsyncIterator [str ]:
185
+ """Get package specs from a line of a ``requirements.txt``-style file.
152
186
153
- async def _packages_from_requirements_line (
154
- req_path : Path , line_no : int , line : str
155
- ) -> list [str ]:
156
- """Extract (potentially nested) package requirements from line of a
157
- requirements file.
187
+ ``micropip`` has a sufficient pep508 implementation to handle most cases.
158
188
159
- `micropip` has a sufficient pep508 implementation to handle most cases
189
+ References to other, local files with ``-r`` are supported.
160
190
"""
161
- req = line .strip ().split ("#" )[0 ].strip ()
162
- # is it another requirement file?
163
- req_file_match = re .match (REQ_FILE_PREFIX , req )
164
- if req_file_match :
165
- if req_file_match [2 ].startswith ("/" ):
166
- sub_req = Path (req )
167
- else :
168
- sub_req = req_path .parent / req_file_match [2 ]
169
- return await _packages_from_requirements_file (sub_req )
170
-
171
- if req .startswith ("-" ):
172
- warn (f"{ req_path } :{ line_no } : unrecognized requirement: { req } " )
173
- req = None
174
- if not req :
175
- return []
176
- return [req ]
191
+ raw = line .strip ().split ("#" )[0 ].strip ()
192
+ # is it another spec file?
193
+ file_match = re .match (REQ_FILE_SPEC , raw )
194
+
195
+ if file_match :
196
+ ref = file_match .groupdict ()["path_ref" ]
197
+ ref_path = Path (ref if ref .startswith ("/" ) else spec_path .parent / ref )
198
+ async for sub_spec in _specs_from_requirements_file (ref_path ):
199
+ yield sub_spec
200
+ elif raw .startswith ("-" ):
201
+ warn (f"{ spec_path } :{ line_no } : unrecognized spec: { raw } " )
202
+ return
203
+ else :
204
+ spec = raw
205
+
206
+ if spec :
207
+ yield spec
0 commit comments