Repository navigation
feature: install FrameworkProcessor requirements with uv for faster job startup #5967
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: master
Are you sure you want to change the base?
Changes from all commits
50fee51
562a7dc
d44614c
0028818
dc2bb8b
21ed773
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 |
|---|---|---|
|
|
@@ -22,7 +22,7 @@ | |
|
|
||
| - ``configure_pip()`` — returns an authenticated pip index URL (or ``None``). | ||
| Use when you need to build your own pip command with custom flags. | ||
| - ``install_requirements(path)`` — configures pip and runs ``pip install -r``. | ||
| - ``install_requirements(path)`` — configures pip and installs with ``uv``. | ||
| Use when you just want requirements installed. | ||
|
|
||
| :: | ||
|
|
@@ -38,6 +38,7 @@ | |
| import logging | ||
| import os | ||
| import re | ||
| import shutil | ||
| import subprocess | ||
| import sys | ||
|
|
||
|
|
@@ -111,23 +112,44 @@ def _login_awscli(region, account, domain, repo): | |
| ) | ||
|
|
||
|
|
||
| def configure_pip(auth_method=CodeArtifactAuthMethod.AUTO): | ||
| def _set_pip_index(python_executable, index_url): | ||
| """Persist an authenticated index URL into pip config. | ||
|
|
||
| Mirrors what ``aws codeartifact login`` does for the CLI path: writes | ||
| ``global.index-url`` so the container's pip — including the ``uv`` bootstrap in | ||
| :func:`_ensure_uv` — pulls from CodeArtifact. This matters in isolated | ||
| environments with no public PyPI access. (``uv`` ignores ``pip.conf``, so the | ||
| index is still surfaced to uv separately via ``UV_INDEX_URL``.) | ||
| """ | ||
| subprocess.check_call( | ||
| [python_executable, "-m", "pip", "config", "set", "global.index-url", index_url] | ||
| ) | ||
|
|
||
|
|
||
| def configure_pip(auth_method=CodeArtifactAuthMethod.AUTO, python_executable=None): | ||
| """Configure pip for CodeArtifact if ``CA_REPOSITORY_ARN`` is set. | ||
|
|
||
| Both auth paths persist the authenticated index into pip config, so any later | ||
| pip invocation — including the ``uv`` bootstrap in :func:`_ensure_uv` — uses | ||
| CodeArtifact even in isolated environments without public PyPI access. | ||
|
|
||
| Args: | ||
| auth_method: Authentication mechanism to use. Defaults to ``CodeArtifactAuthMethod.AUTO`` | ||
| (try boto3 first, fall back to AWS CLI). | ||
| python_executable: Python executable whose pip config is written on the | ||
| boto3 path. Defaults to ``sys.executable``. | ||
|
|
||
| Returns: | ||
| An authenticated pip index URL (str) when boto3 succeeds, | ||
| ``None`` when AWS CLI was used (pip config modified globally), | ||
| An authenticated pip index URL (str) when boto3 succeeds (also written to | ||
| pip config), ``None`` when AWS CLI was used (pip config modified globally), | ||
| or ``None`` when ``CA_REPOSITORY_ARN`` is not set. | ||
|
|
||
| Raises: | ||
| SystemExit: When ``CA_REPOSITORY_ARN`` is set but the requested | ||
| auth method is not available. | ||
| ValueError: When the ARN format is invalid. | ||
| """ | ||
| python_executable = python_executable or sys.executable | ||
| arn = os.environ.get(CA_REPOSITORY_ARN_ENV) | ||
| if not arn: | ||
| return None | ||
|
|
@@ -144,12 +166,15 @@ def configure_pip(auth_method=CodeArtifactAuthMethod.AUTO): | |
|
|
||
| if auth_method in (CodeArtifactAuthMethod.BOTO3, CodeArtifactAuthMethod.AUTO): | ||
| try: | ||
| return _get_index_boto3(region, account, domain, repo) | ||
| index = _get_index_boto3(region, account, domain, repo) | ||
| except ImportError: | ||
| if auth_method == CodeArtifactAuthMethod.BOTO3: | ||
| logger.error("boto3 is not available") | ||
| sys.exit(1) | ||
| logger.info("boto3 not available, trying AWS CLI fallback") | ||
| else: | ||
| _set_pip_index(python_executable, index) | ||
| return index | ||
|
|
||
| if auth_method in (CodeArtifactAuthMethod.AWS_CLI, CodeArtifactAuthMethod.AUTO): | ||
| try: | ||
|
|
@@ -170,25 +195,113 @@ def configure_pip(auth_method=CodeArtifactAuthMethod.AUTO): | |
| sys.exit(1) | ||
|
|
||
|
|
||
| def _ensure_uv(python_executable): | ||
| """Return a path to a ``uv`` executable, bootstrapping it with pip if absent. | ||
|
|
||
| Some containers don't ship ``uv``. When it's missing we install it with the | ||
| container's pip (which has already been pointed at CodeArtifact, if configured). | ||
|
|
||
| Args: | ||
| python_executable: Python executable whose pip is used to bootstrap ``uv``. | ||
|
|
||
| Returns: | ||
| Path to the ``uv`` executable (str). | ||
| """ | ||
| uv = shutil.which("uv") | ||
| if uv: | ||
| return uv | ||
| logger.info("uv not found; bootstrapping it with pip") | ||
| subprocess.check_call([python_executable, "-m", "pip", "install", "uv"]) | ||
| return shutil.which("uv") or "uv" | ||
|
|
||
|
|
||
| def _pip_config_get(python_executable, key): | ||
| """Read a pip config value (e.g. set by ``aws codeartifact login``). | ||
|
|
||
| ``uv`` ignores ``pip.conf``, so any index configured globally on pip must be | ||
| read back and propagated to ``uv`` explicitly. Returns ``None`` when the key | ||
| is unset or pip can't report it. | ||
|
|
||
| Args: | ||
| python_executable: Python executable whose pip config is queried. | ||
| key: pip config key, e.g. ``global.index-url``. | ||
| """ | ||
| try: | ||
| out = subprocess.check_output( | ||
| [python_executable, "-m", "pip", "config", "get", key], | ||
| stderr=subprocess.DEVNULL, | ||
| ) | ||
| except (subprocess.CalledProcessError, FileNotFoundError): | ||
| return None | ||
| value = out.decode().strip() | ||
| return value or None | ||
|
|
||
|
|
||
| def _build_uv_index_env(python_executable, index): | ||
| """Build the ``UV_*`` environment for index configuration. | ||
|
|
||
| Bridges both CodeArtifact auth paths and any pre-existing pip config into the | ||
| environment variables ``uv`` understands: | ||
|
|
||
| - ``index`` (from boto3 auth) becomes ``UV_INDEX_URL``. | ||
| - When boto3 didn't yield an index (CLI login wrote ``pip.conf``, or the user | ||
| pre-configured pip), read ``global.index-url`` back from pip config. | ||
| - ``global.extra-index-url`` / ``global.trusted-host`` are always propagated | ||
| when present, to stay general across private-index setups. | ||
|
|
||
| Args: | ||
| python_executable: Python executable whose pip config is consulted. | ||
| index: Authenticated index URL from :func:`configure_pip`, or ``None``. | ||
|
|
||
| Returns: | ||
| A dict of ``UV_*`` overrides to merge into the subprocess environment. | ||
| """ | ||
| env = {} | ||
|
|
||
| index_url = index or _pip_config_get(python_executable, "global.index-url") | ||
| if index_url: | ||
| env["UV_INDEX_URL"] = index_url | ||
|
|
||
| extra_index_url = _pip_config_get(python_executable, "global.extra-index-url") | ||
| if extra_index_url: | ||
| env["UV_EXTRA_INDEX_URL"] = extra_index_url | ||
|
|
||
| trusted_host = _pip_config_get(python_executable, "global.trusted-host") | ||
| if trusted_host: | ||
| env["UV_INSECURE_HOST"] = trusted_host | ||
|
|
||
| return env | ||
|
|
||
|
|
||
| def install_requirements( | ||
| requirements_file="requirements.txt", | ||
| python_executable=None, | ||
| auth_method=CodeArtifactAuthMethod.AUTO, | ||
| ): | ||
| """Install pip requirements with optional CodeArtifact authentication. | ||
| """Install requirements with ``uv`` and optional CodeArtifact authentication. | ||
|
|
||
| Configures CodeArtifact (if ``CA_REPOSITORY_ARN`` is set), bootstraps ``uv`` | ||
|
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. 🟡 Low: |
||
| when the container doesn't ship it, propagates any private-index configuration | ||
| into ``uv``'s environment, and installs the requirements into the system | ||
| interpreter. | ||
|
|
||
| Args: | ||
| requirements_file: Path to the requirements file. | ||
| python_executable: Python executable to use for pip. Defaults to ``sys.executable``. | ||
| python_executable: Python executable used to bootstrap ``uv`` and read pip | ||
| config. Defaults to ``sys.executable``. | ||
| auth_method: Authentication mechanism for CodeArtifact. Defaults to ``CodeArtifactAuthMethod.AUTO``. | ||
| """ | ||
| python_executable = python_executable or sys.executable | ||
| pip_cmd = [python_executable, "-m", "pip", "install", "-r", requirements_file] | ||
| index = configure_pip(auth_method=auth_method) | ||
| if index: | ||
| pip_cmd.extend(["-i", index]) | ||
| logger.info("Running: %s", " ".join(pip_cmd)) | ||
| subprocess.check_call(pip_cmd) | ||
|
|
||
| index = configure_pip(auth_method=auth_method, python_executable=python_executable) | ||
|
|
||
| uv = _ensure_uv(python_executable) | ||
| env = os.environ.copy() | ||
| env.update(_build_uv_index_env(python_executable, index)) | ||
|
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. 🟠 Medium: |
||
|
|
||
| install_cmd = [uv, "pip", "install", "--system", "-r", requirements_file] | ||
| logger.info("Running: %s", " ".join(install_cmd)) | ||
| subprocess.check_call(install_cmd, env=env) | ||
|
|
||
|
|
||
| def main(): | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Low: When
uvis bootstrapped viapip install uvinto a non-defaultpython_executable, theuvconsole script is placed in that interpreter'sbin, which may not be on PATH.shutil.which("uv")can then returnNoneand the code falls back to the bare string"uv", which will fail to execute if it isn't on PATH. For the custom-executable case, deriving the uv path from the interpreter's bin dir (or invokingpython_executable -m uv) would be more robust than the"uv"fallback.