Skip to content

Commit 31ea148

Browse files
authored
Backport PR #3231 on branch 1.10.x (Switch to towncrier) (#3234)
1 parent 76cb5e2 commit 31ea148

29 files changed

+390
-383
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
/tests/**/*failed-diff.png
1919

2020
# Environment management
21-
/hatch.toml
2221
/Pipfile
2322
/Pipfile.lock
2423
/requirements*.lock

.readthedocs.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,16 @@ version: 2
22
submodules:
33
include: all
44
build:
5-
os: ubuntu-20.04
5+
os: ubuntu-24.04
66
tools:
77
python: '3.12'
8+
jobs:
9+
post_checkout:
10+
# unshallow so version can be derived from tag
11+
- git fetch --unshallow || true
12+
pre_build:
13+
# run towncrier to preview the next version’s release notes
14+
- ( find docs/release-notes -regex '[^.]+[.][^.]+.md' | grep -q . ) && towncrier build --keep || true
815
sphinx:
916
fail_on_warning: true # do not change or you will be fired
1017
configuration: docs/conf.py

ci/scripts/min-deps.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!python3
1+
#!/usr/bin/env python3
22
from __future__ import annotations
33

44
import argparse

ci/scripts/towncrier_automation.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#!/usr/bin/env python3
2+
from __future__ import annotations
3+
4+
import argparse
5+
import subprocess
6+
from typing import TYPE_CHECKING
7+
8+
from packaging.version import Version
9+
10+
if TYPE_CHECKING:
11+
from collections.abc import Sequence
12+
13+
14+
class Args(argparse.Namespace):
15+
version: str
16+
dry_run: bool
17+
18+
19+
def parse_args(argv: Sequence[str] | None = None) -> Args:
20+
parser = argparse.ArgumentParser(
21+
prog="towncrier-automation",
22+
description=(
23+
"This script runs towncrier for a given version, "
24+
"creates a branch off of the current one, "
25+
"and then creates a PR into the original branch with the changes. "
26+
"The PR will be backported to main if the current branch is not main."
27+
),
28+
)
29+
parser.add_argument(
30+
"version",
31+
type=str,
32+
help=(
33+
"The new version for the release must have at least three parts, like `major.minor.patch` and no `major.minor`. "
34+
"It can have a suffix like `major.minor.patch.dev0` or `major.minor.0rc1`."
35+
),
36+
)
37+
parser.add_argument(
38+
"--dry-run",
39+
help="Whether or not to dry-run the actual creation of the pull request",
40+
action="store_true",
41+
)
42+
args = parser.parse_args(argv, Args())
43+
# validate the version
44+
if len(Version(args.version).release) != 3:
45+
msg = f"Version argument {args.version} must contain major, minor, and patch version."
46+
raise ValueError(msg)
47+
return args
48+
49+
50+
def main(argv: Sequence[str] | None = None) -> None:
51+
args = parse_args(argv)
52+
53+
# Run towncrier
54+
subprocess.run(
55+
["towncrier", "build", f"--version={args.version}", "--yes"], check=True
56+
)
57+
58+
# Check if we are on the main branch to know if we need to backport
59+
base_branch = subprocess.run(
60+
["git", "rev-parse", "--abbrev-ref", "HEAD"],
61+
capture_output=True,
62+
text=True,
63+
check=True,
64+
).stdout.strip()
65+
pr_description = (
66+
"" if base_branch == "main" else "@meeseeksmachine backport to main"
67+
)
68+
branch_name = f"release_notes_{args.version}"
69+
70+
# Create a new branch + commit
71+
subprocess.run(["git", "switch", "-c", branch_name], check=True)
72+
subprocess.run(["git", "add", "docs/release-notes"], check=True)
73+
pr_title = f"(chore): generate {args.version} release notes"
74+
subprocess.run(["git", "commit", "-m", pr_title], check=True)
75+
76+
# push
77+
if not args.dry_run:
78+
subprocess.run(
79+
["git", "push", "--set-upstream=origin", branch_name], check=True
80+
)
81+
else:
82+
print("Dry run, not pushing")
83+
84+
# Create a PR
85+
subprocess.run(
86+
[
87+
"gh",
88+
"pr",
89+
"create",
90+
f"--base={base_branch}",
91+
f"--title={pr_title}",
92+
f"--body={pr_description}",
93+
*(["--label=no milestone"] if base_branch == "main" else []),
94+
*(["--dry-run"] if args.dry_run else []),
95+
],
96+
check=True,
97+
)
98+
99+
# Enable auto-merge
100+
if not args.dry_run:
101+
subprocess.run(
102+
["gh", "pr", "merge", branch_name, "--auto", "--squash"], check=True
103+
)
104+
else:
105+
print("Dry run, not merging")
106+
107+
108+
if __name__ == "__main__":
109+
main()

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
"scanpydoc", # needs to be before sphinx.ext.linkcode
8080
"sphinx.ext.linkcode",
8181
"sphinx_design",
82+
"sphinx_tabs.tabs",
8283
"sphinx_search.extension",
8384
"sphinxext.opengraph",
8485
*[p.stem for p in (HERE / "extensions").glob("*.py") if p.stem not in {"git_ref"}],

docs/dev/code.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,12 @@
1212

1313
## Code style
1414

15-
New code should follow
16-
[Black](https://black.readthedocs.io/en/stable/the_black_code_style.html)
17-
and
18-
[flake8](https://flake8.pycqa.org).
19-
We ignore a couple of flake8 checks which are documented in the .flake8 file in the root of this repository.
20-
To learn how to ignore checks per line please read
21-
[flake8 violations](https://flake8.pycqa.org/en/latest/user/violations.html).
22-
Additionally, we use Scanpy’s
23-
[EditorConfig](https://github.com/scverse/scanpy/blob/main/.editorconfig),
15+
Code contributions will be formatted and style checked using [Ruff][].
16+
Ignored checks are configured in the `tool.ruff.lint` section of {file}`pyproject.toml`.
17+
To learn how to ignore checks per line please read about [ignoring errors][].
18+
Additionally, we use Scanpy’s [EditorConfig][],
2419
so using an editor/IDE with support for both is helpful.
20+
21+
[Ruff]: https://docs.astral.sh/ruff/
22+
[ignoring errors]: https://docs.astral.sh/ruff/tutorial/#ignoring-errors
23+
[EditorConfig]: https://github.com/scverse/scanpy/blob/main/.editorconfig

docs/dev/documentation.md

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,37 @@
44

55
## Building the docs
66

7-
Dependencies for building the documentation for scanpy can be installed with `pip install -e "scanpy[doc]"`
8-
9-
To build the docs, enter the `docs` directory and run `make html`. After this process completes you can take a look at the docs by opening `scanpy/docs/_build/html/index.html`.
7+
To build the docs, run `hatch run docs:build`.
8+
Afterwards, you can run `hatch run docs:open` to open {file}`docs/_build/html/index.html`.
109

1110
Your browser and Sphinx cache docs which have been built previously.
1211
Sometimes these caches are not invalidated when you've updated the docs.
1312
If docs are not updating the way you expect, first try "force reloading" your browser page – e.g. reload the page without using the cache.
14-
Next, if problems persist, clear the sphinx cache and try building them again (`make clean` from `docs` directory).
15-
16-
```{note}
17-
If you've cloned the repository pre 1.8.0, you may need to be more thorough in cleaning.
18-
If you run into warnings try removing all untracked files in the docs directory.
19-
```
13+
Next, if problems persist, clear the sphinx cache (`hatch run docs:clean`) and try building them again.
2014

2115
## Adding to the docs
2216

23-
For any user-visible changes, please make sure a note has been added to the release notes for the relevant version so we can credit you!
24-
These files are found in the `docs/release-notes/` directory.
17+
For any user-visible changes, please make sure a note has been added to the release notes using [`hatch run towncrier:create`][towncrier create].
2518
We recommend waiting on this until your PR is close to done since this can often causes merge conflicts.
2619

2720
Once you've added a new function to the documentation, you'll need to make sure there is a link somewhere in the documentation site pointing to it.
2821
This should be added to `docs/api.md` under a relevant heading.
2922

30-
For tutorials and more in depth examples, consider adding a notebook to [scanpy-tutorials](https://github.com/scverse/scanpy-tutorials/).
23+
For tutorials and more in depth examples, consider adding a notebook to the [scanpy-tutorials][] repository.
3124

32-
The tutorials are tied to this repository via a submodule. To update the submodule, run `git submodule update --remote` from the root of the repository. Subsequently, commit and push the changes in a PR. This should be done before each release to ensure the tutorials are up to date.
25+
The tutorials are tied to this repository via a submodule.
26+
To update the submodule, run `git submodule update --remote` from the root of the repository.
27+
Subsequently, commit and push the changes in a PR.
28+
This should be done before each release to ensure the tutorials are up to date.
29+
30+
[towncrier create]: https://towncrier.readthedocs.io/en/stable/tutorial.html#creating-news-fragments
31+
[scanpy-tutorials]: https://github.com/scverse/scanpy-tutorials/
3332

3433
## docstrings format
3534

3635
We use the numpydoc style for writing docstrings.
37-
We'd primarily suggest looking at existing docstrings for examples, but the [napolean guide to numpy style docstrings](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_numpy.html#example-numpy) is also a great source.
38-
If you're unfamiliar with the reStructuredText (`rst`) markup format, [Sphinx has a useful primer](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html).
36+
We'd primarily suggest looking at existing docstrings for examples, but the [napolean guide to numpy style docstrings][] is also a great source.
37+
If you're unfamiliar with the reStructuredText (rST) markup format, check out the [Sphinx rST primer][].
3938

4039
Some key points:
4140

@@ -46,26 +45,34 @@ Some key points:
4645

4746
Look at [sc.tl.louvain](https://github.com/scverse/scanpy/blob/a811fee0ef44fcaecbde0cad6336336bce649484/scanpy/tools/_louvain.py#L22-L90) as an example for everything mentioned here.
4847

48+
[napolean guide to numpy style docstrings]: https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_numpy.html#example-numpy
49+
[sphinx rst primer]: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
50+
4951
### Plots in docstrings
5052

5153
One of the most useful things you can include in a docstring is examples of how the function should be used.
5254
These are a great way to demonstrate intended usage and give users a template they can copy and modify.
53-
We're able to include the plots produced by these snippets in the rendered docs using [matplotlib's plot directive](https://matplotlib.org/devel/plot_directive.html).
55+
We're able to include the plots produced by these snippets in the rendered docs using [matplotlib's plot directive][].
5456
For examples of this, see the `Examples` sections of {func}`~scanpy.pl.dotplot` or {func}`~scanpy.pp.calculate_qc_metrics`.
5557

5658
Note that anything in these sections will need to be run when the docs are built, so please keep them computationally light.
5759

5860
- If you need computed features (e.g. an embedding, differential expression results) load data that has this precomputed.
5961
- Try to re-use datasets, this reduces the amount of data that needs to be downloaded to the CI server.
6062

63+
[matplotlib's plot directive]: https://matplotlib.org/devel/plot_directive.html
64+
6165
### `Params` section
6266

6367
The `Params` abbreviation is a legit replacement for `Parameters`.
6468

6569
To document parameter types use type annotations on function parameters.
6670
These will automatically populate the docstrings on import, and when the documentation is built.
6771

68-
Use the python standard library types (defined in [collections.abc](https://docs.python.org/3/library/collections.abc.html) and [typing](https://docs.python.org/3/library/typing.html) modules) for containers, e.g. `Sequence`s (like `list`), `Iterable`s (like `set`), and `Mapping`s (like `dict`).
72+
Use the python standard library types (defined in {mod}`collections.abc` and {mod}`typing` modules) for containers, e.g.
73+
{class}`~collections.abc.Sequence`s (like `list`),
74+
{class}`~collections.abc.Iterable`s (like `set`), and
75+
{class}`~collections.abc.Mapping`s (like `dict`).
6976
Always specify what these contain, e.g. `{'a': (1, 2)}``Mapping[str, Tuple[int, int]]`.
7077
If you can’t use one of those, use a concrete class like `AnnData`.
7178
If your parameter only accepts an enumeration of strings, specify them like so: `Literal['elem-1', 'elem-2']`.
@@ -80,8 +87,7 @@ There are three types of return sections – prose, tuple, and a mix of both.
8087

8188
#### Examples
8289

83-
For simple cases, use prose as in
84-
{func}`~scanpy.pp.normalize_total`
90+
For simple cases, use prose as in {func}`~scanpy.pp.normalize_total`:
8591

8692
```rst
8793
Returns
@@ -110,7 +116,7 @@ def myfunc(...) -> tuple[int, str]:
110116
```
111117

112118
Many functions also just modify parts of the passed AnnData object, like e.g. {func}`~scanpy.tl.dpt`.
113-
You can then combine prose and lists to best describe what happens.
119+
You can then combine prose and lists to best describe what happens:
114120

115121
```rst
116122
Returns

0 commit comments

Comments
 (0)