Skip to content

Commit 6cc4c0e

Browse files
henryiiipre-commit-ci[bot]Copilot
authored
feat: use uv and dependency-groups (#588)
* feat: use uv and dependency-groups Signed-off-by: Henry Schreiner <[email protected]> * style: pre-commit fixes * style: pre-commit fixes * Apply suggestions from code review * style: pre-commit fixes * Update docs/pages/guides/gha_wheels.md Co-authored-by: Copilot <[email protected]> --------- Signed-off-by: Henry Schreiner <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Copilot <[email protected]>
1 parent 6dd7239 commit 6cc4c0e

14 files changed

+118
-108
lines changed

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ templates for Python packages?
2727
guidelines, with a WebAssembly version integrated with the guide. All checks
2828
cross-linked.
2929
- Follows [PyPA][] best practices and regularly updated.
30+
- Uses uv for high performance CI and task running.
3031

3132
Be sure you have read the [Scientific-Python Development Guide][] first, and
3233
possibly used them on a project or two. This is _not_ a minimal example or
@@ -126,6 +127,7 @@ backports structure with a small typing example.
126127

127128
- GitHub Actions runs testing for the generation itself
128129
- Uses nox so cookie development can be checked locally
130+
- Uses uv for high performance CI
129131
- GitHub actions deploy
130132
- C++ backends include cibuildwheel for wheel builds
131133
- Uses PyPI trusted publisher deployment
@@ -138,8 +140,9 @@ backports structure with a small typing example.
138140
- Includes spell checking
139141
- An pylint nox target can be used to run pylint, which integrated GHA
140142
annotations
141-
- A ReadTheDocs-ready Sphinx docs folder and `[docs]` extra
142-
- A test folder and pytest `[test]` extra
143+
- A ReadTheDocs-ready Sphinx docs folder and `docs` dependency-group
144+
- A test folder and pytest `test` dependency-group
145+
- A dev group for `uv run` integration
143146
- A noxfile is included with a few common targets
144147

145148
#### For developers:

docs/_includes/pyproject.md

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,27 +63,25 @@ Poetry and Setuptools).
6363

6464
### Extras
6565

66-
It is recommended to use extras instead of or in addition to making requirement
67-
files. These extras a) correctly interact with install requires and other
68-
built-in tools, b) are available directly when installing via PyPI, and c) are
69-
allowed in `requirements.txt`, `install_requires`, `pyproject.toml`, and most
70-
other places requirements are passed.
66+
Sometimes you want to ship a package with optional dependencies. For example,
67+
you might have extra requirements that are only needed for running a CLI, or for
68+
plotting. Users must opt-in to get these dependencies by adding them to the
69+
package or wheel name when installing, like `package[cli,mpl]`.
7170

7271
Here is an example of a simple extras:
7372

7473
```toml
7574
[project.optional-dependencies]
76-
test = [
77-
"pytest >=6.0",
75+
cli = [
76+
"click",
7877
]
7978
mpl = [
8079
"matplotlib >=2.0",
8180
]
8281
```
8382

8483
Self dependencies can be used by using the name of the package, such as
85-
`dev = ["package[test,examples]"]`, but this requires Pip 21.2 or newer. We
86-
recommend providing at least `test` and `docs`.
84+
`all = ["package[cli,mpl]"]`, (requires Pip 21.2+).
8785

8886
### Command line
8987

@@ -100,6 +98,33 @@ function, followed by a colon, then the function to call. If you use
10098
`__main__.py` as the file, then `python -m` followed by the module will also
10199
work to call the app (`__name__` will be `"__main__"` in that case).
102100

101+
### Development dependencies
102+
103+
It is recommended to use dependency-groups instead of making requirement files.
104+
This allows you to specify dependencies that are only needed for development;
105+
unlike extras, they are not available when installing via PyPI, but they are
106+
available for local installation, and the `dev` group is even installed by
107+
default when using `uv`.
108+
109+
Here is an example:
110+
111+
```toml
112+
[dependency-groups]
113+
test = [
114+
"pytest >=6.0",
115+
]
116+
dev = [
117+
{ include-group = "test" },
118+
]
119+
```
120+
121+
You can include one dependency group in another. Most tools allow you to install
122+
groups using `--group`, like `pip` (25.1+), `uv pip`, and the high level `uv`
123+
interface. You do not need to install the package, though usually you do (the
124+
high level `uv` interface does). Nox, Tox, and cibuildwheel all support groups
125+
too. The `dependency-groups` package provides tools to get the dependencies,
126+
too.
127+
103128
[metadata]: https://packaging.python.org/en/latest/specifications/core-metadata/
104129
[trove classifiers]: https://pypi.org/classifiers/
105130
[spdx]: https://spdx.org/licenses

docs/pages/guides/docs.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -272,17 +272,16 @@ with code_fence("yaml"):
272272
version: 2
273273

274274
build:
275-
os: ubuntu-22.04
275+
os: ubuntu-24.04
276276
tools:
277-
python: "3.12"
277+
python: "3.13"
278278
commands:
279279
- asdf plugin add uv
280280
- asdf install uv latest
281281
- asdf global uv latest
282-
- uv venv
283-
- uv pip install .[docs]
284-
- .venv/bin/python -m sphinx -T -b html -d docs/_build/doctrees -D
285-
language=en docs $READTHEDOCS_OUTPUT/html
282+
- uv sync --group docs
283+
- uv run python -m sphinx -T -b html -d docs/_build/doctrees -D language=en
284+
docs $READTHEDOCS_OUTPUT/html
286285
```
287286
<!-- prettier-ignore-end -->
288287
<!-- [[[end]]] -->
@@ -335,12 +334,13 @@ with code_fence("python"):
335334
]]] -->
336335
<!-- prettier-ignore-start -->
337336
```python
338-
@nox.session(reuse_venv=True)
337+
@nox.session(reuse_venv=True, default=False)
339338
def docs(session: nox.Session) -> None:
340339
"""
341340
Build the docs. Pass --non-interactive to avoid serving. First positional argument is the target directory.
342341
"""
343342
343+
doc_deps = nox.project.dependency_groups(PROJECT, "docs")
344344
parser = argparse.ArgumentParser()
345345
parser.add_argument(
346346
"-b", dest="builder", default="html", help="Build target (default: html)"
@@ -349,7 +349,7 @@ def docs(session: nox.Session) -> None:
349349
args, posargs = parser.parse_known_args(session.posargs)
350350
serve = args.builder == "html" and session.interactive
351351
352-
session.install("-e.[docs]", "sphinx-autobuild")
352+
session.install("-e.", *doc_deps, "sphinx-autobuild")
353353
354354
shared_args = (
355355
"-n", # nitpicky mode
@@ -396,7 +396,7 @@ with code_fence("python"):
396396
]]] -->
397397
<!-- prettier-ignore-start -->
398398
```python
399-
@nox.session
399+
@nox.session(default=False)
400400
def build_api_docs(session: nox.Session) -> None:
401401
"""
402402
Build (regenerate) API docs.

docs/pages/guides/gha_basic.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,11 @@ tests:
114114
python-version: ${{ matrix.python-version }}
115115
allow-prereleases: true
116116
117-
- name: Install package
118-
run: python -m pip install -e .[test]
117+
- name: Download uv
118+
uses: astral/setup-uv@v6
119119
120120
- name: Test package
121-
run: python -m pytest
121+
run: uv run pytest
122122
```
123123

124124
{% endraw %}

docs/pages/guides/gha_wheels.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,18 @@ Linux and Windows):
5656

5757
```toml
5858
[tool.cibuildwheel]
59-
test-extras = "test"
59+
test-groups = ["test"]
6060
test-command = "pytest {project}/tests"
61+
build-frontend = "build[uv]"
6162
# Optional
6263
build-verbosity = 1
6364
```
6465

65-
The `test-extras` will cause the pip install to use `[test]`. The `test-command`
66-
will use pytest to run your tests. You can also set the build verbosity (`-v` in
67-
pip) if you want to.
66+
The build frontend is set to `build[uv]`, which is faster than the default build
67+
backend; you just need uv installed, but that's easy to do. The `test-extras`
68+
will cause the pip install to use the dependency-group(s) specified. The
69+
`test-command` will use pytest to run your tests. You can also set the build
70+
verbosity (`-v` in pip) if you want to.
6871

6972
## Making an SDist
7073

@@ -114,6 +117,8 @@ build_wheels:
114117
fetch-depth: 0
115118
submodules: true
116119
120+
- uses: astral-sh/setup-uv@v6
121+
117122
- uses: pypa/[email protected]
118123
119124
- name: Upload wheels
@@ -150,8 +155,8 @@ you want a different supported image, set `CIBW_MANYLINUX_X86_64_IMAGE`,
150155
`CIBW_MANYLINUX_I686_IMAGE`, etc. If you always need a specific image, you can
151156
set that in the `pyproject.toml` file instead.
152157

153-
You can speed up the build by specifying the `build[uv]` build-frontend option
154-
and pre-installing `uv` on the runners.
158+
You can skip specifying the `build[uv]` build-frontend option and pre-installing
159+
`uv` on the runners, but it will be a slower.
155160

156161
## Publishing
157162

@@ -237,7 +242,8 @@ the sdist, for example).
237242

238243
> Other architectures
239244
>
240-
> On Travis, `cibuildwheel` even has the ability to create ARM and PowerPC
245+
> GitHub Actions supports ARM on Linux and Windows as well. On Travis,
246+
> `cibuildwheel` even has the ability to create rarer architectures like PowerPC
241247
> builds natively. IBM Z builds are also available but in beta. However, due to
242248
> Travis CI's recent dramatic reduction on open source support, emulating these
243249
> architectures on GHA or Azure is probably better. Maybe look into Cirrus CI,

docs/pages/guides/tasks.md

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,9 @@ def tests(session: nox.Session) -> None:
105105
"""
106106
Run the unit and regular tests.
107107
"""
108-
session.install(".[test]")
108+
pyproject = nox.project.load_toml()
109+
deps = nox.project.dependency_groups(pyproject, "test")
110+
session.install("-e.", *deps)
109111
session.run("pytest", *session.posargs)
110112
```
111113

@@ -129,15 +131,6 @@ You can see all defined sessions (along with the docstrings) using:
129131
$ nox -l
130132
```
131133

132-
It is a good idea to list the sessions you want by default by setting
133-
`nox.options.sessions` near the top of your file:
134-
135-
```python
136-
nox.options.sessions = ["lint", "tests"]
137-
```
138-
139-
This will keep you from running extra things like `docs` by default.
140-
141134
### Parametrizing
142135

143136
You can parametrize sessions. either on Python or on any other item.
@@ -212,7 +205,8 @@ def tests(session: nox.Session) -> None:
212205
"""
213206
Run the unit and regular tests.
214207
"""
215-
session.install("-e.[test]")
208+
test_deps = nox.project.dependency_groups(PROJECT, "test")
209+
session.install("-e.", *test_deps)
216210
session.run("pytest", *session.posargs)
217211
```
218212
<!-- prettier-ignore-end -->
@@ -226,12 +220,13 @@ with code_fence("python"):
226220
]]] -->
227221
<!-- prettier-ignore-start -->
228222
```python
229-
@nox.session(reuse_venv=True)
223+
@nox.session(reuse_venv=True, default=False)
230224
def docs(session: nox.Session) -> None:
231225
"""
232226
Build the docs. Pass --non-interactive to avoid serving. First positional argument is the target directory.
233227
"""
234228
229+
doc_deps = nox.project.dependency_groups(PROJECT, "docs")
235230
parser = argparse.ArgumentParser()
236231
parser.add_argument(
237232
"-b", dest="builder", default="html", help="Build target (default: html)"
@@ -240,7 +235,7 @@ def docs(session: nox.Session) -> None:
240235
args, posargs = parser.parse_known_args(session.posargs)
241236
serve = args.builder == "html" and session.interactive
242237
243-
session.install("-e.[docs]", "sphinx-autobuild")
238+
session.install("-e.", *doc_deps, "sphinx-autobuild")
244239
245240
shared_args = (
246241
"-n", # nitpicky mode
@@ -287,7 +282,7 @@ from pathlib import Path
287282
DIR = Path(__file__).parent.resolve()
288283
289284
290-
@nox.session
285+
@nox.session(default=False)
291286
def build(session: nox.Session) -> None:
292287
"""
293288
Build an SDist and wheel.

noxfile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ def tests(session: nox.Session, backend: str, vcs: bool) -> None:
225225
session.chdir(cookie)
226226

227227
name = f"cookie-{backend}"
228-
session.install(".[test]")
228+
session.install(".", "--group=test")
229229
session.run("python", "-m", "pytest", "-ra")
230230
version = session.run(
231231
"python",

{{cookiecutter.project_name}}/.github/CONTRIBUTING.md

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@ description of best practices for developing scientific packages.
66
# Quick development
77

88
The fastest way to start with development is to use nox. If you don't have nox,
9-
you can use `pipx run nox` to run it without installing, or `pipx install nox`.
10-
If you don't have pipx (pip for applications), then you can install with
11-
`pip install pipx` (the only case were installing an application with regular
12-
pip is reasonable). If you use macOS, then pipx and nox are both in brew, use
13-
`brew install pipx nox`.
9+
you can use `uvx nox` to run it without installing, or `uv tool install nox`. If
10+
you don't have uv, you can
11+
[install it a variety of ways](https://docs.astral.sh/uv/getting-started/installation/),
12+
including with pip, pipx, brew, and just downloading the binary (single file).
1413

1514
To use, run `nox`. This will lint and test using every installed version of
1615
Python on your system, skipping ones that are not installed. You can also run
@@ -30,38 +29,17 @@ environment for each run.
3029

3130
You can set up a development environment by running:
3231

33-
{% if cookiecutter.backend == "poetry" -%}
34-
35-
```bash
36-
poetry install
37-
```
38-
39-
{%- else -%}
40-
4132
```bash
42-
python3 -m venv .venv
43-
source ./.venv/bin/activate
44-
pip install -v -e .[dev]
33+
uv sync
4534
```
4635

47-
If you have the
48-
[Python Launcher for Unix](https://github.com/brettcannon/python-launcher), you
49-
can instead do:
50-
51-
```bash
52-
py -m venv .venv
53-
py -m install -v -e .[dev]
54-
```
55-
56-
{%- endif %}
57-
5836
# Pre-commit
5937

6038
You should prepare pre-commit, which will help you by checking that commits pass
6139
required checks:
6240

6341
```bash
64-
pip install pre-commit # or brew install pre-commit on macOS
42+
uv tool install pre-commit # or brew install pre-commit on macOS
6543
pre-commit install # Will install a pre-commit hook into the git repo
6644
```
6745

@@ -73,15 +51,15 @@ You can also/alternatively run `pre-commit run` (changes only) or
7351
Use pytest to run the unit checks:
7452

7553
```bash
76-
pytest
54+
uv run pytest
7755
```
7856

7957
# Coverage
8058

8159
Use pytest-cov to generate coverage reports:
8260

8361
```bash
84-
pytest --cov={{ cookiecutter.project_name }}
62+
uv run pytest --cov={{ cookiecutter.project_name }}
8563
```
8664

8765
# Building docs

0 commit comments

Comments
 (0)