Skip to content
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

Uv3 #4

Closed
wants to merge 14 commits into from
6 changes: 2 additions & 4 deletions .github/workflows/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,10 @@ jobs:
matrix:
os:
[
ubuntu-20.04,
ubuntu-22.04,
ubuntu-24.04,
windows-2019,
windows-2022,
macos-11,
macos-12,
macos-13,
macos-14,
]
Expand All @@ -44,5 +42,5 @@ jobs:
python-version: 3.9
- uses: ./
with:
python-versions: "3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10, 3.11, 3.12, pypy-2.7, pypy-3.7, pypy-3.8, pypy-3.9, pypy-3.10"
python-versions: "3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13, pypy-2.7, pypy-3.7, pypy-3.8, pypy-3.9, pypy-3.10"
- run: nox --non-interactive --error-on-missing-interpreter --session github_actions_all_tests
12 changes: 11 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-20.04, windows-latest, macos-13]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-version:
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
- "3.13"
include:
- os: macos-14
python-version: "3.12"
Expand All @@ -30,6 +36,7 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
- name: Setup uv
uses: yezz123/setup-uv@v4
- name: Set up Miniconda
Expand All @@ -39,6 +46,7 @@ jobs:
python-version: ${{ matrix.python-version }}
miniforge-variant: Mambaforge
use-mamba: true
channels: conda-forge/label/python_rc,conda-forge
- name: Install Nox-under-test (uv)
run: uv pip install --system .
- name: Run tests on ${{ matrix.os }} (tox <4)
Expand All @@ -50,6 +58,8 @@ jobs:
with:
name: coverage-${{ github.job }}-${{ strategy.job-index }}
path: .coverage.*
include-hidden-files: true
if-no-files-found: error

coverage:
needs: build
Expand Down
13 changes: 8 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,18 @@ To just check for lint errors, run:

To run against a particular Python version:

nox --session tests-3.8
nox --session tests-3.9
nox --session tests-3.10
nox --session tests-3.11
nox --session tests-3.12
nox --session "tests(python='3.12', tox_version='latest')"
nox --session conda_tests
nox --session mamba_tests
nox --session micromamba_tests

When you send a pull request the CI will handle running everything, but it is
recommended to test as much as possible locally before pushing.

You can list all possible tests with:

nox --list-sessions

## Getting a sticker

If you've contributed to Nox, you can get a cute little Nox sticker. Reach out to Thea at me@thea.codes to request one.
Expand Down
2 changes: 1 addition & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ inputs:
python-versions:
description: "comma-separated list of python versions to install"
required: false
default: "3.8, 3.9, 3.10, 3.11, 3.12, pypy-3.9, pypy-3.10"
default: "3.8, 3.9, 3.10, 3.11, 3.12, 3.13, pypy-3.9, pypy-3.10"
branding:
icon: package
color: blue
Expand Down
35 changes: 35 additions & 0 deletions nox/virtualenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import abc
import contextlib
import functools
import json
import os
import platform
import re
Expand All @@ -28,6 +29,8 @@
from socket import gethostbyname
from typing import Any, ClassVar

from packaging import version

import nox
import nox.command
from nox.logger import logger
Expand Down Expand Up @@ -65,7 +68,32 @@ def find_uv() -> tuple[bool, str]:
return uv_on_path is not None, "uv"


def uv_version() -> version.Version:
ret = subprocess.run(
[UV, "version", "--output-format", "json"],
check=False,
text=True,
capture_output=True,
)
if ret.returncode == 0 and ret.stdout:
return version.Version(json.loads(ret.stdout).get("version"))
else:
logger.info("Failed to establish uv's version.")
return version.Version("0.0")


def uv_install_python(python_version: str) -> bool:
"""Attempts to install a given python version with uv"""
ret = subprocess.run(
[UV, "python", "install", python_version],
check=False,
)
return ret.returncode == 0


HAS_UV, UV = find_uv()
if HAS_UV:
UV_PYTHON_SUPPORT = uv_version() >= version.Version("0.3")


class InterpreterNotFound(OSError):
Expand Down Expand Up @@ -526,6 +554,12 @@ def _resolved_interpreter(self) -> str:
self._resolved = cleaned_interpreter
return self._resolved

if HAS_UV and UV_PYTHON_SUPPORT:
uv_python_success = uv_install_python(cleaned_interpreter)
if uv_python_success:
self._resolved = cleaned_interpreter
return self._resolved

# The rest of this is only applicable to Windows, so if we don't have
# an interpreter by now, raise.
if _SYSTEM != "Windows":
Expand Down Expand Up @@ -594,6 +628,7 @@ def create(self) -> bool:
f"Creating virtual environment ({self.venv_backend}) using"
f" {resolved_interpreter_name} in {self.location_name}"
)
logger.info(cmd)
nox.command.run(cmd, silent=True, log=nox.options.verbose or False)

return True
Expand Down
6 changes: 5 additions & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,15 @@
"python, tox_version",
[
(python, tox_version)
for python in ("3.8", "3.9", "3.10", "3.11", "3.12")
for python in ("3.8", "3.9", "3.10", "3.11", "3.12", "3.13")
for tox_version in ("latest", "<4")
],
)
def tests(session: nox.Session, tox_version: str) -> None:
"""Run test suite with pytest."""

session.run("python", "--version")
session.run("which", "python")
coverage_file = (
f".coverage.{sys.platform}.{session.python}.tox{tox_version.lstrip('<')}"
)
Expand Down Expand Up @@ -186,6 +188,7 @@ def _check_python_version(session: nox.Session) -> None:
"3.10",
"3.11",
"3.12",
"3.13",
"pypy3.9",
"pypy3.10",
]
Expand All @@ -203,6 +206,7 @@ def github_actions_default_tests(session: nox.Session) -> None:
"3.10",
"3.11",
"3.12",
"3.13",
"pypy3.8",
"pypy3.9",
"pypy3.10",
Expand Down
15 changes: 15 additions & 0 deletions tests/test_tox_to_nox.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,21 @@ def makeconfig(toxini_content):

return makeconfig

def test_sauco():
from subprocess import check_output
import sys
import os

print(check_output(["python", "--version"], text=True))
print(sys.executable)
print(sys.version)
print(os.getenv("PATH"))

if TOX4:
assert False




def test_trivial(makeconfig):
result = makeconfig(
Expand Down
43 changes: 43 additions & 0 deletions tests/test_virtualenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,11 @@ def test_uv_creation(make_one):
assert venv._check_reused_environment_type()


@has_uv
def test_uv_managed_python(make_one):
make_one(interpreter="cpython3.12", venv_backend="uv")


def test_constructor_defaults(make_one):
venv, _ = make_one()
assert venv.location
Expand Down Expand Up @@ -620,6 +625,42 @@ def find_uv_bin():
assert nox.virtualenv.find_uv() == (found, path)


@pytest.mark.parametrize(
["return_code", "stdout", "expected_result"],
[
(0, '{"version": "0.2.3", "commit_info": null}', "0.2.3"),
(1, None, "0.0"),
(1, '{"version": "9.9.9", "commit_info": null}', "0.0"),
],
)
def test_uv_version_error(monkeypatch, return_code, stdout, expected_result):
def mock_run(*args, **kwargs):
return subprocess.CompletedProcess(
args=["uv", "version", "--output-format", "json"],
stdout=stdout,
returncode=return_code,
)

monkeypatch.setattr(subprocess, "run", mock_run)
assert nox.virtualenv.uv_version() == version.Version(expected_result)


@pytest.mark.parametrize(
["requested_python", "expected_result"],
[
("3.11", True),
("pypy3.8", True),
("cpython3.9", True),
("python3.12", True),
("nonpython9.22", False),
("java11", False),
],
)
@has_uv
def test_uv_install(requested_python, expected_result):
assert nox.virtualenv.uv_install_python(requested_python) == expected_result


def test_create_reuse_venv_environment(make_one, monkeypatch):
# Making the reuse requirement more strict
monkeypatch.setenv("NOX_ENABLE_STALENESS_CHECK", "1")
Expand Down Expand Up @@ -840,6 +881,7 @@ def special_run(cmd, *args, **kwargs):


@mock.patch("nox.virtualenv._SYSTEM", new="Windows")
@mock.patch("nox.virtualenv.UV_PYTHON_SUPPORT", new=False)
def test__resolved_interpreter_windows_path_and_version(make_one, patch_sysfind):
# Establish that if we get a standard pythonX.Y path, we look it
# up via the path on Windows.
Expand All @@ -865,6 +907,7 @@ def test__resolved_interpreter_windows_path_and_version(make_one, patch_sysfind)
@pytest.mark.parametrize("sysfind_result", [r"c:\python37-x64\python.exe", None])
@pytest.mark.parametrize("sysexec_result", ["3.7.3\\n", RAISE_ERROR])
@mock.patch("nox.virtualenv._SYSTEM", new="Windows")
@mock.patch("nox.virtualenv.UV_PYTHON_SUPPORT", new=False)
def test__resolved_interpreter_windows_path_and_version_fails(
input_, sysfind_result, sysexec_result, make_one, patch_sysfind
):
Expand Down
Loading