From 32416db5a78d436bf1ede28a9faab2f01f33681d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Fri, 29 Aug 2025 19:58:41 +0200 Subject: [PATCH 01/14] Draft HTML docs with Sphinx, Furo & myst parser --- docs/command_line.md | 6 +-- docs/conf.py | 63 ++++++++++++++++++++++ docs/index.md | 36 +++++++++++++ docs/{user_guide.md => introduction.md} | 18 ++++--- docs/release_notes/index.md | 9 ++++ docs/release_notes/v0.2.0.md | 2 +- docs/templates/sidebar/external-links.html | 6 +++ docs/typing_syntax.md | 14 ++--- pyproject.toml | 17 ++++-- src/docstub/__init__.py | 6 +-- 10 files changed, 151 insertions(+), 26 deletions(-) create mode 100644 docs/conf.py create mode 100644 docs/index.md rename docs/{user_guide.md => introduction.md} (94%) create mode 100644 docs/release_notes/index.md create mode 100644 docs/templates/sidebar/external-links.html diff --git a/docs/command_line.md b/docs/command_line.md index 7f73047..e2a0982 100644 --- a/docs/command_line.md +++ b/docs/command_line.md @@ -5,7 +5,7 @@ -```plain +```none Usage: docstub [OPTIONS] COMMAND [ARGS]... Generate Python stub files from docstrings. @@ -27,7 +27,7 @@ Commands: -```plain +```none Usage: docstub run [OPTIONS] PACKAGE_PATH Generate Python stub files. @@ -66,7 +66,7 @@ Options: -```plain +```none Usage: docstub clean [OPTIONS] Clean the cache. diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..9f1cb47 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,63 @@ +# Configuration file for the Sphinx documentation builder. +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +from datetime import date + +# -- Project information ------------------------------------------------------ + +project = "docstub" +copyright = f"{date.today().year}, docstub team" + +templates_path = ["templates"] + +# -- General configuration ---------------------------------------------------- + +extensions = [ + "numpydoc", + "myst_parser", + "sphinx_copybutton", +] + + +# -- HTML output -------------------------------------------------------------- + +html_theme = "furo" + +html_title = "docstub docs" + +html_theme_options = { + "footer_icons": [ + { + "name": "docstub's sources on GitHub", + "url": "https://github.com/scientific-python/docstub", + "html": """ + + + + """, + "class": "", + }, + ], +} + +html_sidebars = { + "**": [ + "sidebar/brand.html", + "sidebar/search.html", + "sidebar/scroll-start.html", + "sidebar/navigation.html", + "sidebar/external-links.html", + "sidebar/ethical-ads.html", + "sidebar/scroll-end.html", + ] +} + + +# -- MyST parser extension ---------------------------------------------------- + +myst_enable_extensions = [ + # Enable fieldlist to allow for Field Lists like in rST (e.g., :orphan:) + "fieldlist", + # Enable fencing directives with `:::` + "colon_fence", +] diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..3e9296e --- /dev/null +++ b/docs/index.md @@ -0,0 +1,36 @@ +# docstub documentation + +:::{admonition} In early development! +:class: tip + +Expect bugs, missing features, and incomplete documentation. +Docstub is still evaluating which features it needs to support as the community gives feedback. +Several features are experimental and included to make adoption of docstub easier. +Long-term, some of these might be discouraged or removed as docstub matures. +::: + +docstub is a command-line tool to generate [Python stub files](https://typing.python.org/en/latest/guides/writing_stubs.html) from type descriptions found in [numpydoc](https://numpydoc.readthedocs.io)-style docstrings. + +Many packages in the scientific Python ecosystem already describe expected parameter and return types in their docstrings. +Docstub aims to take advantage of these and help with the adoption of type annotations. +It does so by supporting widely used readable conventions such as `array of dtype` or `iterable of int(s)` which it translates into valid type annotations. + + +:::{toctree} +:caption: User guides +:maxdepth: 1 +:hidden: + +introduction +::: + +:::{toctree} +:caption: Reference +:maxdepth: 1 +:hidden: + +command_line +configuration +typing_syntax +release_notes/index +::: diff --git a/docs/user_guide.md b/docs/introduction.md similarity index 94% rename from docs/user_guide.md rename to docs/introduction.md index e6d72e7..ec15a03 100644 --- a/docs/user_guide.md +++ b/docs/introduction.md @@ -1,11 +1,13 @@ -# User guide +# Introduction -> [!NOTE] -> **In early development!** -> Expect bugs, missing features, and incomplete documentation. -> Docstub is still evaluating which features it needs to support as the community gives feedback. -> Several features are experimental and included to make adoption of docstub easier. -> Long-term, some of these might be discouraged or removed as docstub matures. +:::{admonition} In early development! +:class: tip + +Expect bugs, missing features, and incomplete documentation. +Docstub is still evaluating which features it needs to support as the community gives feedback. +Several features are experimental and included to make adoption of docstub easier. +Long-term, some of these might be discouraged or removed as docstub matures. +::: ## Installation @@ -19,7 +21,7 @@ pip install docstub In case you want to check out an unreleased version you can install directly from the repository with: ```shell -pip install docstub @ git+https://github.com/scientific-python/docstub' +pip install 'docstub @ git+https://github.com/scientific-python/docstub' ``` To pin to a specific commit you can append `@COMMIT_SHA` to the repository URL above. diff --git a/docs/release_notes/index.md b/docs/release_notes/index.md new file mode 100644 index 0000000..16997f0 --- /dev/null +++ b/docs/release_notes/index.md @@ -0,0 +1,9 @@ +# Release notes + +:::{toctree} +:maxdepth: 1 + +v0.4.0 +v0.3.0 +v0.2.0 +::: diff --git a/docs/release_notes/v0.2.0.md b/docs/release_notes/v0.2.0.md index 43f0859..8a43202 100644 --- a/docs/release_notes/v0.2.0.md +++ b/docs/release_notes/v0.2.0.md @@ -1,4 +1,4 @@ -## docstub 0.2.0 +# docstub 0.2.0 A first prototype of the tool with the following features: diff --git a/docs/templates/sidebar/external-links.html b/docs/templates/sidebar/external-links.html new file mode 100644 index 0000000..5e86eb6 --- /dev/null +++ b/docs/templates/sidebar/external-links.html @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/docs/typing_syntax.md b/docs/typing_syntax.md index 4849ccb..bd071dc 100644 --- a/docs/typing_syntax.md +++ b/docs/typing_syntax.md @@ -1,11 +1,13 @@ # Typing syntax in docstrings -> [!NOTE] -> **In early development!** -> Expect bugs, missing features, and incomplete documentation. -> Docstub is still evaluating which features it needs to support as the community gives feedback. -> Several features are experimental and included to make adoption of docstub easier. -> Long-term, some of these might be discouraged or removed as docstub matures. +:::{admonition} In early development! +:class: tip + +Expect bugs, missing features, and incomplete documentation. +Docstub is still evaluating which features it needs to support as the community gives feedback. +Several features are experimental and included to make adoption of docstub easier. +Long-term, some of these might be discouraged or removed as docstub matures. +::: Docstub defines its own [grammar](../src/docstub/doctype.lark) to parse and transform type information in docstrings (doctypes) into valid Python type expressions. This grammar fully supports [Python's conventional typing syntax](https://typing.python.org/en/latest/index.html). diff --git a/pyproject.toml b/pyproject.toml index 18be2a1..723b95a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,16 +48,25 @@ docstub = "docstub.__main__:cli" [dependency-groups] -dev = [ - "pre-commit >=4.3.0", - "ipython", -] test = [ "pytest >=8.4.1", "pytest-cov >= 5.0.0", "mypy >=1.17.0", "basedpyright >=1.31", ] +docs = [ + "sphinx", + "furo", + "numpydoc", + "myst-parser", + "sphinx-copybutton", +] +dev = [ + {include-group = "test"}, + {include-group = "docs"}, + "pre-commit >=4.3.0", + "ipython", +] [tool.setuptools_scm] diff --git a/src/docstub/__init__.py b/src/docstub/__init__.py index 17da713..85f19b2 100644 --- a/src/docstub/__init__.py +++ b/src/docstub/__init__.py @@ -1,8 +1,6 @@ -""" -Copyright (c) 2024 Lars Grüter. All rights reserved. +# Copyright (c) 2024 Lars Grüter. All rights reserved. -docstub: Generate Python stub files (PYI) from docstrings -""" +"""Generate Python stub files (PYI) from docstrings.""" from ._version import __version__ From 2d322de807d735dca2a9699887288373f88ba375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Sun, 19 Oct 2025 11:27:28 +0200 Subject: [PATCH 02/14] Customize Furo and sphinx configuration --- docs/conf.py | 62 ++++++++++++---------- docs/static/furo_overrides.css | 9 ++++ docs/templates/external-links.html | 8 +++ docs/templates/sidebar/external-links.html | 6 --- 4 files changed, 52 insertions(+), 33 deletions(-) create mode 100644 docs/static/furo_overrides.css create mode 100644 docs/templates/external-links.html delete mode 100644 docs/templates/sidebar/external-links.html diff --git a/docs/conf.py b/docs/conf.py index 9f1cb47..a3f8010 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -6,16 +6,30 @@ # -- Project information ------------------------------------------------------ project = "docstub" -copyright = f"{date.today().year}, docstub team" +copyright = f"{date.today().year}, docstub contributors" templates_path = ["templates"] -# -- General configuration ---------------------------------------------------- + +# -- Extension configuration -------------------------------------------------- extensions = [ - "numpydoc", - "myst_parser", + "sphinx.ext.intersphinx", "sphinx_copybutton", + # https://numpydoc.readthedocs.io/ + "myst_parser", +] + +intersphinx_mapping = { + "python": ("https://docs.python.org/3/", None), + "typing": ("https://typing.python.org/en/latest/", None), +} + +myst_enable_extensions = [ + # Enable fieldlist to allow for Field Lists like in rST (e.g., :orphan:) + "fieldlist", + # Enable fencing directives with `:::` + "colon_fence", ] @@ -23,21 +37,24 @@ html_theme = "furo" +html_static_path = ["static"] + +html_css_files = ["furo_overrides.css"] + html_title = "docstub docs" html_theme_options = { - "footer_icons": [ - { - "name": "docstub's sources on GitHub", - "url": "https://github.com/scientific-python/docstub", - "html": """ - - - - """, - "class": "", - }, - ], + "light_css_variables": { + # Make font less harsh on light theme + "color-foreground-primary": "#363636", + "color-announcement-background": "var(--color-admonition-title-background--important)", + "color-announcement-text": "var(--color-content-foreground)", + "admonition-font-size": "var(--font-size--normal)", + }, + "dark_css_variables": { + "color-announcement-background": "var(--color-admonition-title-background--important)", + }, + "announcement": "🧪 In early development! API and behavior may break between releases.", } html_sidebars = { @@ -46,18 +63,9 @@ "sidebar/search.html", "sidebar/scroll-start.html", "sidebar/navigation.html", - "sidebar/external-links.html", + "external-links.html", "sidebar/ethical-ads.html", "sidebar/scroll-end.html", + "sidebar/variant-selector.html", ] } - - -# -- MyST parser extension ---------------------------------------------------- - -myst_enable_extensions = [ - # Enable fieldlist to allow for Field Lists like in rST (e.g., :orphan:) - "fieldlist", - # Enable fencing directives with `:::` - "colon_fence", -] diff --git a/docs/static/furo_overrides.css b/docs/static/furo_overrides.css new file mode 100644 index 0000000..0efb107 --- /dev/null +++ b/docs/static/furo_overrides.css @@ -0,0 +1,9 @@ +html { + scroll-behavior: auto; +} + +/* Left-align tables instead of centering them */ +article .align-default { + margin-left: unset; + margin-right: unset; +} diff --git a/docs/templates/external-links.html b/docs/templates/external-links.html new file mode 100644 index 0000000..54dd0ea --- /dev/null +++ b/docs/templates/external-links.html @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/docs/templates/sidebar/external-links.html b/docs/templates/sidebar/external-links.html deleted file mode 100644 index 5e86eb6..0000000 --- a/docs/templates/sidebar/external-links.html +++ /dev/null @@ -1,6 +0,0 @@ - \ No newline at end of file From c5c129da03c72ec1c3cd9784b47884d29c1dc0be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Sun, 19 Oct 2025 11:28:31 +0200 Subject: [PATCH 03/14] Tweak command line and configuration references --- docs/command_line.md | 11 +++++++---- docs/configuration.md | 28 ++++++++++++++++------------ 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/docs/command_line.md b/docs/command_line.md index e2a0982..9c18aa4 100644 --- a/docs/command_line.md +++ b/docs/command_line.md @@ -1,6 +1,9 @@ -# Command line reference +# Command line -## Command `docstub` +The reference for docstub's command line interface. +It uses [Click](https://click.palletsprojects.com/en/stable/), so [shell completion](https://click.palletsprojects.com/en/stable/shell-completion/) can be enabled. + +## `docstub` @@ -22,7 +25,7 @@ Commands: -## Command `docstub run` +## `docstub run` @@ -61,7 +64,7 @@ Options: -## Command `docstub clean` +## `docstub clean` diff --git a/docs/configuration.md b/docs/configuration.md index a0a9024..d5ac9fb 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,15 +1,17 @@ -# Configuration reference +# Configuration files -Docstub will automatically look for configuration in files named +Docstub will automatically look for configuration files in the current directory. +These files must be named -- `pyproject.toml`, and +- `pyproject.toml` - `docstub.toml` -in the current working directory. -If config files are explicitly passed to the command line interface via the `--config` option, docstub won't look implicitly look for files in the current directory. -Multiple configuration files can be used, whose content will be merged. +Alternatively, config files can also be passed in the command line with the `--config` option. +In this case, docstub will not look for configuration files in the current directory. +When multiple configuration files are passed explicitly, their content is merged. -Out of the box, docstub makes use of an internal configuration file [`numpy_config.toml`](../src/docstub/numpy_config.toml) which provides defaults to use NumPy types. +Out of the box, docstub makes use of an internal configuration files are always loaded. +One such file is [`numpy_config.toml`](../src/docstub/numpy_config.toml) which provides defaults for NumPy types. ## Configuration fields in `[tool.docstub]` @@ -19,7 +21,7 @@ All configuration must be declared inside a `[tool.docstub]` table. ### `ignore_files` -- [TOML type](https://toml.io/en/latest): array of string(s) +[TOML type](https://toml.io/en/latest): array of string(s) Ignore files and directories matching these [glob-style patterns](https://docs.python.org/3/library/glob.html#glob.translate). Patterns that don't start with "/" are interpreted as relative to the @@ -39,7 +41,7 @@ ignore_files = [ ### `types` -- [TOML type](https://toml.io/en/latest): table, mapping string to string +[TOML type](https://toml.io/en/latest): table, mapping string to string Types and their external modules to use in docstrings. Docstub can't yet automatically discover where to import types from other packages from. @@ -60,7 +62,7 @@ NDArray = "numpy.typing" ### `type_prefixes` -- [TOML type](https://toml.io/en/latest): table, mapping string to string +[TOML type](https://toml.io/en/latest): table, mapping string to string Prefixes for external modules to match types in docstrings. Docstub can't yet automatically discover where to import types from other packages from. @@ -81,7 +83,7 @@ plt = "matplotlib.pyplot ### `type_nicknames` -- [TOML type](https://toml.io/en/latest): table, mapping string to string +[TOML type](https://toml.io/en/latest): table, mapping string to string Nicknames for types that can be used in docstrings to describe valid Python types or annotations. @@ -90,6 +92,8 @@ Example: ```toml [tool.docstub.type_nicknames] func = "Callable" +buffer = "collections.abc.Buffer" ``` -- Will map `func` to the `Callable` type from the `typing` module. +- Will map `func` to the `Callable`. +- Will map `buffer` to `collections.abc.Buffer`. From 8d4e442f1292e7b63e5cddfacb6c54f2d93d91ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Sun, 19 Oct 2025 11:29:07 +0200 Subject: [PATCH 04/14] Add a minimal glossary --- docs/configuration.md | 1 + docs/glossary.md | 24 ++++++++++++++++++++++++ docs/index.md | 1 + 3 files changed, 26 insertions(+) create mode 100644 docs/glossary.md diff --git a/docs/configuration.md b/docs/configuration.md index d5ac9fb..03b5409 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -60,6 +60,7 @@ NDArray = "numpy.typing" - Will allow using `NDarray` in docstrings and will use `from numpy.typing import NDArray` to import the type. +(config-type-prefixes)= ### `type_prefixes` [TOML type](https://toml.io/en/latest): table, mapping string to string diff --git a/docs/glossary.md b/docs/glossary.md new file mode 100644 index 0000000..2e31647 --- /dev/null +++ b/docs/glossary.md @@ -0,0 +1,24 @@ +# Glossary + +This section defines central terms used in this documentation. + +:::{glossary} +:sorted: + +doctype + A type description of a type in a docstring, such as of a parameter, return value, or attribute. + Any {term}`annotation expression` is valid as a doctype, but doctypes support an [extended syntax](typing_syntax.md) with natural language variants. + +type name + The name of a single (atomic) type. + A type name can include a {term}`type prefix`. + An {term}`annotation expression` can contain multiple typen names. + For example, the annotation expression `collections.abc.Iterable[int or float]` consists of the three names `collections.abc.Iterable`, `int` and `float`. + +type prefix + A dot-delimited prefix that is part of a {term}`type name`. + The prefix can describe the full path of a type or consist of an alias. + For example, `collections.abc.Iterable` has the type prefix `collections.abc`. + `np.int` as the prefix `np` which may be an alias for `numpy`. + [Type prefixes can be defined in the configuration](#config-type-prefixes) or are inferred by docstub from import statements it can see. +::: \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 3e9296e..37c80b8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -32,5 +32,6 @@ introduction command_line configuration typing_syntax +glossary release_notes/index ::: From 47107fd2e4f2cf6d523f7a09cabb1e5e8b2b3186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Sun, 19 Oct 2025 11:30:11 +0200 Subject: [PATCH 05/14] Tweak introduction --- docs/introduction.md | 58 ++++++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/docs/introduction.md b/docs/introduction.md index ec15a03..21b5bdb 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -1,15 +1,5 @@ # Introduction -:::{admonition} In early development! -:class: tip - -Expect bugs, missing features, and incomplete documentation. -Docstub is still evaluating which features it needs to support as the community gives feedback. -Several features are experimental and included to make adoption of docstub easier. -Long-term, some of these might be discouraged or removed as docstub matures. -::: - - ## Installation Docstub is available as a [package on PyPI](https://pypi.org/project/docstub/) and can be installed from there with your favorite package manager. E.g.: @@ -27,16 +17,14 @@ pip install 'docstub @ git+https://github.com/scientific-python/docstub' To pin to a specific commit you can append `@COMMIT_SHA` to the repository URL above. -## Getting started +## First example -Consider a simple example with the following documented function +Consider a simple file `example.py` with the following documented function -```python -# example.py - +```{code-block} python def example_metric(image, *, mask=None, sigma=1.0, method='standard'): """Pretend to calculate a local metric between two images. @@ -67,12 +55,12 @@ Feeding this input to docstub with docstub run example.py ``` -will create `example.pyi` in the same directory +will create the [stub file](https://typing.python.org/en/latest/spec/distributing.html#stub-files) `example.pyi` in the same directory -```python +```{code-block} python # File generated with docstub from collections.abc import Iterable @@ -95,15 +83,15 @@ def example_metric( There are several interesting things to note here: - Many existing conventions that the scientific Python ecosystem uses, will work out of the box. - In this case, docstub knew how to translate `array-like`, `array of dtype uint8` into a valid Python type for the stub file. - In a similar manner, `or` can be used as a "natural language" alternative to `|` to form unions. - You can find more details in [Typing syntax in docstrings](typing_syntax.md). + In this case, docstub knew how to translate `array-like`, `array of dtype uint8` into a valid {term}`annotation expression` for the stub file. + In a similar manner, `or` was used as a "natural language" alternative to `|` to form unions. + This alternative extended syntax is described in [](typing_syntax.md). - Optional arguments that default to `None` are recognized and a `| None` is appended automatically. The `optional` or `default = ...` part don't influence the annotation. -- Referencing the `float` and `Iterable` types worked out of the box. - All builtin types as well as types from the standard libraries `typing` and `collections.abc` module can be used like this. +- Referencing the `float` and `Iterable` worked out of the box. + All [built-in types](https://docs.python.org/3/library/stdtypes.html#built-in-types) as well as types from the standard library's `typing` and `collections.abc` module can be used like this. Necessary imports will be added automatically to the stub file. @@ -137,16 +125,17 @@ ski = "skimage" which will enable any type that is prefixed with `ski.` or `sklearn.tree.`, e.g. `ski.transform.AffineTransform` or `sklearn.tree.DecisionTreeClassifier`. -> [!IMPORTANT] -> Docstub doesn't check that types actually exist or if a symbol is a valid type. -> We always recommend validating the generated stubs with a full type checker! - -> [!TIP] -> Docstub currently collects types statically. -> So it won't see compiled modules and won't be able to generate stubs for them. -> For now, you can add stubs for compiled modules yourself and docstub will include these in the generated output. -> Support for dynamic type collection is on the roadmap. +:::{important} +Docstub doesn't check that types actually exist or if a symbol is a valid type. +We always recommend validating the generated stubs with a full type checker! +::: +:::{tip} +Docstub currently collects types statically. +So it won't see compiled modules and won't be able to generate stubs for them. +For now, you can add stubs for compiled modules yourself and docstub will include these in the generated output. +Support for dynamic type collection is on the roadmap. +::: The codebase docstub is running on may already use existing conventions to refer to common types (or you may want to do so). Docstub refers to these alternatives as "type nicknames". @@ -169,9 +158,10 @@ Two command line options can help addressing these errors gradually: This way you can adjust the upper bound of allowed errors as they are addressed. Useful, if you are running in docstub in continuous integration. -> [!TIP] -> If you are trying out docstub and have feedback or problems, we'd love to hear from you! -> Feel welcome to [open an issue](https://github.com/scientific-python/docstub/issues/new/choose). 🚀 +:::{tip} +If you are trying out docstub and have feedback or problems, we'd love to hear from you! +Feel welcome to [open an issue](https://github.com/scientific-python/docstub/issues/new/choose). 🚀 +::: ## Dealing with typing problems From 39400f7d93f463932191a26818ca8fd53d7ca6b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Sun, 19 Oct 2025 11:30:35 +0200 Subject: [PATCH 06/14] Update typing_syntax --- docs/typing_syntax.md | 54 +++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/docs/typing_syntax.md b/docs/typing_syntax.md index bd071dc..2a54612 100644 --- a/docs/typing_syntax.md +++ b/docs/typing_syntax.md @@ -1,21 +1,12 @@ # Typing syntax in docstrings -:::{admonition} In early development! -:class: tip - -Expect bugs, missing features, and incomplete documentation. -Docstub is still evaluating which features it needs to support as the community gives feedback. -Several features are experimental and included to make adoption of docstub easier. -Long-term, some of these might be discouraged or removed as docstub matures. -::: - Docstub defines its own [grammar](../src/docstub/doctype.lark) to parse and transform type information in docstrings (doctypes) into valid Python type expressions. This grammar fully supports [Python's conventional typing syntax](https://typing.python.org/en/latest/index.html). So any type expression that is valid in Python, can be used in a docstrings as is. In addition, docstub extends this syntax with several "natural language" expressions that are commonly used in the scientific Python ecosystem. Docstrings should follow a form that is inspired by the NumPyDoc style: -``` +```none Section name ------------ name : doctype, optional_info @@ -23,10 +14,10 @@ name : doctype, optional_info ``` - `name` might be the name of a parameter, attribute or similar. -- `doctype` the actual type information that will be transformed into a Python type. -- `optional_info` is optional and captures anything after the first comma (that is not inside a type expression). +- `doctype` contains the actual type information that will be transformed into a Python type (see also {term}`doctype`). +- `optional_info` is optional and captures anything after the first (top-level) comma. It is useful to provide additional information for readers. - Its presence and content doesn't currently affect the resulting type annotation. + Its presence and content doesn't currently affect the generated {term}`annotation expression`. ## Unions @@ -67,10 +58,11 @@ and **mappings** exist. | `dict of {str: int}` | `dict[str, int]` | -> [!TIP] -> While it is possible to nest these variants repeatedly, it is discouraged to do so to keep type descriptions readable. -> For complex annotations with nested containers, consider using Python's conventional syntax. -> In the future, docstub may warn against or disallow nesting these natural language variants. +:::{tip} +While it is possible to nest these variants repeatedly, it decreases the readability. +For complex nested annotations with nested containers, consider using Python's conventional syntax. +In the future, docstub may warn against or disallow nesting these natural language variants. +::: ## Shape and dtype syntax for arrays @@ -86,10 +78,11 @@ This expression allows adding shape and datatype information for data structures | `array-like of DTYPE` | `ArrayLike[DTYPE]` | | `array_like of dtype DTYPE` | `ArrayLike[DTYPE]` | -> [!NOTE] -> Noting the **shape** of an array in the docstring is supported. -> However, Python's typing system is not yet able to express this information. -> It is therefore not included in the resulting type annotation. +:::{note} +Noting the **shape** of an array in the docstring is supported. +However, Python's typing system is not yet able to express this information. +It is therefore not included in the resulting type annotation. +::: | Docstring type | Python type annotation | |--------------------------|------------------------| @@ -109,16 +102,17 @@ Instead of using [`typing.Literal`](https://docs.python.org/3/library/typing.htm | `{-1, 0, 3, True, False}` | `Literal[-1, 0, 3, True, False]` | | `{"red", "blue", None}` | `Literal["red", "blue", None]` | -> [!TIP] -> Enclosing a single value `{X}` is currently allowed but discouraged. -> Instead consider the more explicit `Literal[X]`. - -> [!WARNING] -> Python's `typing.Literal` only supports a restricted set of parameters. -> E.g., `float` literals are not yet supported by the type system but are allowed by docstub. -> Addressing this use case is on the roadmap. -> See [issue 47](https://github.com/scientific-python/docstub/issues/47) for more details. +:::{tip} +Enclosing a single value `{X}` is allowed. +However, `Literal[X]` is more explicit. +::: +:::{warning} +Python's `typing.Literal` only supports a restricted set of parameters. +E.g., `float` literals are not yet supported by the type system but are allowed by docstub. +Addressing this use case is on the roadmap. +See [issue 47](https://github.com/scientific-python/docstub/issues/47) for more details. +::: ## reStructuredText role From 53cfe233248b8ba5a74e91c029c0c365ed1cdd86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Sun, 19 Oct 2025 11:31:32 +0200 Subject: [PATCH 07/14] Update README and doc landing page --- README.md | 11 +++++------ docs/index.md | 16 +++++++--------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index d2b736e..7675f94 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,15 @@ > [!NOTE] > **In early development!** -> Expect bugs, missing features, and incomplete documentation. -> Docstub is still evaluating which features it needs to support as the community gives feedback. -> Several features are experimental and included to make adoption of docstub easier. -> Long-term, some of these might be discouraged or removed as docstub matures. +> Docstub is not feature-complete or thoroughly tested yet. +> Its behavior, configuration or command line interface may change significantly between releases. -docstub is a command-line tool to generate [Python stub files](https://typing.python.org/en/latest/guides/writing_stubs.html) (i.e., PYI files) from type descriptions found in [numpydoc](https://numpydoc.readthedocs.io)-style docstrings. +docstub is a command-line tool to generate [Python stub files](https://typing.python.org/en/latest/guides/writing_stubs.html). +It extracts necessary type information from [NumPyDoc style](https://numpydoc.readthedocs.io) docstrings. Many packages in the scientific Python ecosystem already describe expected parameter and return types in their docstrings. Docstub aims to take advantage of these and help with the adoption of type annotations. -It does so by supporting widely used readable conventions such as `array of dtype` or `iterable of int(s)` which it translates into valid type annotations. +It does so by supporting widely used readable conventions such as `array of dtype` or `iterable of int(s)` which are translated into valid type annotations. ## Installation & getting started diff --git a/docs/index.md b/docs/index.md index 37c80b8..bf54a8f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,25 +1,24 @@ # docstub documentation :::{admonition} In early development! -:class: tip +:class: important -Expect bugs, missing features, and incomplete documentation. -Docstub is still evaluating which features it needs to support as the community gives feedback. -Several features are experimental and included to make adoption of docstub easier. -Long-term, some of these might be discouraged or removed as docstub matures. +Docstub is not feature-complete or thoroughly tested yet. +Its behavior, configuration or command line interface may change significantly between releases. ::: -docstub is a command-line tool to generate [Python stub files](https://typing.python.org/en/latest/guides/writing_stubs.html) from type descriptions found in [numpydoc](https://numpydoc.readthedocs.io)-style docstrings. +docstub is a command-line tool to generate [Python stub files](https://typing.python.org/en/latest/spec/distributing.html#stub-files). +It extracts necessary type information from [NumPyDoc style](https://numpydoc.readthedocs.io) docstrings. Many packages in the scientific Python ecosystem already describe expected parameter and return types in their docstrings. Docstub aims to take advantage of these and help with the adoption of type annotations. -It does so by supporting widely used readable conventions such as `array of dtype` or `iterable of int(s)` which it translates into valid type annotations. +It does so by supporting widely used readable conventions such as `array of dtype` or `iterable of int(s)` which are translated into valid type annotations. +--- :::{toctree} :caption: User guides :maxdepth: 1 -:hidden: introduction ::: @@ -27,7 +26,6 @@ introduction :::{toctree} :caption: Reference :maxdepth: 1 -:hidden: command_line configuration From fb65601d9c7359651f82d915a2e1b66bd76f92e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Sun, 19 Oct 2025 11:32:10 +0200 Subject: [PATCH 08/14] Add small off-topic TODO to GroupedErrorReporter --- src/docstub/_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/docstub/_utils.py b/src/docstub/_utils.py index 781bba1..5badda9 100644 --- a/src/docstub/_utils.py +++ b/src/docstub/_utils.py @@ -320,6 +320,7 @@ def key(message): message["column"] or -1, ) + # TODO use itertools.groupby here? groups = {} for message in sorted(self._messages, key=key): group_name = (message["short"], message["details"]) From e76f055fc38a952e06f911b6730d2138e4b99e0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Sun, 19 Oct 2025 11:32:47 +0200 Subject: [PATCH 09/14] Account for updates in test_docs.py --- tests/test_docs.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/test_docs.py b/tests/test_docs.py index 4b65884..1c6054e 100644 --- a/tests/test_docs.py +++ b/tests/test_docs.py @@ -12,16 +12,16 @@ PROJECT_ROOT = Path(__file__).parent.parent -def test_user_guide_example(tmp_path): - # Load user guide - md_file = PROJECT_ROOT / "docs/user_guide.md" +def test_introduction_example(tmp_path): + # Load introduction + md_file = PROJECT_ROOT / "docs/introduction.md" with md_file.open("r") as io: md_content = io.read() # Extract code block for example.py regex_py = ( r"" - r"\n+```python(.*)```\n+" + r"\n+```{code-block} python(.*)```\n+" r"" ) matches_py = re.findall(regex_py, md_content, flags=re.DOTALL) @@ -35,7 +35,7 @@ def test_user_guide_example(tmp_path): runner = CliRunner() run_result = runner.invoke(_cli.run, [str(py_file)]) # noqa: F841 - # Load created PYI file, this is what we expect to find in the user guide's + # Load created PYI file, this is what we expect to find in the introduction's # code block for example.pyi pyi_file = py_file.with_suffix(".pyi") assert pyi_file.is_file() @@ -45,7 +45,7 @@ def test_user_guide_example(tmp_path): # Extract code block for example.pyi from guide regex_pyi = ( r"" - r"\n+```python(.*)```\n+" + r"\n+```{code-block} python(.*)```\n+" r"" ) matches_pyi = re.findall(regex_pyi, md_content, flags=re.DOTALL) @@ -62,7 +62,7 @@ def test_user_guide_example(tmp_path): def test_command_line_reference(command, name): ctx = click.Context(command, info_name=name) expected_help = f""" -```plain +```none {command.get_help(ctx)} ``` """.strip() From 89828541ea6a988ed870b750b676a099a0803760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Sun, 19 Oct 2025 11:33:42 +0200 Subject: [PATCH 10/14] Add small test for type nicknames corresponding to the example in configuration.md. --- tests/test_analysis.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_analysis.py b/tests/test_analysis.py index 1050178..b816265 100644 --- a/tests/test_analysis.py +++ b/tests/test_analysis.py @@ -320,6 +320,19 @@ def test_scoped_type_prefix(self, module_factory): assert type_name == "cal.January" assert py_import == PyImport(implicit="sub.module:cal") + def test_nicknames(self, caplog): + types = { + "Buffer": PyImport(from_="collections.abc", import_="Buffer"), + } + type_nicknames = { + "buffer": "collections.abc.Buffer", + } + matcher = TypeMatcher(types=types, type_nicknames=type_nicknames) + + type_name, py_import = matcher.match("buffer") + assert type_name == "Buffer" + assert py_import == PyImport(from_="collections.abc", import_="Buffer") + def test_nested_nicknames(self, caplog): types = { "Foo": PyImport(implicit="Foo"), From 07fd9c82257f7bfaba7841e3a22fedfebf27eedd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Sun, 19 Oct 2025 11:37:58 +0200 Subject: [PATCH 11/14] Add small off-topic TODO to ReportHandler --- src/docstub/_report.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/docstub/_report.py b/src/docstub/_report.py index e1e4dbe..1622f8d 100644 --- a/src/docstub/_report.py +++ b/src/docstub/_report.py @@ -304,6 +304,7 @@ def emit_grouped(self): will be grouped together. """ # Group by report + # TODO use itertools.groupby here? groups = {} for record in self._records: group_id = record.getMessage(), getattr(record, "details", "") From 22010ac5c34c2746386d3801e5b34874843e2715 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Sun, 19 Oct 2025 18:49:18 +0200 Subject: [PATCH 12/14] Use implicit heading anchors --- docs/conf.py | 2 ++ docs/configuration.md | 1 - docs/glossary.md | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index a3f8010..25d7b40 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -32,6 +32,8 @@ "colon_fence", ] +myst_heading_anchors = 3 + # -- HTML output -------------------------------------------------------------- diff --git a/docs/configuration.md b/docs/configuration.md index 03b5409..d5ac9fb 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -60,7 +60,6 @@ NDArray = "numpy.typing" - Will allow using `NDarray` in docstrings and will use `from numpy.typing import NDArray` to import the type. -(config-type-prefixes)= ### `type_prefixes` [TOML type](https://toml.io/en/latest): table, mapping string to string diff --git a/docs/glossary.md b/docs/glossary.md index 2e31647..02178aa 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -19,6 +19,6 @@ type prefix A dot-delimited prefix that is part of a {term}`type name`. The prefix can describe the full path of a type or consist of an alias. For example, `collections.abc.Iterable` has the type prefix `collections.abc`. - `np.int` as the prefix `np` which may be an alias for `numpy`. - [Type prefixes can be defined in the configuration](#config-type-prefixes) or are inferred by docstub from import statements it can see. + `np.int` has the prefix `np` which may be an alias for `numpy`. + [Type prefixes can be defined in the configuration](configuration.md#type_prefixes) or are inferred by docstub from import statements it can see. ::: \ No newline at end of file From ad999c004826307f4b5b6bfb9477f3879e36c169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Sun, 19 Oct 2025 18:58:55 +0200 Subject: [PATCH 13/14] Add basic .readthedocs.yaml (config) --- .readthedocs.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .readthedocs.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..c435536 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,18 @@ +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version, and other tools you might need +build: + os: ubuntu-24.04 + tools: + python: "3.13" + commands: + # https://docs.readthedocs.com/platform/stable/build-customization.html#install-dependencies-with-uv + - asdf plugin add uv + - asdf install uv latest + - asdf global uv latest + - uv run --group docs sphinx-build -T -b html -d docs/_build/doctrees \ + -D language=en docs $READTHEDOCS_OUTPUT/html From 35b12ae07a87ef8173a6a657e55711f45b5ec6b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Sun, 19 Oct 2025 19:09:32 +0200 Subject: [PATCH 14/14] Try without "\" in sphinx-build command --- .readthedocs.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index c435536..68c0313 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -14,5 +14,4 @@ build: - asdf plugin add uv - asdf install uv latest - asdf global uv latest - - uv run --group docs sphinx-build -T -b html -d docs/_build/doctrees \ - -D language=en docs $READTHEDOCS_OUTPUT/html + - uv run --group docs sphinx-build -T -b html -d docs/_build/doctrees -D language=en docs $READTHEDOCS_OUTPUT/html