diff --git a/.gitignore b/.gitignore index 51533016e..8124b9450 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,6 @@ __pycache__/ /src/anndata/_version.py /requirements*.lock /.python-version -/hatch.toml # Test results (nunit/junit) and coverage /test-data/ diff --git a/ci/scripts/min-deps.py b/ci/scripts/min-deps.py index a7482e70e..b5b0b980e 100755 --- a/ci/scripts/min-deps.py +++ b/ci/scripts/min-deps.py @@ -27,7 +27,7 @@ def min_dep(req: Requirement) -> Requirement: ------- >>> min_dep(Requirement("numpy>=1.0")) - "numpy==1.0" + """ req_name = req.name if req.extras: diff --git a/docs/conf.py b/docs/conf.py index a0e338006..6a0006a70 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -61,9 +61,11 @@ "sphinx.ext.linkcode", "nbsphinx", "IPython.sphinxext.ipython_console_highlighting", - "patch_sphinx_toolbox_autoprotocol", + "patch_sphinx_toolbox_autoprotocol", # internal extension "sphinx_toolbox.more_autodoc.autoprotocol", + # other internal extensions "patch_myst_cite", + "release_notes", ] myst_enable_extensions = [ "html_image", # So README.md can be used on github and sphinx docs diff --git a/docs/extensions/release_notes.py b/docs/extensions/release_notes.py new file mode 100644 index 000000000..bb28453a7 --- /dev/null +++ b/docs/extensions/release_notes.py @@ -0,0 +1,111 @@ +from __future__ import annotations + +import itertools +import re +from pathlib import Path +from typing import TYPE_CHECKING + +from docutils import nodes +from packaging.version import Version +from sphinx.util.docutils import SphinxDirective + +if TYPE_CHECKING: + from collections.abc import Iterable, Sequence + from typing import ClassVar + + from myst_parser.mdit_to_docutils.base import DocutilsRenderer + from sphinx.application import Sphinx + + +FULL_VERSION_RE = re.compile(r"^(\d+)\.(\d+)\.(\d+)$") + + +class ReleaseNotes(SphinxDirective): + required_arguments: ClassVar = 1 + + def run(self) -> Sequence[nodes.Node]: + dir_ = Path(self.arguments[0]) + # resolve relative dir + if not dir_.is_absolute(): + src_file = Path(self.get_source_info()[0]) + if not src_file.is_file(): + msg = f"Cannot find relative path to: {src_file}" + raise self.error(msg) + dir_ = src_file.parent / self.arguments[0] + if not dir_.is_dir(): + msg = f"Not a directory: {dir_}" + raise self.error(msg) + + versions = sorted( + ( + (Version(f.stem), f) + for f in dir_.iterdir() + if FULL_VERSION_RE.match(f.stem) + ), + reverse=True, # descending + ) + version_groups = itertools.groupby( + versions, key=lambda vf: (vf[0].major, vf[0].minor) + ) + for (major, minor), versions in version_groups: + self.render_version_group(major, minor, versions) + return [] + + def render_version_group( + self, major: int, minor: int, versions: Iterable[tuple[Version, Path]] + ) -> None: + target = nodes.target( + ids=[f"v{major}-{minor}"], + names=[f"v{major}.{minor}"], + ) + section = nodes.section( + "", + nodes.title("", f"Version {major}.{minor}"), + ids=[], + names=[f"version {major}.{minor}"], + ) + self.state.document.note_implicit_target(section) + self.state.document.note_explicit_target(target) + # append target and section to parent + self.renderer.current_node.append(target) + self.renderer.update_section_level_state(section, 2) + # append children to section + with self.renderer.current_node_context(section): + for _, p in versions: + self.render_include(p) + + def render_include(self, path: Path) -> None: + # hacky solution because of https://github.com/executablebooks/MyST-Parser/issues/967 + from docutils.parsers.rst.directives.misc import Include + from myst_parser.mocking import MockIncludeDirective + + srcfile, lineno = self.get_source_info() + parent_dir = Path(srcfile).parent + + d = MockIncludeDirective( + renderer=self.renderer, + name=type(self).__name__, + klass=Include, # type: ignore # wrong type hint + arguments=[str(path.relative_to(parent_dir))], + options={}, + body=[], + lineno=lineno, + ) + d.run() + + # TODO: replace the above with this once the above mentioned bug is fixed + # from sphinx.util.parsing import nested_parse_to_nodes + # return nested_parse_to_nodes( + # self.state, + # path.read_text(), + # source=str(path), + # offset=self.content_offset, + # ) + + @property + def renderer(self) -> DocutilsRenderer: + return self.state._renderer + + +def setup(app: Sphinx) -> None: + app.add_directive("release-notes", ReleaseNotes) diff --git a/docs/index.md b/docs/index.md index 61d759ab1..06aacaed6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,7 +5,7 @@ See {doc}`/release-notes/index`, particularly {ref}`v0.10` for the current release, -and {ref}`v0.11` for the upcoming release, +and [the `.feature` fragments](https://github.com/scverse/anndata/tree/main/docs) for the upcoming release, ```{toctree} :hidden: true diff --git a/docs/release-notes/0.10.9.md b/docs/release-notes/0.10.9.md deleted file mode 100644 index 519704b3a..000000000 --- a/docs/release-notes/0.10.9.md +++ /dev/null @@ -1,21 +0,0 @@ -(v0.10.9)= -### 0.10.9 {small}`the future` - -#### Bugfix - -* Add warning for setting `X` on a view with repeated indices {pr}`1501` {user}`ilan-gold` -* Coerce {class}`numpy.matrix` classes to arrays when trying to store them in `AnnData` {pr}`1516` {user}`flying-sheep` -* Fix for setting a dense `X` view with a sparse matrix {pr}`1532` {user}`ilan-gold` -* Upper bound {mod}`numpy` for `gpu` installation on account of {issue}`cupy/cupy#8391` {pr}`1540` {user}`ilan-gold` -* Fix writing large number of columns for `h5` files {pr}`1147` {user}`ilan-gold` {user}`selmanozleyen` -* Upper bound dask on account of {issue}`1579` {pr}`1580` {user}`ilan-gold` -* Disallow using {class}`~pandas.DataFrame`s with multi-index columns {pr}`1589` {user}`ilan-gold` -* Ensure setting {attr}`pandas.DataFrame.index` on a view of a {class}`~anndata.AnnData` instantiates the {class}`~pandas.DataFrame` from the view {pr}`1586` {user}`ilan-gold` - -#### Documentation - -* add `callback` typing for {func}`~anndata.experimental.read_dispatched` and {func}`~anndata.experimental.write_dispatched` {pr}`1557` {user}`ilan-gold` - -#### Performance - -* Support for `concat_on_disk` outer join {pr}`1504` {user}`ilan-gold` diff --git a/docs/release-notes/1147.bugfix.md b/docs/release-notes/1147.bugfix.md new file mode 100644 index 000000000..c02530f58 --- /dev/null +++ b/docs/release-notes/1147.bugfix.md @@ -0,0 +1 @@ +Fix writing large number of columns for `h5` files {user}`ilan-gold` {user}`selmanozleyen` diff --git a/docs/release-notes/1501.bugfix.md b/docs/release-notes/1501.bugfix.md new file mode 100644 index 000000000..d08e6fe82 --- /dev/null +++ b/docs/release-notes/1501.bugfix.md @@ -0,0 +1 @@ +Add warning for setting `X` on a view with repeated indices {user}`ilan-gold` diff --git a/docs/release-notes/1504.performance.md b/docs/release-notes/1504.performance.md new file mode 100644 index 000000000..b50436d9d --- /dev/null +++ b/docs/release-notes/1504.performance.md @@ -0,0 +1 @@ +Support for `concat_on_disk` outer join {user}`ilan-gold` diff --git a/docs/release-notes/1516.bugfix.md b/docs/release-notes/1516.bugfix.md new file mode 100644 index 000000000..71fbf1de8 --- /dev/null +++ b/docs/release-notes/1516.bugfix.md @@ -0,0 +1 @@ +Coerce {class}`numpy.matrix` classes to arrays when trying to store them in `AnnData` {user}`flying-sheep` diff --git a/docs/release-notes/1532.bugfix.md b/docs/release-notes/1532.bugfix.md new file mode 100644 index 000000000..6df5a7a22 --- /dev/null +++ b/docs/release-notes/1532.bugfix.md @@ -0,0 +1 @@ +Fix for setting a dense `X` view with a sparse matrix {user}`ilan-gold` diff --git a/docs/release-notes/1540.bugfix.md b/docs/release-notes/1540.bugfix.md new file mode 100644 index 000000000..4a03c72c9 --- /dev/null +++ b/docs/release-notes/1540.bugfix.md @@ -0,0 +1 @@ +Upper bound {mod}`numpy` for `gpu` installation on account of {issue}`cupy/cupy#8391`{user}`ilan-gold` diff --git a/docs/release-notes/1557.doc.md b/docs/release-notes/1557.doc.md new file mode 100644 index 000000000..39e85410e --- /dev/null +++ b/docs/release-notes/1557.doc.md @@ -0,0 +1 @@ +add `callback` typing for {func}`~anndata.experimental.read_dispatched` and {func}`~anndata.experimental.write_dispatched` {user}`ilan-gold` diff --git a/docs/release-notes/1580.bugfix.md b/docs/release-notes/1580.bugfix.md new file mode 100644 index 000000000..96fcede1f --- /dev/null +++ b/docs/release-notes/1580.bugfix.md @@ -0,0 +1 @@ +Upper bound dask on account of {issue}`1579` {user}`ilan-gold` diff --git a/docs/release-notes/1586.bugfix.md b/docs/release-notes/1586.bugfix.md new file mode 100644 index 000000000..d09374415 --- /dev/null +++ b/docs/release-notes/1586.bugfix.md @@ -0,0 +1 @@ +Ensure setting {attr}`pandas.DataFrame.index` on a view of a {class}`~anndata.AnnData` instantiates the {class}`~pandas.DataFrame` from the view {user}`ilan-gold` diff --git a/docs/release-notes/1589.bugfix.md b/docs/release-notes/1589.bugfix.md new file mode 100644 index 000000000..9413aaa1e --- /dev/null +++ b/docs/release-notes/1589.bugfix.md @@ -0,0 +1 @@ +Disallow using {class}`~pandas.DataFrame`s with multi-index columns {user}`ilan-gold` diff --git a/docs/release-notes/index.md b/docs/release-notes/index.md index c0a802f23..60b7a352b 100644 --- a/docs/release-notes/index.md +++ b/docs/release-notes/index.md @@ -1,106 +1,4 @@ # Release notes -(v0.11)= -## Version 0.11 - -```{include} /release-notes/0.11.0.md -``` - -(v0.10)= -## Version 0.10 - -```{include} /release-notes/0.10.9.md -``` - -```{include} /release-notes/0.10.8.md -``` - -```{include} /release-notes/0.10.7.md -``` - -```{include} /release-notes/0.10.6.md -``` - -```{include} /release-notes/0.10.5.md -``` - -```{include} /release-notes/0.10.4.md -``` - -```{include} /release-notes/0.10.3.md -``` - -```{include} /release-notes/0.10.2.md -``` - -```{include} /release-notes/0.10.1.md -``` - -```{include} /release-notes/0.10.0.md -``` - -(v0.9)= -## Version 0.9 - -```{include} /release-notes/0.9.2.md -``` - -```{include} /release-notes/0.9.1.md -``` - -```{include} /release-notes/0.9.0.md -``` - -(v0.8)= -## Version 0.8 - -```{include} /release-notes/0.8.0.md -``` - -(v0.7)= -## Version 0.7 - -```{include} /release-notes/0.7.8.md -``` - -```{include} /release-notes/0.7.7.md -``` - -```{include} /release-notes/0.7.6.md -``` - -```{include} /release-notes/0.7.5.md -``` - -```{include} /release-notes/0.7.4.md -``` - -```{include} /release-notes/0.7.3.md -``` - -```{include} /release-notes/0.7.2.md -``` - -```{include} /release-notes/0.7.0.md -``` - -(v0.6)= -## Version 0.6 - -```{include} /release-notes/0.6.x.md -``` - -```{include} /release-notes/0.6.0.md -``` - -(v0.5)= -## Version 0.5 - -```{include} /release-notes/0.5.0.md -``` - -(v0.4)= -## Version 0.4 - -```{include} /release-notes/0.4.0.md +```{release-notes} . ``` diff --git a/hatch.toml b/hatch.toml new file mode 100644 index 000000000..ad888c3bb --- /dev/null +++ b/hatch.toml @@ -0,0 +1,11 @@ +[envs.default] +installer = "uv" +features = ["dev"] + +[envs.docs] +features = ["doc"] +dependencies = ["setuptools"] # https://bitbucket.org/pybtex-devs/pybtex/issues/169 + +[envs.docs.scripts] +build = "sphinx-build -M html docs docs/_build -W --keep-going {args}" +clean = "git clean -fX -- docs" diff --git a/pyproject.toml b/pyproject.toml index a36f06af6..2931d7ff6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,6 +64,7 @@ dev = [ "setuptools_scm", # test speedups "pytest-xdist", + "towncrier>=24.8.0", ] doc = [ "sphinx>=7.4.6", @@ -147,6 +148,7 @@ python_files = "test_*.py" testpaths = [ "anndata", # docstrings (module name due to --pyargs) "./tests", # unit tests + "./ci/scripts", # CI script tests "./docs/concatenation.rst", # further doctests ] # For some reason this effects how logging is shown when tests are run @@ -192,3 +194,19 @@ strict = true [tool.codespell] skip = ".git,*.pdf,*.svg" ignore-words-list = "theis,coo,homogenous" + +[tool.towncrier] +package = "anndata" +directory = "docs/release-notes" +filename = "docs/release-notes/{version}.md" +single_file = false +package_dir = "src" +issue_format = "{{pr}}`{issue}`" +title_format = "(v{version})=\n### {version} {{small}}`{project_date}`" +[tool.towncrier.fragment.bugfix] +[tool.towncrier.fragment.doc] +[tool.towncrier.fragment.feature] +[tool.towncrier.fragment.misc] + +[tool.towncrier.fragment.performance] +name = "Performance"