-
Notifications
You must be signed in to change notification settings - Fork 43
Support passing of index URLs to piplite (CLI, runtime configuration, and requirements files) #169
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 38 commits
939dc42
bed093c
5d45a40
83757c0
a9b539d
95b4700
32e0f51
8014816
48d1970
7207454
ffe3907
1c8e574
e7d3818
a5e9565
c04d84f
23852ca
b3f7808
d0fd31a
a9a62b3
2d7aed6
a1bcf66
a8cf844
d2192fe
4a7116c
443c206
e4e7a30
fec4e1b
98576dc
d694c00
3fc2381
d131c93
f9fdb3c
610c066
5dc6c2c
89d6339
eaca48b
253a3bc
0cf4a57
7ab3309
92566ff
cb0aa87
d9c96e8
f55adea
a3fff15
f15c661
24e1da8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,7 +13,7 @@ async def install( | |
| deps: bool = True, # --no-deps | ||
| credentials: str | None = None, # no CLI alias | ||
| pre: bool = False, # --pre | ||
| index_urls: list[str] | str | None = None, # no CLI alias | ||
| index_urls: list[str] | str | None = None, # -i and --index-url | ||
| *, | ||
| constraints: list[str] | None = None, # --constraints | ||
| reinstall: bool = False, # no CLI alias | ||
|
|
@@ -37,6 +37,11 @@ async def install( | |
|
|
||
| REQ_FILE_SPEC = r"^(?P<flag>-r|--requirements)\s*=?\s*(?P<path_ref>.+)$" | ||
|
|
||
| # Matches --index-url or -i directives (with optional = separator and optional quotes) | ||
| INDEX_URL_SPEC = ( | ||
| r'^(--index-url|-i)\s*=?\s*(?:"([^"]*)"|\047([^\047]*)\047|([^\s]*))\s*$' | ||
|
||
| ) | ||
|
|
||
| __all__ = ["get_transformed_code"] | ||
|
|
||
|
|
||
|
|
@@ -95,6 +100,13 @@ def _get_parser() -> ArgumentParser: | |
| action="store_true", | ||
| help="whether pre-release packages should be considered", | ||
| ) | ||
| parser.add_argument( | ||
| "--index-url", | ||
| "-i", | ||
| type=str, | ||
| default=None, | ||
| help="base URL of the package index to use for lookup", | ||
| ) | ||
| parser.add_argument( | ||
| "--force-reinstall", | ||
| action="store_true", | ||
|
|
@@ -138,8 +150,7 @@ async def get_action_kwargs(argv: list[str]) -> tuple[str | None, dict[str, Any] | |
| except (Exception, SystemExit): | ||
| return None, {} | ||
|
|
||
| kwargs = {} | ||
|
|
||
| kwargs: dict[str, Any] = {} | ||
| action = args.action | ||
|
|
||
| if action == "install": | ||
|
|
@@ -154,33 +165,54 @@ async def get_action_kwargs(argv: list[str]) -> tuple[str | None, dict[str, Any] | |
| if args.verbose: | ||
| kwargs["keep_going"] = True | ||
|
|
||
| if args.index_url: | ||
| kwargs["index_urls"] = args.index_url | ||
|
|
||
| if args.force_reinstall: | ||
| kwargs["reinstall"] = True | ||
|
|
||
| collected_index_urls: list[str] = [] | ||
| for req_file in args.requirements or []: | ||
| async for spec in _specs_from_requirements_file(Path(req_file)): | ||
| async for spec in _specs_from_requirements_file( | ||
| Path(req_file), collected_index_urls=collected_index_urls | ||
| ): | ||
| kwargs["requirements"] += [spec] | ||
|
|
||
| for const_file in args.constraints or []: | ||
| async for spec in _specs_from_requirements_file(Path(const_file)): | ||
| kwargs["constraints"] += [spec] | ||
| kwargs.setdefault("constraints", []).append(spec) | ||
|
|
||
| # Apply index URLs from requirements files only if --index-url was not | ||
| # already given on the command line. | ||
| if collected_index_urls and "index_urls" not in kwargs: | ||
| kwargs["index_urls"] = collected_index_urls | ||
|
|
||
| return action, kwargs | ||
|
|
||
|
|
||
| async def _specs_from_requirements_file(spec_path: Path) -> AsyncIterator[str]: | ||
| async def _specs_from_requirements_file( | ||
| spec_path: Path, | ||
| *, | ||
| collected_index_urls: list[str] | None = None, | ||
agriyakhetarpal marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ) -> AsyncIterator[str]: | ||
| """Extract package specs from a ``requirements.txt``-style file.""" | ||
| if not spec_path.exists(): | ||
| warn(f"piplite could not find requirements file {spec_path}") | ||
| return | ||
|
|
||
| for line_no, line in enumerate(spec_path.read_text(encoding="utf").splitlines()): | ||
| async for spec in _specs_from_requirements_line(spec_path, line_no + 1, line): | ||
| for line_no, line in enumerate(spec_path.read_text(encoding="utf-8").splitlines()): | ||
| async for spec in _specs_from_requirements_line( | ||
| spec_path, line_no + 1, line, collected_index_urls=collected_index_urls | ||
| ): | ||
| yield spec | ||
|
|
||
|
|
||
| async def _specs_from_requirements_line( | ||
| spec_path: Path, line_no: int, line: str | ||
| spec_path: Path, | ||
| line_no: int, | ||
| line: str, | ||
| *, | ||
| collected_index_urls: list[str] | None = None, | ||
| ) -> AsyncIterator[str]: | ||
| """Get package specs from a line of a ``requirements.txt``-style file. | ||
|
|
||
|
|
@@ -195,13 +227,23 @@ async def _specs_from_requirements_line( | |
| if file_match: | ||
| ref = file_match.groupdict()["path_ref"] | ||
| ref_path = Path(ref if ref.startswith("/") else spec_path.parent / ref) | ||
| async for sub_spec in _specs_from_requirements_file(ref_path): | ||
| async for sub_spec in _specs_from_requirements_file( | ||
| ref_path, collected_index_urls=collected_index_urls | ||
| ): | ||
| yield sub_spec | ||
| return | ||
|
|
||
| index_url_match = re.match(INDEX_URL_SPEC, raw) | ||
| if index_url_match: | ||
| # Extract the URL from whichever capture group matched (quoted or bare) | ||
| # and collect it so the caller can forward it to piplite.install. | ||
| url = next((g for g in index_url_match.groups()[1:] if g is not None), None) | ||
| if url and collected_index_urls is not None: | ||
| collected_index_urls.append(url) | ||
| return | ||
| elif raw.startswith("-"): | ||
| warn(f"{spec_path}:{line_no}: unrecognized spec: {raw}") | ||
| return | ||
| else: | ||
| spec = raw | ||
|
|
||
| if spec: | ||
| yield spec | ||
| if raw: | ||
| yield raw | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,18 +20,26 @@ | |
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| #: a list of Warehouse-like API endpoints or derived multi-package all.json | ||
| _PIPLITE_URLS: list[str] = [] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, dunno about just dropping these; might be a cas that needs to be deprecated, but supported through this major version.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, can restore both as aliases. We can deprecate them properly in a follow-up later! Edit: see a3fff15 |
||
|
|
||
| #: a cache of available packages | ||
| _PIPLITE_INDICES: dict[str, dict[str, Any]] = {} | ||
|
|
||
| #: don't fall back to pypi.org if a package is not found in _PIPLITE_URLS | ||
| _PIPLITE_DISABLE_PYPI = False | ||
|
|
||
| #: a well-known file name respected by the rest of the build chain | ||
| ALL_JSON = "/all.json" | ||
|
|
||
| #: This is the runtime configuration set from the JS side via worker.ts, which acts as | ||
| #: a single source of truth for all site-level piplite settings. | ||
| #: | ||
| #: Keys written by worker.ts on every kernel start: | ||
| #: piplite_urls – list of local all.json warehouse index URLs | ||
| #: disable_pypi – whether to block fallback to pypi.org | ||
| #: | ||
| #: Keys that are optionally written by worker.ts from pipliteInstallDefaultOptions: | ||
| #: index_urls – default index URL(s) that get forwarded to micropip.install | ||
| _PIPLITE_DEFAULT_INSTALL_ARGS: dict[str, Any] = { | ||
| "piplite_urls": [], # a list of Warehouse-like API endpoints or derived multi-package all.json | ||
| "disable_pypi": False, # don't fall back to pypi.org if package not found in _PIPLITE_URLS | ||
| } | ||
|
|
||
|
|
||
| class PiplitePyPIDisabled(ValueError): | ||
| """An error for when PyPI is disabled at the site level, but a download was | ||
|
|
@@ -91,7 +99,7 @@ async def _query_package( | |
| fetch_kwargs: dict[str, Any] | None = None, | ||
| ) -> ProjectInfo: | ||
| """Fetch the warehouse API metadata for a specific ``pkgname``.""" | ||
| for piplite_url in _PIPLITE_URLS: | ||
| for piplite_url in _PIPLITE_DEFAULT_INSTALL_ARGS.get("piplite_urls", []): | ||
| if not piplite_url.split("?")[0].split("#")[0].endswith(ALL_JSON): | ||
| logger.warning("Non-all.json piplite URL not supported %s", piplite_url) | ||
| continue | ||
|
|
@@ -105,7 +113,7 @@ async def _query_package( | |
| if pypi_json_from_index: | ||
| return pypi_json_from_index | ||
|
|
||
| if _PIPLITE_DISABLE_PYPI: | ||
| if _PIPLITE_DEFAULT_INSTALL_ARGS.get("disable_pypi", False): | ||
| raise PiplitePyPIDisabled( | ||
| f"{name} could not be installed: PyPI fallback is disabled" | ||
| ) | ||
|
|
@@ -130,7 +138,17 @@ async def _install( | |
| constraints: list[str] | None = None, | ||
| reinstall: bool = False, | ||
| ): | ||
| """Invoke micropip.install with a patch to get data from local indexes""" | ||
| """Invoke micropip.install with a patch to get data from local indexes. | ||
|
|
||
| If ``index_urls`` is not explicitly provided and a default has been | ||
| configured via ``_PIPLITE_DEFAULT_INSTALL_ARGS`` (e.g. from | ||
| ``pipliteInstallDefaultOptions`` in ``jupyter-lite.json``), the default | ||
| is used. | ||
| """ | ||
| effective_index_urls = index_urls | ||
| if effective_index_urls is None: | ||
| effective_index_urls = _PIPLITE_DEFAULT_INSTALL_ARGS.get("index_urls") | ||
|
|
||
| with patch("micropip.package_index.query_package", _query_package): | ||
| return await micropip.install( | ||
| requirements=requirements, | ||
|
|
@@ -139,7 +157,7 @@ async def _install( | |
| credentials=credentials, | ||
| pre=pre, | ||
| constraints=constraints, | ||
| index_urls=index_urls, | ||
| index_urls=effective_index_urls, | ||
| verbose=verbose, | ||
| reinstall=reinstall, | ||
| ) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.