Skip to content

uv_manager

๐Ÿค– AI-Generated Content

This documentation was generated with AI assistance and is still being audited. Some, or potentially a lot, of this information may be inaccurate. Learn more.

flavor.packaging.python.uv_manager

UV tool manager for FlavorPack packaging.

This module provides UV (uv) command management with Foundation integration for Python package management operations that benefit from uv's performance.

IMPORTANT: UV commands are used for specific operations where performance is critical. For complex dependency resolution, use PyPaPipManager instead.

Classes

UVManager

UVManager(config: BaseConfig | None = None)

Bases: BaseToolManager

UV tool manager extending Foundation's BaseToolManager.

Handles UV-specific operations with proper platform support for fast Python package management operations.

CRITICAL: This class is for UV-specific operations where speed matters. For complex dependency resolution, use PyPaPipManager instead.

DO NOT REPLACE PyPA pip commands with uv pip - they have different capabilities!

Initialize the UV manager.

Parameters:

Name Type Description Default
config BaseConfig | None

Foundation configuration (can be None for default)

None
Source code in flavor/packaging/python/uv_manager.py
def __init__(self, config: BaseConfig | None = None) -> None:
    """
    Initialize the UV manager.

    Args:
        config: Foundation configuration (can be None for default)
    """
    if config is None:
        config = BaseConfig()

    super().__init__(config)

    # UV-specific configuration
    self.python_version = "3.11"  # Default Python version
    self.use_system_uv = True  # Prefer system UV if available
Functions
compile_requirements
compile_requirements(
    input_file: Path,
    output_file: Path,
    python_version: str | None = None,
) -> None

Compile requirements file using UV pip-compile.

Parameters:

Name Type Description Default
input_file Path

Input requirements.in file

required
output_file Path

Output requirements.txt file

required
python_version str | None

Target Python version

None
Source code in flavor/packaging/python/uv_manager.py
def compile_requirements(
    self, input_file: Path, output_file: Path, python_version: str | None = None
) -> None:
    """
    Compile requirements file using UV pip-compile.

    Args:
        input_file: Input requirements.in file
        output_file: Output requirements.txt file
        python_version: Target Python version
    """

    compile_cmd = self._get_uv_pip_compile_cmd(input_file, output_file, python_version)

    logger.debug("๐Ÿ’ป Compiling requirements with UV", command=" ".join(compile_cmd))
    run(compile_cmd, check=True, capture_output=True)
create_venv
create_venv(
    venv_path: Path, python_version: str | None = None
) -> None

Create virtual environment using UV.

Parameters:

Name Type Description Default
venv_path Path

Path where venv should be created

required
python_version str | None

Specific Python version constraint

None
Source code in flavor/packaging/python/uv_manager.py
def create_venv(self, venv_path: Path, python_version: str | None = None) -> None:
    """
    Create virtual environment using UV.

    Args:
        venv_path: Path where venv should be created
        python_version: Specific Python version constraint
    """

    # Use current Python for UV execution
    python_exe = Path(sys.executable)

    venv_cmd = self._get_uv_venv_cmd(python_exe, venv_path, python_version)

    logger.debug("๐Ÿ’ป Creating UV venv", command=" ".join(venv_cmd))
    run(venv_cmd, check=True, capture_output=True)
download_uv_binary
download_uv_binary(
    dest_dir: Path, python_exe: Path | None = None
) -> Path | None

Download UV binary for packaging (manylinux2014 on Linux).

CRITICAL: This downloads the UV binary itself, not packages using UV. UV cannot download itself - this uses PyPA pip or direct download.

Parameters:

Name Type Description Default
dest_dir Path

Directory to save UV binary to

required
python_exe Path | None

Python executable to use for pip (optional)

None

Returns:

Type Description
Path | None

Path to UV binary if successful, None otherwise

Retries

Up to 3 attempts with exponential backoff for network errors

Source code in flavor/packaging/python/uv_manager.py
@retry(
    ConnectionError,
    TimeoutError,
    OSError,
    max_attempts=3,
    base_delay=1.0,
    backoff=BackoffStrategy.EXPONENTIAL,
    jitter=True,
)
def download_uv_binary(self, dest_dir: Path, python_exe: Path | None = None) -> Path | None:
    """
    Download UV binary for packaging (manylinux2014 on Linux).

    CRITICAL: This downloads the UV binary itself, not packages using UV.
    UV cannot download itself - this uses PyPA pip or direct download.

    Args:
        dest_dir: Directory to save UV binary to
        python_exe: Python executable to use for pip (optional)

    Returns:
        Path to UV binary if successful, None otherwise

    Retries:
        Up to 3 attempts with exponential backoff for network errors
    """
    import sys
    import tempfile
    import zipfile

    # Import PyPaPipManager here to avoid circular dependency
    from flavor.packaging.python.pypapip_manager import PyPaPipManager

    pypapip = PyPaPipManager()
    python_exe = python_exe or Path(sys.executable)

    with tempfile.TemporaryDirectory() as temp_dir:
        temp_path = Path(temp_dir)

        # Determine platform for manylinux2014 compatibility
        arch = get_arch_name()
        uv_platform_tag = None
        if get_os_name() == "linux":
            if arch == "amd64":
                uv_platform_tag = "manylinux2014_x86_64"
            elif arch == "arm64":
                uv_platform_tag = "manylinux2014_aarch64"

        # Download UV wheel using PyPA pip
        download_cmd = pypapip._get_pypapip_download_cmd(
            python_exe=python_exe,
            dest_dir=temp_path,
            packages=["uv"],
            binary_only=True,
            platform_tag=uv_platform_tag,
        )

        try:
            logger.debug("Downloading UV wheel", cmd=" ".join(download_cmd))
            run(download_cmd, check=True, capture_output=True)

            # Find the downloaded wheel
            uv_wheel = None
            for file in temp_path.glob("uv-*.whl"):
                uv_wheel = file
                logger.debug(f"Found UV wheel: {uv_wheel.name}")
                break

            if not uv_wheel:
                logger.error("UV wheel not found after download")
                return None

            # Extract UV binary from wheel
            with zipfile.ZipFile(uv_wheel, "r") as wheel_zip:
                for name in wheel_zip.namelist():
                    if name.endswith("/uv") or name == "uv":
                        uv_path = dest_dir / "uv"

                        logger.debug(f"Extracting UV binary from {name}")
                        with (
                            wheel_zip.open(name) as src,
                            uv_path.open("wb") as dst,
                        ):
                            dst.write(src.read())

                        # Make executable (Unix-like systems only)
                        if sys.platform != "win32":
                            uv_path.chmod(0o755)

                        return uv_path

            logger.error("UV binary not found in wheel")
            return None

        except Exception as e:
            logger.error(f"Failed to download UV binary: {e}")
            return None
download_wheels_network
download_wheels_network(
    requirements_file: Path, dest_dir: Path
) -> bool

Download wheels via UV's HTTP client using install+cache collection.

Note: uv pip download does not exist as a UV subcommand. Strategy: install into an isolated --target dir with a private --cache-dir, then collect the .whl files that UV wrote into its wheel cache during the install.

This works where pip fails because UV uses its own Rust HTTP client (reqwest) which succeeds on Windows GHA where Python urllib3 gets [Errno 11001] getaddrinfo failed.

Parameters:

Name Type Description Default
requirements_file Path

Path to requirements.txt file (no hashes)

required
dest_dir Path

Directory to collect wheel files into

required

Returns:

Type Description
bool

True if wheels were collected, False otherwise

Source code in flavor/packaging/python/uv_manager.py
def download_wheels_network(self, requirements_file: Path, dest_dir: Path) -> bool:
    """
    Download wheels via UV's HTTP client using install+cache collection.

    Note: `uv pip download` does not exist as a UV subcommand.
    Strategy: install into an isolated --target dir with a private
    --cache-dir, then collect the .whl files that UV wrote into its
    wheel cache during the install.

    This works where pip fails because UV uses its own Rust HTTP client
    (reqwest) which succeeds on Windows GHA where Python urllib3 gets
    [Errno 11001] getaddrinfo failed.

    Args:
        requirements_file: Path to requirements.txt file (no hashes)
        dest_dir: Directory to collect wheel files into

    Returns:
        True if wheels were collected, False otherwise
    """
    import shutil as _shutil
    import tempfile

    uv_exe = self.get_uv_executable()

    with tempfile.TemporaryDirectory() as tmp_dir:
        tmp_path = Path(tmp_dir)
        uv_cache = tmp_path / "uv_cache"
        install_target = tmp_path / "target"
        uv_cache.mkdir()
        install_target.mkdir()

        # Install with isolated cache so we can find exactly which wheels
        # UV downloaded.  UV caches .whl files under cache/wheels/**/*.whl.
        cmd = [
            uv_exe.as_posix(),
            "pip",
            "install",
            "--cache-dir",
            uv_cache.as_posix(),
            "--target",
            install_target.as_posix(),
            "-r",
            requirements_file.as_posix(),
        ]
        logger.warning(f"๐Ÿ’ป UV pip install (network fallback): {' '.join(cmd)}")
        result = run(cmd, check=False, capture_output=True)
        if result.returncode != 0:
            logger.warning(
                f"UV pip install failed (rc={result.returncode}): {result.stderr.strip()[:400]}"
            )
            return False

        # Collect .whl files from UV's isolated wheel cache
        whl_files = list(uv_cache.glob("**/*.whl"))
        if not whl_files:
            logger.warning("UV pip install succeeded but no .whl files found in UV cache")
            return False

        for whl in whl_files:
            _shutil.copy2(str(whl), str(dest_dir / whl.name))
        logger.info(f"โœ… Collected {len(whl_files)} wheels from UV pip install cache")
        return True
download_wheels_offline
download_wheels_offline(
    requirements_file: Path, dest_dir: Path
) -> bool

Attempt to download wheels from a pre-warmed local wheel cache (no network).

Checks the FLAVOR_WHEEL_CACHE environment variable for a directory containing pre-downloaded .whl files (populated by the CI pre-warm step or equivalent). Uses pip download --no-index --find-links to copy matching wheels from that local directory into dest_dir without any network access.

Note: uv pip download does not exist as a UV subcommand. This function uses standard pip with --no-index to ensure zero network activity.

Parameters:

Name Type Description Default
requirements_file Path

Path to requirements.txt file

required
dest_dir Path

Directory to download wheels to

required

Returns:

Type Description
bool

True if all wheels were found in local cache, False otherwise

Source code in flavor/packaging/python/uv_manager.py
def download_wheels_offline(self, requirements_file: Path, dest_dir: Path) -> bool:
    """
    Attempt to download wheels from a pre-warmed local wheel cache (no network).

    Checks the FLAVOR_WHEEL_CACHE environment variable for a directory containing
    pre-downloaded .whl files (populated by the CI pre-warm step or equivalent).
    Uses `pip download --no-index --find-links` to copy matching wheels from that
    local directory into dest_dir without any network access.

    Note: `uv pip download` does not exist as a UV subcommand. This function
    uses standard pip with --no-index to ensure zero network activity.

    Args:
        requirements_file: Path to requirements.txt file
        dest_dir: Directory to download wheels to

    Returns:
        True if all wheels were found in local cache, False otherwise
    """
    import os
    import sys

    wheel_cache_dir = os.environ.get(ENV_WHEEL_CACHE)
    if not wheel_cache_dir:
        logger.warning(f"๐Ÿ’ป {ENV_WHEEL_CACHE} not set, skipping offline wheel strategy")
        return False

    cache_path = Path(wheel_cache_dir)
    whl_count = len(list(cache_path.glob("*.whl"))) if cache_path.exists() else 0
    if not cache_path.exists() or whl_count == 0:
        logger.warning(f"๐Ÿ’ป {ENV_WHEEL_CACHE} dir empty or missing: {cache_path} ({whl_count} wheels)")
        return False

    logger.warning(f"๐Ÿ’ป Offline wheel copy from {ENV_WHEEL_CACHE}: {cache_path} ({whl_count} wheels)")
    python_exe = Path(sys.executable)
    cmd = [
        str(python_exe),
        "-m",
        "pip",
        "download",
        "--no-index",
        "--find-links",
        str(cache_path),
        "--dest",
        str(dest_dir),
        "-r",
        str(requirements_file),
        "--quiet",
    ]
    result = run(cmd, check=False, capture_output=True, env=_windows_system_env() or None)
    if result.returncode == 0:
        logger.warning(f"โœ… Copied wheels from {ENV_WHEEL_CACHE} (offline)")
        return True
    logger.warning(
        f"{ENV_WHEEL_CACHE} copy failed (rc={result.returncode}): {result.stderr.strip()[:400]}"
    )
    return False
export_requirements
export_requirements(
    project_dir: Path,
    output_file: Path,
    no_dev: bool = True,
) -> None

Export pinned requirements from an existing uv.lock file (no network needed).

Uses uv export --frozen which reads the committed lock file without making any PyPI/DNS calls. Preferred over compile_requirements when a lock file is already present.

uv export excludes the root project itself from output by default, so only transitive runtime dependencies are listed.

Parameters:

Name Type Description Default
project_dir Path

Project directory containing uv.lock

required
output_file Path

Output requirements.txt file

required
no_dev bool

Whether to exclude dev dependencies (default True)

True
Source code in flavor/packaging/python/uv_manager.py
def export_requirements(self, project_dir: Path, output_file: Path, no_dev: bool = True) -> None:
    """
    Export pinned requirements from an existing uv.lock file (no network needed).

    Uses `uv export --frozen` which reads the committed lock file without
    making any PyPI/DNS calls. Preferred over compile_requirements when a
    lock file is already present.

    uv export excludes the root project itself from output by default, so
    only transitive runtime dependencies are listed.

    Args:
        project_dir: Project directory containing uv.lock
        output_file: Output requirements.txt file
        no_dev: Whether to exclude dev dependencies (default True)
    """
    uv_exe = self.get_uv_executable()
    cmd = [uv_exe.as_posix(), "export", "--frozen", "--no-hashes", "--output-file", output_file.as_posix()]
    if no_dev:
        cmd.append("--no-dev")
    # --no-hashes: hash annotations in requirements.txt cause uv pip download to
    # enter strict hash-checking mode, which fails when packages aren't pre-cached
    # at the exact pinned version (e.g. Windows GHA: uv tool install caches
    # anyio==4.12.1 but uv.lock pins anyio==4.11.0). Omitting hashes lets all
    # three download methods (pip, uv offline, uv network) work with plain
    # version-pinned requirements without hash verification overhead.
    #
    # Note: --no-project was added in uv 0.6+; older versions (0.10.x in GHA)
    # don't have it. Instead we post-process the output to strip editable/local
    # entries (file:// lines) which represent the root project itself.

    logger.debug("๐Ÿ’ป Exporting requirements from uv.lock (offline)", command=" ".join(cmd))
    run(cmd, check=True, capture_output=True, cwd=project_dir)

    # Strip editable installs and local file:// requirements โ€” these are the
    # root project itself, which is built from source and must not be downloaded.
    self._strip_local_requirements(output_file)
find_system_uv
find_system_uv() -> Path | None

Find system-installed UV executable.

Returns:

Type Description
Path | None

Path to UV executable if found, None otherwise

Source code in flavor/packaging/python/uv_manager.py
def find_system_uv(self) -> Path | None:
    """
    Find system-installed UV executable.

    Returns:
        Path to UV executable if found, None otherwise
    """
    import shutil
    import sys

    system_uv = shutil.which("uv")
    if system_uv:
        logger.debug(f"Found system UV: {system_uv}")
        return Path(system_uv)

    # Also look next to the current Python executable and in Scripts/bin.
    # Inside a Flavor PSP workenv the workenv's Scripts/bin directory may
    # not be in PATH, but uv is installed as a sibling of python.exe or
    # in the Scripts/bin subdirectory (depending on Python layout).
    python_dir = Path(sys.executable).parent
    search_dirs = [python_dir, python_dir / "Scripts", python_dir / "bin"]
    for search_dir in search_dirs:
        for name in ("uv.exe", "uv"):
            candidate = search_dir / name
            if candidate.exists():
                logger.debug(f"Found UV next to Python executable: {candidate}")
                return candidate

    logger.debug("No system UV found")
    return None
get_available_versions
get_available_versions() -> list[str]

Get list of available UV versions from GitHub releases.

Returns:

Type Description
list[str]

List of version strings available for download

Source code in flavor/packaging/python/uv_manager.py
def get_available_versions(self) -> list[str]:
    """
    Get list of available UV versions from GitHub releases.

    Returns:
        List of version strings available for download
    """
    # For now, return a static list of known good versions
    # In a full implementation, this would query GitHub API
    return ["0.6.6", "0.6.5", "0.6.4", "0.5.29"]
get_metadata
get_metadata(version: str) -> ToolMetadata

Get metadata for a specific UV version.

Parameters:

Name Type Description Default
version str

UV version string

required

Returns:

Type Description
ToolMetadata

ToolMetadata with UV download information

Raises:

Type Description
ToolNotFoundError

If version metadata cannot be retrieved

Source code in flavor/packaging/python/uv_manager.py
def get_metadata(self, version: str) -> ToolMetadata:
    """
    Get metadata for a specific UV version.

    Args:
        version: UV version string

    Returns:
        ToolMetadata with UV download information

    Raises:
        ToolNotFoundError: If version metadata cannot be retrieved
    """
    platform_info = self.get_platform_info()
    platform = platform_info["platform"]
    arch = platform_info["arch"]

    # UV release URL pattern
    base_url = "https://github.com/astral-sh/uv/releases/download"

    # Platform-specific naming
    if platform == "darwin":
        if arch == "amd64" or arch == "arm64":
            platform_suffix = "apple-darwin"
        else:
            raise ToolNotFoundError(f"Unsupported Darwin architecture: {arch}")
    elif platform == "linux":
        if arch == "amd64" or arch == "arm64":
            platform_suffix = "unknown-linux-gnu"
        else:
            raise ToolNotFoundError(f"Unsupported Linux architecture: {arch}")
    elif platform == "windows":
        if arch == "amd64" or arch == "arm64":
            platform_suffix = "pc-windows-msvc"
        else:
            raise ToolNotFoundError(f"Unsupported Windows architecture: {arch}")
    else:
        raise ToolNotFoundError(f"Unsupported platform: {platform}")

    # Build download URL
    arch_mapping = {"amd64": "x86_64", "arm64": "aarch64"}
    uv_arch = arch_mapping.get(arch, arch)

    filename = f"uv-{uv_arch}-{platform_suffix}.tar.gz"
    download_url = f"{base_url}/{version}/{filename}"

    return ToolMetadata(
        name=self.tool_name,
        version=version,
        platform=platform,
        arch=arch,
        download_url=download_url,
        executable_name=self.executable_name,
    )
get_uv_executable
get_uv_executable(version: str | None = None) -> Path

Get path to UV executable, installing if necessary.

Parameters:

Name Type Description Default
version str | None

Specific version to use (None for system UV)

None

Returns:

Type Description
Path

Path to UV executable

Raises:

Type Description
ToolNotFoundError

If UV cannot be found or installed

Source code in flavor/packaging/python/uv_manager.py
def get_uv_executable(self, version: str | None = None) -> Path:
    """
    Get path to UV executable, installing if necessary.

    Args:
        version: Specific version to use (None for system UV)

    Returns:
        Path to UV executable

    Raises:
        ToolNotFoundError: If UV cannot be found or installed
    """
    # Try system UV first if enabled and no version specified
    if self.use_system_uv and version is None and (system_uv := self.find_system_uv()):
        return system_uv

    # Install specific version if requested
    if version:
        return asyncio.run(self.install(version))

    # Install latest as fallback
    logger.info("Installing UV as system UV not available")
    return asyncio.run(self.install("latest"))
install_packages_fast
install_packages_fast(
    venv_python: Path,
    packages: list[str],
    requirements_file: Path | None = None,
) -> None

Install packages using UV pip for speed.

Parameters:

Name Type Description Default
venv_python Path

Python executable in target venv

required
packages list[str]

List of package names to install

required
requirements_file Path | None

Optional requirements file

None
Source code in flavor/packaging/python/uv_manager.py
def install_packages_fast(
    self,
    venv_python: Path,
    packages: list[str],
    requirements_file: Path | None = None,
) -> None:
    """
    Install packages using UV pip for speed.

    Args:
        venv_python: Python executable in target venv
        packages: List of package names to install
        requirements_file: Optional requirements file
    """
    if not packages and not requirements_file:
        logger.debug("No packages to install")
        return

    logger.info("๐ŸŒ๐Ÿ“ฅ Installing packages with UV (fast mode)")

    install_cmd = self._get_uv_pip_install_cmd(venv_python, packages, requirements_file)

    logger.debug("๐Ÿ’ป Installing packages with UV", command=" ".join(install_cmd))
    run(install_cmd, check=True, capture_output=True)