Skip to content

Commit d6e29b1

Browse files
committed
Rewrite noxfile for uv workflow
- Simplify session names `dev`, `test`, `lint`, etc - Use uv in all calls - Move linting and formatting into noxfile session
1 parent d0db51f commit d6e29b1

File tree

1 file changed

+75
-112
lines changed

1 file changed

+75
-112
lines changed

noxfile.py

Lines changed: 75 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,16 @@
11
from __future__ import annotations
22

3-
import os
3+
import functools
44
import pathlib
55
import shutil
6-
import sys
7-
from functools import partial
86

97
import nox
108

119
# Control factors for finding pieces of the module
1210
MODULE_NAME = "module_name"
13-
TESTS_PATH = "tests"
14-
COVERAGE_FAIL_UNDER = 50
15-
VENV_PATH = "./.venv"
11+
COVERAGE_FAIL_UNDER = "100"
1612
LINT_PATH = "./src"
17-
REQUIREMENTS_PATH = "./requirements"
18-
19-
20-
LINTING_COMMANDS = (
21-
(
22-
"isort",
23-
"--verbose",
24-
"--force-single-line-imports",
25-
"--profile",
26-
"black",
27-
"--add-import",
28-
"from __future__ import annotations",
29-
LINT_PATH,
30-
TESTS_PATH,
31-
),
32-
("black", "--verbose", LINT_PATH, TESTS_PATH),
33-
("flake8", "--show-source", "--verbose", LINT_PATH, TESTS_PATH),
34-
("mypy", "--no-incremental", "--package", MODULE_NAME),
35-
("mypy", "--no-incremental", TESTS_PATH),
36-
)
13+
TESTS_PATH = "./tests"
3714

3815
# What we allowed to clean (delete)
3916
CLEANABLE_TARGETS = [
@@ -43,6 +20,7 @@
4320
"./.coverage",
4421
"./.coverage.*",
4522
"./coverage.json",
23+
"./htmlcov",
4624
"./**/.mypy_cache",
4725
"./**/.pytest_cache",
4826
"./**/__pycache__",
@@ -51,132 +29,123 @@
5129
]
5230

5331
# Define the default sessions run when `nox` is called on the CLI
54-
nox.options.default_venv_backend = "virtualenv"
32+
nox.options.default_venv_backend = "uv"
5533
nox.options.sessions = ["lint", "test"]
5634

35+
# All linters are run with `uv run --active`
36+
# Ordering matters. Formatters should run before static checks.
37+
LINTERS: list[tuple[str, ...]] = [
38+
(
39+
"isort",
40+
"--verbose",
41+
"--force-single-line-imports",
42+
"--profile",
43+
"black",
44+
"--add-import",
45+
"from __future__ import annotations",
46+
LINT_PATH,
47+
TESTS_PATH,
48+
),
49+
("black", "--verbose", LINT_PATH, TESTS_PATH),
50+
("flake8", "--verbose", "--show-source", LINT_PATH, TESTS_PATH),
51+
("mypy", "--pretty", "--no-incremental", "--package", MODULE_NAME),
52+
("mypy", "--pretty", "--no-incremental", TESTS_PATH),
53+
]
5754

58-
@nox.session()
59-
def dev(session: nox.Session) -> None:
60-
"""Setup a development environment by creating the venv and installs dependencies."""
61-
# Use the active environement if it exists, otherwise create a new one
62-
venv_path = os.environ.get("VIRTUAL_ENV", VENV_PATH)
63-
64-
if sys.platform == "win32":
65-
venv_path = f"{venv_path}/Scripts"
66-
activate_command = f"{venv_path}/activate"
67-
else:
68-
venv_path = f"{venv_path}/bin"
69-
activate_command = f"source {venv_path}/activate"
70-
71-
if not os.path.exists(VENV_PATH):
72-
session.run("python", "-m", "venv", VENV_PATH, "--upgrade-deps")
73-
74-
python = partial(session.run, f"{venv_path}/python", "-m")
75-
contraint = ("--constraint", f"{REQUIREMENTS_PATH}/constraints.txt")
7655

77-
python("pip", "install", "--editable", ".[dev,test]", *contraint, external=True)
56+
@nox.session(name="dev", python=False)
57+
def dev_session(session: nox.Session) -> None:
58+
"""Create a development environment. Optionally: Provide the python version to use."""
59+
python_version: list[str] = []
60+
if session.posargs:
61+
python_version = ["--python", session.posargs[0]]
7862

79-
if not os.environ.get("VIRTUAL_ENV"):
80-
session.log(f"\n\nRun '{activate_command}' to enter the virtual environment.\n")
63+
session.run_install("uv", "sync", "--frozen", "--all-groups", *python_version, external=True)
64+
session.run_install("uv", "run", "pre-commit", "install", external=True)
8165

8266

8367
@nox.session(name="test")
8468
def run_tests_with_coverage(session: nox.Session) -> None:
85-
"""Run pytest with coverage, outputs console report and json."""
69+
"""Run pytest in isolated environment, display coverage. Extra arguements passed to pytest."""
8670
print_standard_logs(session)
8771

88-
contraint = ("--constraint", f"{REQUIREMENTS_PATH}/constraints.txt")
72+
partial = "partial-coverage" in session.posargs
73+
extra: list[str] = []
74+
if "no-config" in session.posargs:
75+
session.posargs.remove("no-config")
76+
extra = ["--no-config"]
8977

90-
session.install(".[test]", *contraint)
78+
session.run_install("uv", "sync", "--frozen", "--active", "--group", "test", *extra)
9179

92-
coverage = partial(session.run, "python", "-m", "coverage")
80+
coverage = functools.partial(session.run, "uv", "run", "--active", *extra, "coverage")
9381

9482
coverage("erase")
9583

96-
if "partial-coverage" in session.posargs:
97-
coverage("run", "--parallel-mode", "--module", "pytest", TESTS_PATH)
84+
if partial:
85+
session.posargs.remove("partial-coverage")
86+
coverage("run", "--parallel-mode", "--module", "pytest", *session.posargs)
9887
else:
99-
coverage("run", "--module", "pytest", TESTS_PATH)
88+
coverage("run", "--module", "pytest", *session.posargs)
10089
coverage("report", "--show-missing", f"--fail-under={COVERAGE_FAIL_UNDER}")
101-
coverage("json")
90+
coverage("html")
10291

10392

104-
@nox.session()
105-
def coverage_combine(session: nox.Session) -> None:
106-
"""CI: Combine parallel-mode coverage files and produce reports."""
93+
@nox.session(name="combine")
94+
def combine_coverage(session: nox.Session) -> None:
95+
"""Combine parallel-mode coverage files and produce reports."""
10796
print_standard_logs(session)
10897

109-
contraint = ("--constraint", f"{REQUIREMENTS_PATH}/constraints.txt")
98+
session.run_install("uv", "sync", "--frozen", "--active", "--group", "test")
11099

111-
session.install("-r", f"{REQUIREMENTS_PATH}/requirements-test.txt", *contraint)
100+
coverage = functools.partial(session.run, "uv", "run", "--active", "coverage")
112101

113-
coverage = partial(session.run, "python", "-m", "coverage")
114102
coverage("combine")
115103
coverage("report", "--show-missing", f"--fail-under={COVERAGE_FAIL_UNDER}")
116-
coverage("json")
104+
coverage("html")
117105

118106

119107
@nox.session(name="lint")
120108
def run_linters_and_formatters(session: nox.Session) -> None:
121109
"""Run code formatters, linters, and type checking against all files."""
122110
print_standard_logs(session)
123111

124-
contraint = ("--constraint", f"{REQUIREMENTS_PATH}/constraints.txt")
125-
session.install(".[dev,test]", *contraint)
126-
127-
python = partial(session.run, "python", "-m")
112+
session.run_install("uv", "sync", "--frozen", "--active", "--group", "test", "--group", "lint")
128113

129-
for linter_command in LINTING_COMMANDS:
130-
python(*linter_command)
114+
for linter_args in LINTERS:
115+
session.run("uv", "run", "--active", *linter_args)
131116

132117

133-
@nox.session()
134-
def build(session: nox.Session) -> None:
135-
"""Build distribution files."""
118+
@nox.session(name="build")
119+
def build_artifacts(session: nox.Session) -> None:
120+
"""Build a sdist and wheel."""
136121
print_standard_logs(session)
137122

138-
session.install("build")
139-
session.run("python", "-m", "build")
123+
session.run("uv", "build")
140124

141125

142-
@nox.session(name="update-deps")
143-
def update_deps(session: nox.Session) -> None:
144-
"""Process requirement*.txt files, updating only additions/removals."""
126+
@nox.session(name="upgrade")
127+
def upgrade_dependencies(session: nox.Session) -> None:
128+
"""Upgrade all versions of all dependencies."""
145129
print_standard_logs(session)
146130

147-
session.install("pip-tools")
148-
session.run(
149-
"pip-compile",
150-
"--strip-extras",
151-
"--no-annotate",
152-
"--no-emit-index-url",
153-
"--output-file",
154-
f"{REQUIREMENTS_PATH}/constraints.txt",
155-
*get_requirement_files(),
156-
)
157-
158-
159-
@nox.session(name="upgrade-deps")
160-
def upgrade_deps(session: nox.Session) -> None:
161-
"""Process requirement*.txt files and upgrade all libraries as possible."""
131+
session.run("uv", "lock", "--upgrade")
132+
133+
134+
@nox.session(name="upgrade-package")
135+
def upgrade_specific_package(session: nox.Session) -> None:
136+
"""Upgrade specific package name given in extra args."""
162137
print_standard_logs(session)
163138

164-
session.install("pip-tools")
165-
session.run(
166-
"pip-compile",
167-
"--strip-extras",
168-
"--no-annotate",
169-
"--no-emit-index-url",
170-
"--upgrade",
171-
"--output-file",
172-
f"{REQUIREMENTS_PATH}/constraints.txt",
173-
*get_requirement_files(),
174-
)
139+
if not session.posargs:
140+
session.log("No package name provided, nothing to do.")
141+
142+
else:
143+
session.run("uv", "lock", "--upgrade-package", *session.posargs)
175144

176145

177146
@nox.session(python=False)
178-
def clean(_: nox.Session) -> None:
179-
"""Clean cache, .pyc, .pyo, and test/build artifact files from project."""
147+
def clean(session: nox.Session) -> None:
148+
"""Clean cache, .pyc, .pyo, and build artifact files from project."""
180149
count = 0
181150
for searchpath in CLEANABLE_TARGETS:
182151
for filepath in pathlib.Path(".").glob(searchpath):
@@ -186,17 +155,11 @@ def clean(_: nox.Session) -> None:
186155
filepath.unlink()
187156
count += 1
188157

189-
print(f"{count} files cleaned.")
158+
session.log(f"{count} files cleaned.")
190159

191160

192161
def print_standard_logs(session: nox.Session) -> None:
193162
"""Reusable output for monitoring environment factors."""
194163
version = session.run("python", "--version", silent=True)
195164
session.log(f"Running from: {session.bin}")
196165
session.log(f"Running with: {version}")
197-
198-
199-
def get_requirement_files() -> list[pathlib.Path]:
200-
"""Get a list of requirement files matching "requirements*.txt"."""
201-
glob = pathlib.Path(REQUIREMENTS_PATH).glob("requirements*.txt")
202-
return [path for path in glob]

0 commit comments

Comments
 (0)