Source code for asv.plugins.uv

# Licensed under a 3-clause BSD style license - see LICENSE.rst
import os
import re

from .. import environment, util
from ..console import log

[docs] WIN = os.name == "nt"
[docs] class Uv(environment.Environment): """ Manage an environment using 'uv venv'. """
[docs] tool_name = "uv"
def __init__(self, conf, python, requirements, tagged_env_vars): """ Parameters ---------- conf : Config instance python : str Version of Python. Must be of the form "MAJOR.MINOR". executable : str Path to Python executable. requirements : dict Dictionary mapping a PyPI package name to a version identifier string. """
[docs] self._python = python
[docs] self._requirements = requirements
super().__init__(conf, python, requirements, tagged_env_vars) try: self._uv_path = util.which("uv") except OSError: self._uv_path = None if not self._uv_path: raise environment.EnvironmentUnavailable("uv command not found") @property
[docs] def name(self): """ Get a name to uniquely identify this environment. """ python = self._python if self._python.startswith('pypy'): # get_env_name adds py-prefix python = python[2:] return environment.get_env_name( self.tool_name, python, self._requirements, self._tagged_env_vars )
@classmethod
[docs] def matches(self, python): if not (re.match(r'^[0-9].*$', python) or re.match(r'^pypy[0-9.]*$', python)): # The python name should be a version number, or pypy+number return False if not self._uv_path: log.warning( "asv requires the 'uv' command to be available when using the 'uv' environment_type." ) return False return True
[docs] def _setup(self): """ Setup the environment on disk using 'uv venv'. Then, all of the requirements are installed into it using `pip install`. """ env = dict(os.environ) env.update(self.build_env_vars) # Adjust the environment variables to use the virtualenv self._venv_env_vars = {"VIRTUAL_ENV": self._path} if "PATH" in env: self._venv_env_vars["PATH"] = f"{self._path}/bin:{env['PATH']}" else: self._venv_env_vars["PATH"] = f"{self._path}/bin" env.update(self._venv_env_vars) log.info(f"Creating virtualenv for {self.name}") util.check_call( [ 'uv', 'venv', f'--python={self._python}', '--no-project', '--seed', self._path, ], env=env, ) log.info(f"Installing requirements for {self.name}") self._install_requirements()
[docs] def _install_requirements(self): pip_args = ['install', '-v', 'wheel', 'pip>=8'] env = dict(os.environ) env.update(self.build_env_vars) self._run_pip(pip_args, env=env) pip_args = [] for key, val in {**self._requirements, **self._base_requirements}.items(): if key.startswith("pip+"): pip_args.append(f"{key[4:]} {val}") else: pip_args.append(f"{key} {val}") for declaration in pip_args: parsed_declaration = util.ParsedPipDeclaration(declaration) pip_call = util.construct_pip_call(self._run_pip, parsed_declaration) pip_call()
[docs] def _run_pip(self, args, **kwargs): # Run pip via python -m pip, so that it works on Windows when # upgrading pip itself, and avoids shebang length limit on Linux return self.run_executable('python', ['-m', 'pip'] + list(args), **kwargs)
[docs] def run(self, args, **kwargs): joined_args = ' '.join(args) log.debug(f"Running '{joined_args}' in {self.name}") return self.run_executable('python', args, **kwargs)