Skip to content
Draft
Show file tree
Hide file tree
Changes from 22 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,20 @@ jobs:
with:
python-version: ${{ matrix.python }}
nogil: ${{ matrix.build == 'free-threading' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "0.9.5"
enable-cache: true
cache-suffix: ${{ runner.os }}-${{ matrix.python }}-${{ matrix.build != '' && matrix.build || 'default' }}
- name: Install
shell: bash
# pyperformance must be installed:
# pyperformance/tests/test_compare.py imports it
run: |
python -m pip install --upgrade pip setuptools
python -m pip install -e .
PYTHON_BIN="$(python -c 'import sys; print(sys.executable)')"
uv pip install --python "$PYTHON_BIN" --upgrade pip setuptools
uv pip install --python "$PYTHON_BIN" -e .
- name: Display Python version
run: |
python -VV
Expand Down
31 changes: 22 additions & 9 deletions dev.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# A script for running pyperformance out of the repo in dev-mode.

import os.path
import shutil
import sys

REPO_ROOT = os.path.dirname(os.path.abspath(__file__))
Expand Down Expand Up @@ -29,7 +30,7 @@ def ensure_venv_ready(venvroot=None, kind="dev", venvsdir=VENVS):
readyfile = os.path.join(sys.prefix, "READY")
isready = os.path.exists(readyfile)
else:
import venv
from pyperformance._uv import run_uv

if not venvroot:
venvroot = resolve_venv_root(kind, venvsdir)
Expand All @@ -42,7 +43,15 @@ def ensure_venv_ready(venvroot=None, kind="dev", venvsdir=VENVS):
print(f"creating venv at {relroot}...")
else:
print(f"venv {relroot} not ready, re-creating...")
venv.create(venvroot, with_pip=True, clear=True)
shutil.rmtree(venvroot)
ec, _, _ = run_uv(
"venv",
"--python",
sys.executable,
venvroot,
)
if ec != 0:
sys.exit("ERROR: venv creation failed")
else:
assert os.path.exists(os.path.join(venvroot, "pyvenv.cfg"))
# Return the venv's Python executable.
Expand All @@ -52,18 +61,22 @@ def ensure_venv_ready(venvroot=None, kind="dev", venvsdir=VENVS):

# Now make sure the venv has pyperformance installed.
if not isready:
import subprocess

relroot = os.path.relpath(venvroot)
print(f"venv {relroot} not ready, installing dependencies...")
proc = subprocess.run(
[python, "-m", "pip", "install", "--upgrade", "--editable", REPO_ROOT],
ec, _, _ = run_uv(
"pip",
"install",
"--python",
python,
"--upgrade",
"--editable",
f"{REPO_ROOT}[dev]",
)
if proc.returncode != 0:
sys.exit("ERROR: install failed")
if ec != 0:
sys.exit("ERROR: uv pip install failed")
with open(readyfile, "w"):
pass
print("...venv {relroot} ready!")
print(f"...venv {relroot} ready!")

return venvroot, python

Expand Down
46 changes: 46 additions & 0 deletions pyperformance/_uv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""Utilities for invoking the ``uv`` CLI."""

import os
import os.path
import shutil

from . import _pip, _utils


def run_uv(*args, env=None, capture=None, verbose=True):
uv = shutil.which("uv")
if not uv:
if verbose:
print(
"ERROR: uv executable not found. Install uv from https://astral.sh/uv."
)
return 127, None, None
return _utils.run_cmd([uv, *args], env=env, capture=capture, verbose=verbose)


def install_requirements(reqs, *extra, python, upgrade=True, env=None, verbose=True):
args = [
"pip",
"install",
"--python",
python,
]
if upgrade:
args.append("--upgrade")

for spec in (reqs, *extra):
if spec is None:
continue
if isinstance(spec, os.PathLike):
spec = os.fspath(spec)
if os.path.isfile(spec) and spec.endswith(".txt"):
args.extend(["-r", spec])
else:
args.append(spec)

ec, stdout, stderr = run_uv(*args, env=env, verbose=verbose)
if ec == 127:
return _pip.install_requirements(
reqs, *extra, python=python, env=env, upgrade=upgrade
)
return ec, stdout, stderr
26 changes: 19 additions & 7 deletions pyperformance/_venv.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import sys
import types

from . import _pip, _pythoninfo, _utils
from . import _pip, _pythoninfo, _utils, _uv


class VenvCreationFailedError(Exception):
Expand Down Expand Up @@ -111,11 +111,21 @@ def create_venv(
):
"""Create a new venv at the given root, optionally installing pip."""
already_existed = os.path.exists(root)
if withpip:
args = ["-m", "venv", root]

if isinstance(python, str) or python is None:
target_python = python or sys.executable
else:
args = ["-m", "venv", "--without-pip", root]
ec, _, _ = _utils.run_python(*args, python=python, env=env)
try:
target_python = python.sys.executable
except AttributeError as exc:
raise TypeError(f"expected python str, got {python!r}") from exc

args = [
"venv",
*(["--python", target_python] if target_python else []),
root,
]
ec, _, _ = _uv.run_uv(*args, env=env)
if ec != 0:
if cleanonfail and not already_existed:
_utils.safe_rmtree(root)
Expand Down Expand Up @@ -204,7 +214,9 @@ def base(self):
return self._base

def ensure_pip(self, downloaddir=None, *, installer=True, upgrade=True):
if not upgrade and _pip.is_pip_installed(self.python, env=self._env):
if _pip.is_pip_installed(self.python, env=self._env):
if upgrade:
self.upgrade_pip(installer=installer)
return
ec, _, _ = _pip.install_pip(
self.python,
Expand Down Expand Up @@ -240,7 +252,7 @@ def upgrade_pip(self, *, installer=True):

def ensure_reqs(self, *reqs, upgrade=True):
print("Installing requirements into the virtual environment %s" % self.root)
ec, _, _ = _pip.install_requirements(
ec, _, _ = _uv.install_requirements(
*reqs,
python=self.python,
env=self._env,
Expand Down
1 change: 0 additions & 1 deletion pyperformance/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ def cmd_venv_recreate(options, root, python, benchmarks):
venv.ensure_pip()
try:
venv.ensure_reqs(requirements)
venv.ensure_reqs(requirements)
except _venv.RequirementsInstallationFailedError:
sys.exit(1)
else:
Expand Down
8 changes: 2 additions & 6 deletions pyperformance/requirements/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
#
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# pip-compile --output-file=pyperformance/requirements/requirements.txt requirements.in
#
# This file was autogenerated by uv via the following command:
# uv pip compile -o requirements.txt requirements.in
packaging==23.1
# via -r requirements.in
psutil==5.9.5
Expand Down
25 changes: 15 additions & 10 deletions pyperformance/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ def _resolve_venv_python(venv):


def create_venv(root=None, python=sys.executable, *, verbose=False):
from pyperformance._uv import run_uv

if not root:
tmpdir = tempfile.mkdtemp()
root = os.path.join(tmpdir, "venv")
Expand All @@ -75,16 +77,19 @@ def cleanup():
def cleanup():
return None

run_cmd(
python or sys.executable,
"-m",
"venv",
root,
capture=not verbose,
onfail="raise",
verbose=verbose,
)
return root, _resolve_venv_python(root), cleanup
argv = ["venv", "--seed"]
if python:
argv.extend(["--python", python])
argv.append(root)
exitcode, _, _ = run_uv(*argv, capture=not verbose, verbose=verbose)
if exitcode:
if exitcode == 127:
raise RuntimeError(
"uv executable is required to provision test environments"
)
raise RuntimeError(f'"uv {" ".join(argv)}" failed with exit code {exitcode}')
venv_root = os.path.realpath(root)
return venv_root, _resolve_venv_python(venv_root), cleanup


class CleanupFile:
Expand Down
11 changes: 5 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# Update dependencies:
#
# - python3 -m pip install --user --upgrade pip-tools build
# - git clean -fdx # remove all untracked files!
# - (pip-compile --upgrade -o requirements.txt requirements.in)
# - uv pip compile --upgrade -o requirements.txt requirements.in
#
# Prepare a release:
#
Expand All @@ -28,8 +27,8 @@
# - git tag VERSION
# - git push --tags
# - Remove untracked files/dirs: git clean -fdx
# - python -m build
# - twine upload dist/*
# - uv run python -m build
# - uvx twine upload dist/*
#
# After the release:
#
Expand Down Expand Up @@ -65,8 +64,8 @@ classifiers = [
]
dynamic = [ "version" ]
dependencies = [
"packaging",
"pyperf",
"packaging==23.1",
"pyperf==2.9",
"tomli; python_version<'3.11'",
]

Expand Down
4 changes: 2 additions & 2 deletions requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
pyperf

# for benchmark metadata:
packaging
packaging==23.1
tomli; python_version < '3.11'


Expand All @@ -22,4 +22,4 @@ tomli; python_version < '3.11'
# The list of optional dependencies is hardcoded in pyperformance/venv.py

# XXX Do we still need this?
psutil
psutil==5.9.5
Loading
Loading