From ecdd733d38f8358b0d2f6bff5a2ed61adbe25adc Mon Sep 17 00:00:00 2001 From: Marianne Corvellec Date: Tue, 3 Dec 2024 15:22:01 +0100 Subject: [PATCH 01/29] Start writing docs --- doc/source/index.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 doc/source/index.rst diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 0000000..a3f4ada --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,19 @@ +docstub's documentation +======================= + +**Date**: |today|, **Version**: |version| + +Welcome! `docstub` is a command-line tool to generate `Python`_ stub files +(i.e., PYI files) from type descriptions found in `numpydoc`_ style docstrings. + +`numpy`_, `scipy`_, and the scikits follow a common convention for docstrings +that provides for consistency, while also allowing toolchains such as +`numpydoc`_ to produce well-formatted reference guides. + +Our project follows the `SciPy code of conduct`_. + +.. _numpy: https://numpy.org +.. _numpydoc: https://numpydoc.readthedocs.io +.. _Python: https://www.python.org +.. _scipy: https://docs.scipy.org +.. _Scipy code of conduct: https://github.com/scipy/scipy/blob/master/doc/source/dev/conduct/code_of_conduct.rst From 60eead1fb73c72e9ecb18abb5aa8a2ff489a563d Mon Sep 17 00:00:00 2001 From: Marianne Corvellec Date: Tue, 14 Jan 2025 15:57:18 +0100 Subject: [PATCH 02/29] Start with just a Markdown file --- doc/{source/index.rst => index.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename doc/{source/index.rst => index.md} (100%) diff --git a/doc/source/index.rst b/doc/index.md similarity index 100% rename from doc/source/index.rst rename to doc/index.md From 50758a74ddc116d2d003f90c5cffe4548f199868 Mon Sep 17 00:00:00 2001 From: Marianne Corvellec Date: Tue, 14 Jan 2025 16:08:11 +0100 Subject: [PATCH 03/29] Convert RST file to MD --- doc/index.md | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/doc/index.md b/doc/index.md index a3f4ada..fc79d48 100644 --- a/doc/index.md +++ b/doc/index.md @@ -1,19 +1,17 @@ -docstub's documentation -======================= +# docstub's documentation -**Date**: |today|, **Version**: |version| +**Date**: , **Version**: -Welcome! `docstub` is a command-line tool to generate `Python`_ stub files -(i.e., PYI files) from type descriptions found in `numpydoc`_ style docstrings. +Welcome! [docstub]{.title-ref} is a command-line tool to generate +[Python](https://www.python.org) stub files (i.e., PYI files) from type +descriptions found in [numpydoc](https://numpydoc.readthedocs.io) style +docstrings. -`numpy`_, `scipy`_, and the scikits follow a common convention for docstrings -that provides for consistency, while also allowing toolchains such as -`numpydoc`_ to produce well-formatted reference guides. +[numpy](https://numpy.org), [scipy](https://docs.scipy.org), and the +scikits follow a common convention for docstrings that provides for +consistency, while also allowing toolchains such as +[numpydoc](https://numpydoc.readthedocs.io) to produce well-formatted +reference guides. -Our project follows the `SciPy code of conduct`_. - -.. _numpy: https://numpy.org -.. _numpydoc: https://numpydoc.readthedocs.io -.. _Python: https://www.python.org -.. _scipy: https://docs.scipy.org -.. _Scipy code of conduct: https://github.com/scipy/scipy/blob/master/doc/source/dev/conduct/code_of_conduct.rst +Our project follows the [SciPy code of +conduct](https://github.com/scipy/scipy/blob/master/doc/source/dev/conduct/code_of_conduct.rst). From bd09212e73cc8b1eca459f7bb9d893f381249340 Mon Sep 17 00:00:00 2001 From: Marianne Corvellec Date: Tue, 14 Jan 2025 16:08:38 +0100 Subject: [PATCH 04/29] Drop date for now --- doc/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/index.md b/doc/index.md index fc79d48..91ce220 100644 --- a/doc/index.md +++ b/doc/index.md @@ -1,6 +1,6 @@ # docstub's documentation -**Date**: , **Version**: +**Version**: Welcome! [docstub]{.title-ref} is a command-line tool to generate [Python](https://www.python.org) stub files (i.e., PYI files) from type From 699c509f3552cb85287e0158b11d520be21c3996 Mon Sep 17 00:00:00 2001 From: Marianne Corvellec Date: Tue, 14 Jan 2025 17:16:16 +0100 Subject: [PATCH 05/29] Explain the gist of docstub --- doc/index.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/doc/index.md b/doc/index.md index 91ce220..a4793ba 100644 --- a/doc/index.md +++ b/doc/index.md @@ -15,3 +15,47 @@ reference guides. Our project follows the [SciPy code of conduct](https://github.com/scipy/scipy/blob/master/doc/source/dev/conduct/code_of_conduct.rst). + +## Basics + +Consider a function written as follows: + +```py +def example_metric(image0, image1, sigma=1.0, method='standard'): + """Pretend to calculate a local metric between two images. + + Parameters + ---------- + image0 : array-like + First image. + image1 : array_like + Second image. + sigma : float + Sigma parameter. + method : {'standard', 'modified'}, optional, default = 'standard' + The method to use for calculating the metric. + + Returns + ------- + met : ndarray of dtype float + """ + pass +``` + +Feeding this input to docstub results in the following output: + +```py +def example_metric( + image0: ArrayLike, + image1: ArrayLike, + sigma: float = ..., + method: Literal["standard", "modified"] = ..., +) -> NDArray[float] +``` + +As you can see, it is a typed function signature, where types are read from +the well-enough written docstring. + +In practice, you run the docstub command on a .py file and get a corresponding +.pyi file containing the same imports, the same variables, with classes and +functions replaced with their respective typed signatures. From f3a34943e46303b4fab761c763379a6d55f9f3cd Mon Sep 17 00:00:00 2001 From: Marianne Corvellec Date: Tue, 14 Jan 2025 17:22:33 +0100 Subject: [PATCH 06/29] Update intro --- doc/index.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/index.md b/doc/index.md index a4793ba..3aa7494 100644 --- a/doc/index.md +++ b/doc/index.md @@ -4,11 +4,12 @@ Welcome! [docstub]{.title-ref} is a command-line tool to generate [Python](https://www.python.org) stub files (i.e., PYI files) from type -descriptions found in [numpydoc](https://numpydoc.readthedocs.io) style +descriptions found in [numpydoc](https://numpydoc.readthedocs.io)-style docstrings. -[numpy](https://numpy.org), [scipy](https://docs.scipy.org), and the -scikits follow a common convention for docstrings that provides for +[numpy](https://numpy.org), [scipy](https://docs.scipy.org), +[scikit-image](https://scikit-image.org/), and others +follow a common convention for docstrings that provides for consistency, while also allowing toolchains such as [numpydoc](https://numpydoc.readthedocs.io) to produce well-formatted reference guides. From 8c3a470cb0b712b928cf394a3393d710eda33c0d Mon Sep 17 00:00:00 2001 From: Marianne Corvellec Date: Tue, 14 Jan 2025 17:23:00 +0100 Subject: [PATCH 07/29] Draw from structure by NumPy docs --- doc/index.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/doc/index.md b/doc/index.md index 3aa7494..657e380 100644 --- a/doc/index.md +++ b/doc/index.md @@ -1,8 +1,10 @@ -# docstub's documentation +# docstub user guide **Version**: -Welcome! [docstub]{.title-ref} is a command-line tool to generate +## What is docstub? + +[docstub]{.title-ref} is a command-line tool to generate [Python](https://www.python.org) stub files (i.e., PYI files) from type descriptions found in [numpydoc](https://numpydoc.readthedocs.io)-style docstrings. @@ -17,7 +19,14 @@ reference guides. Our project follows the [SciPy code of conduct](https://github.com/scipy/scipy/blob/master/doc/source/dev/conduct/code_of_conduct.rst). -## Basics +## Installation + +To install docstub, you need Python 3.10, 3.11, or 3.12. +We recommend that you install docstub with `pip`: + + pip install 'docstub [optional] @ git+https://github.com/scientific-python/docstub' + +## Fundamentals and usage Consider a function written as follows: From 06c034171227b99635d7b4beee21b086dce73ac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Wed, 12 Mar 2025 13:59:07 +0100 Subject: [PATCH 08/29] Update doc/index.md Co-authored-by: Marianne Corvellec --- doc/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/index.md b/doc/index.md index 657e380..7b86d5c 100644 --- a/doc/index.md +++ b/doc/index.md @@ -64,7 +64,7 @@ def example_metric( ``` As you can see, it is a typed function signature, where types are read from -the well-enough written docstring. +the (well-enough) written docstring. In practice, you run the docstub command on a .py file and get a corresponding .pyi file containing the same imports, the same variables, with classes and From 1005981e421fc153578f6df718abda3cfee842bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Thu, 8 May 2025 19:19:35 +0200 Subject: [PATCH 09/29] Add command line reference to doc/ and ensure it's correctness with a test. --- doc/command_line_reference.md | 27 +++++++++++++++++++++++++++ tests/test_doc.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 doc/command_line_reference.md create mode 100644 tests/test_doc.py diff --git a/doc/command_line_reference.md b/doc/command_line_reference.md new file mode 100644 index 0000000..25e3383 --- /dev/null +++ b/doc/command_line_reference.md @@ -0,0 +1,27 @@ +# Command line reference + +Running +``` +docstub --help +``` +will print + + + + +```plain +Usage: docstub [OPTIONS] ROOT_PATH + + Generate Python stub files from docstrings. + +Options: + --version Show the version and exit. + -o, --out-dir DIRECTORY Set output directory explicitly. + --config FILE Set configuration file explicitly. + --group-errors Group errors by type and content. Will delay + showing errors until all files have been processed. + -v, --verbose Log more details. + -h, --help Show this message and exit. +``` + + diff --git a/tests/test_doc.py b/tests/test_doc.py new file mode 100644 index 0000000..ee5fc41 --- /dev/null +++ b/tests/test_doc.py @@ -0,0 +1,31 @@ +"""Test documentation in doc/.""" + +import re +from pathlib import Path + +import click + +from docstub._cli import main + +PROJECT_ROOT = Path(__file__).parent.parent + + +def test_command_line_help(): + ctx = click.Context(main, info_name="docstub") + expected_help = f""" + +```plain +{main.get_help(ctx)} +``` + +""" + md_file = PROJECT_ROOT / "doc/command_line_reference.md" + with md_file.open("r") as io: + md_content = io.read() + + regex = r"(.*)" + match = re.findall(regex, md_content, flags=re.DOTALL) + assert len(match) == 1 + + actual_help = match[0] + assert actual_help == expected_help From 3a2c3d49cf6fc390f6248b4e2e6dee3e78723755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Thu, 8 May 2025 19:20:18 +0200 Subject: [PATCH 10/29] Draft typing_reference.md --- doc/typing_reference.md | 94 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 doc/typing_reference.md diff --git a/doc/typing_reference.md b/doc/typing_reference.md new file mode 100644 index 0000000..5fc8f02 --- /dev/null +++ b/doc/typing_reference.md @@ -0,0 +1,94 @@ +# Typing with docstub + +> [!NOTE] +> This document is work in progress and might be incomplete. + + +## Docstring annotation syntax + +The basic structure of to describe the type of a symbol is as follows: +``` +name : type_description (, optional) (, extra_info) +``` +- `name` might be the name of a parameter or attribute. + But typing names in other sections like "Returns" or "Yields" is also supported. +- `type_description` +- `optional` +- `extra_info` + + +[Grammar reference](../src/docstub/doctype.lark) + + +### Or + +| Docstring type | Python type annotation | +|----------------|------------------------| +| `X or Y` | `X \| Y` | + + +### Containers + +| Docstring type | Python type annotation | +|----------------------------|------------------------| +| `CONTAINER of X` | `CONTAINER[X]` | +| `CONTAINER of (X \| Y)` | `CONTAINER[X \| Y]` | +| `CONTAINER of (X, Y, ...)` | `CONTAINER[X, Y, ...]` | + + +### Shape and dtype syntax for arrays + +`array` and `ndarray`, and `array-like` and `array_like` can be used interchange-ably. + +| Docstring type | Python type annotation | +|-----------------------------|------------------------| +| `array of DTYPE` | `ndarray[DTYPE]` | +| `ndarray of dtype DTYPE` | `ndarray[DTYPE]` | +| `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, typing is not yet possible and the shape doesn't impact the resulting annotation. + +| Docstring type | Python type annotation | +|--------------------------|------------------------| +| `(3,) array of DTYPE` | `ndarray[DTYPE]` | +| `(X, Y) array of DTYPE` | `ndarray[DTYPE]` | +| `([P,] M, N) array-like` | `ArrayLike` | +| `(M, ...) ndarray` | `ArrayLike` | + + +### Literals + +| Docstring type | Python type annotation | +|---------------------|----------------------------| +| `{1, "string", .2}` | `Literal[1, "string", .2]` | +| `{X}` | `Literal[X]` | + + +### Sphinx-like reference + +| Docstring type | Python type annotation | +|-------------------|------------------------| +| ``:ref:`X` `` | `X` | +| ``:class:`Y.X` `` | `Y.X` | + +Can be used in any context where a qualified name can be used. + + +## Special cases + +### Disable docstub with comment directive + +```python +class Foo: + """Docstring.""" + + # docstub: off + a: int = None + b: str = "" + c: int = None + b: str = "" + # docstub: on +``` From 89048756d6c92d79a8d8efd669e1db49e2fe3c8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Thu, 8 May 2025 19:42:26 +0200 Subject: [PATCH 11/29] Draft user guide Copy over the structure from index.md and extend it. --- doc/index.md | 71 ----------------------------------- doc/user_guide.md | 96 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 71 deletions(-) delete mode 100644 doc/index.md create mode 100644 doc/user_guide.md diff --git a/doc/index.md b/doc/index.md deleted file mode 100644 index 7b86d5c..0000000 --- a/doc/index.md +++ /dev/null @@ -1,71 +0,0 @@ -# docstub user guide - -**Version**: - -## What is docstub? - -[docstub]{.title-ref} is a command-line tool to generate -[Python](https://www.python.org) stub files (i.e., PYI files) from type -descriptions found in [numpydoc](https://numpydoc.readthedocs.io)-style -docstrings. - -[numpy](https://numpy.org), [scipy](https://docs.scipy.org), -[scikit-image](https://scikit-image.org/), and others -follow a common convention for docstrings that provides for -consistency, while also allowing toolchains such as -[numpydoc](https://numpydoc.readthedocs.io) to produce well-formatted -reference guides. - -Our project follows the [SciPy code of -conduct](https://github.com/scipy/scipy/blob/master/doc/source/dev/conduct/code_of_conduct.rst). - -## Installation - -To install docstub, you need Python 3.10, 3.11, or 3.12. -We recommend that you install docstub with `pip`: - - pip install 'docstub [optional] @ git+https://github.com/scientific-python/docstub' - -## Fundamentals and usage - -Consider a function written as follows: - -```py -def example_metric(image0, image1, sigma=1.0, method='standard'): - """Pretend to calculate a local metric between two images. - - Parameters - ---------- - image0 : array-like - First image. - image1 : array_like - Second image. - sigma : float - Sigma parameter. - method : {'standard', 'modified'}, optional, default = 'standard' - The method to use for calculating the metric. - - Returns - ------- - met : ndarray of dtype float - """ - pass -``` - -Feeding this input to docstub results in the following output: - -```py -def example_metric( - image0: ArrayLike, - image1: ArrayLike, - sigma: float = ..., - method: Literal["standard", "modified"] = ..., -) -> NDArray[float] -``` - -As you can see, it is a typed function signature, where types are read from -the (well-enough) written docstring. - -In practice, you run the docstub command on a .py file and get a corresponding -.pyi file containing the same imports, the same variables, with classes and -functions replaced with their respective typed signatures. diff --git a/doc/user_guide.md b/doc/user_guide.md new file mode 100644 index 0000000..f8ff37c --- /dev/null +++ b/doc/user_guide.md @@ -0,0 +1,96 @@ +# User guide + +> [!NOTE] +> In early development! +> Expect to encounter bugs, missing features, and fatal errors. + + +## Installation + +While a docstub package is already available on PyPI, we recommend trying out docstub by installing directly from GitHub with + +```shell +pip install 'docstub [optional] @ git+https://github.com/scientific-python/docstub' +``` + +If you want to pin to a certain commit you can append `@COMMIT_SHA` to the repo URL above. + + +## Getting started + +Consider a simple example with the following documented function + +```python +# src/example.py + +def example_metric(image, *, mask=None, sigma=1.0, method='standard'): + """Pretend to calculate a local metric between two images. + + Parameters + ---------- + image : array-like + First image. + mask : array of dtype uint8, optional + Second image. + sigma : float or Iterable of float, optional + Sigma value for each dimension in `image`. A single value is broadcast + to all dimensions. + method : {'standard', 'modified'}, optional, default = 'standard' + The method to use for calculating the metric. + + Returns + ------- + met : ndarray of dtype float + """ + pass +``` + +Feeding this input to docstub with + +```shell +docstub simple_script.py +``` + +will create `example.pyi` in the same directory + +```python +# File generated with docstub + +from collections.abc import Iterable +from typing import Literal + +import numpy as np +from numpy.typing import ArrayLike, NDArray + +def example_metric( + image: ArrayLike, + *, + mask: NDArray[np.uint8] | None = ..., + sigma: float | Iterable[float] = ..., + method: Literal["standard", "modified"] = ... +) -> NDArray[float]: ... +``` + +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 type annotation in the stub file. + +- In a similar manner, `or` can be used as a "natural language" alternative to `|`. + +- Optional arguments that default to `None` are recognized and a `| None` is appended automatically if the type doesn't include it already. + +- Common container types such as `Iterable` can be used and a necessary import will be added automatically. + + +## Importing types + + +## Adding your own aliases for docstring descriptions + + +## Adopting docstub gradually + +`--group-errors` + +`--allow-errors` From 1701b7947c2913e3e2526e5ef6c80ccb8c5794aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Thu, 8 May 2025 19:42:56 +0200 Subject: [PATCH 12/29] Tweak README and point to user guide --- README.md | 53 +++++++++++++++++++---------------------------------- 1 file changed, 19 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 6eb71e9..b19a0ae 100644 --- a/README.md +++ b/README.md @@ -2,53 +2,38 @@ > [!NOTE] > In early development! +> Expect to encounter bugs, missing features, and fatal errors. -A command line tool to generate Python stub files (PYI) from type descriptions -in NumPyDoc style docstrings. +docstub is a command-line tool to generate [Python](https://www.python.org) stub files (i.e., PYI files) 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` which it translates into valid type annotations. -## Installation -To try out docstub, for now, we recommend installing docstub directly from this -repo: +## Installation & getting started -```shell -pip install 'docstub [optional] @ git+https://github.com/scientific-python/docstub' -``` +Please refer to the [user guide](doc/user_guide.md) to get started with docstub. -## Usage & configuration - -```shell -cd examples/ -docstub example_pkg/ -``` -will create stub files for `example_pkg/` in `examples/example_pkg-stubs/`. -For now, refer to `docstub --help` for more. - - -### Declare imports and synonyms - -Types in docstrings can and are used without having to import them. However, -when docstub creates stub files from these docstrings it actually needs to -know how to import those unknown types. - -> [!TIP] -> docstub already knows about types in Python's `typing` or `collections.abc` -> modules. That means you can just use types like `Literal` or `Sequence`. - -While docstub is smart enough to find some types via static analysis of -definitions in the given source directory, it must be told about other types -for now. To do so, refer to the syntax and comments in the -`default_config.toml`. +## Contributing +The best way you can help and contribute right now is by trying docstub out! +Feedback to what features might still be missing or where it breaks for you would be greatly appreciated. +Pointers to where the documentation is confusing and unclear. -## Contributing +Since docstub is still in early development there isn't an official contribution guide yet. +Docstubs features and API is still being heavily extended and the internal structure is still somewhat in flux. +That said, if that only entices you, feel free to open a PR. +But please do check in with an issue before you do so. -TBD +Our project follows the [SciPy code of +conduct](https://github.com/scipy/scipy/blob/master/doc/source/dev/conduct/code_of_conduct.rst). ## Acknowledgements Thanks to [docs2stubs](https://github.com/gramster/docs2stubs) by which this project was heavily inspired and influenced. + +And thanks to CZI for supporting this work with an [EOSS grant](https://chanzuckerberg.com/eoss/proposals/from-library-to-protocol-scikit-image-as-an-api-reference/). From c9414f540960277034f476ffabc7d83297e875ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Thu, 8 May 2025 20:06:41 +0200 Subject: [PATCH 13/29] Test example.py and example.pyi blocks in user guide --- doc/command_line_reference.md | 2 +- doc/user_guide.md | 10 +++++- tests/test_doc.py | 58 +++++++++++++++++++++++++++++------ 3 files changed, 59 insertions(+), 11 deletions(-) diff --git a/doc/command_line_reference.md b/doc/command_line_reference.md index 25e3383..23208ca 100644 --- a/doc/command_line_reference.md +++ b/doc/command_line_reference.md @@ -6,7 +6,7 @@ docstub --help ``` will print - + ```plain diff --git a/doc/user_guide.md b/doc/user_guide.md index f8ff37c..4fd2945 100644 --- a/doc/user_guide.md +++ b/doc/user_guide.md @@ -20,8 +20,11 @@ If you want to pin to a certain commit you can append `@COMMIT_SHA` to the repo Consider a simple example with the following documented function + + + ```python -# src/example.py +# example.py def example_metric(image, *, mask=None, sigma=1.0, method='standard'): """Pretend to calculate a local metric between two images. @@ -45,6 +48,8 @@ def example_metric(image, *, mask=None, sigma=1.0, method='standard'): pass ``` + + Feeding this input to docstub with ```shell @@ -53,6 +58,8 @@ docstub simple_script.py will create `example.pyi` in the same directory + + ```python # File generated with docstub @@ -70,6 +77,7 @@ def example_metric( method: Literal["standard", "modified"] = ... ) -> NDArray[float]: ... ``` + There are several interesting things to note here: diff --git a/tests/test_doc.py b/tests/test_doc.py index ee5fc41..5418381 100644 --- a/tests/test_doc.py +++ b/tests/test_doc.py @@ -5,27 +5,67 @@ import click -from docstub._cli import main +from docstub._cli import main as docstub_main PROJECT_ROOT = Path(__file__).parent.parent +def test_getting_started_example(tmp_path): + # Load user guide + md_file = PROJECT_ROOT / "doc/user_guide.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(.*)\n```\n" + r"" + ) + matches_py = re.findall(regex_py, md_content, flags=re.DOTALL) + assert len(matches_py) == 1 + py_source = matches_py[0] + + # Create example.py and run docstub on it + py_file = tmp_path / "example.py" + with py_file.open("x") as io: + io.write(py_source) + docstub_main([str(py_file)], standalone_mode=False) + + # Load created PYI file, this is what we expect to find in the user guide's + # code block for example.pyi + pyi_file = py_file.with_suffix(".pyi") + assert pyi_file.is_file() + with pyi_file.open("r") as io: + expected_pyi = io.read().strip() + + # Extract code block for example.pyi from guide + regex_pyi = ( + r"" + r"\n```python\n(.*)\n```\n" + r"" + ) + matches_pyi = re.findall(regex_pyi, md_content, flags=re.DOTALL) + assert len(matches_pyi) == 1 + actual_pyi = matches_pyi[0].strip() + + assert expected_pyi == actual_pyi + + def test_command_line_help(): - ctx = click.Context(main, info_name="docstub") + ctx = click.Context(docstub_main, info_name="docstub") expected_help = f""" - ```plain -{main.get_help(ctx)} +{docstub_main.get_help(ctx)} ``` - -""" +""".strip() md_file = PROJECT_ROOT / "doc/command_line_reference.md" with md_file.open("r") as io: md_content = io.read() regex = r"(.*)" - match = re.findall(regex, md_content, flags=re.DOTALL) - assert len(match) == 1 + matches = re.findall(regex, md_content, flags=re.DOTALL) + assert len(matches) == 1 - actual_help = match[0] + actual_help = matches[0].strip() assert actual_help == expected_help From 8f3d831a051800c8d27d973d52ec0a618e2d35ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Fri, 9 May 2025 14:07:30 +0200 Subject: [PATCH 14/29] Correctly point to Scientific Python's CoC That one is automatically assigned to the repo from the GitHub org. --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index b19a0ae..7c695c0 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,7 @@ Docstubs features and API is still being heavily extended and the internal struc That said, if that only entices you, feel free to open a PR. But please do check in with an issue before you do so. -Our project follows the [SciPy code of -conduct](https://github.com/scipy/scipy/blob/master/doc/source/dev/conduct/code_of_conduct.rst). +Our project follows the [Scientific Python's Code of Conduct](https://scientific-python.org/code_of_conduct/). ## Acknowledgements From bb431c2ed8ee0c9153ed361d64a9c0c0561069b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Fri, 9 May 2025 14:14:48 +0200 Subject: [PATCH 15/29] Update command line reference --- doc/command_line_reference.md | 27 ++++++++++++++++++--------- doc/user_guide.md | 2 ++ tests/test_doc.py | 4 ++-- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/doc/command_line_reference.md b/doc/command_line_reference.md index 23208ca..c6e641f 100644 --- a/doc/command_line_reference.md +++ b/doc/command_line_reference.md @@ -10,18 +10,27 @@ will print ```plain -Usage: docstub [OPTIONS] ROOT_PATH +Usage: docstub [OPTIONS] PACKAGE_PATH - Generate Python stub files from docstrings. + Generate Python stub files with type annotations from docstrings. + + Given a path `PACKAGE_PATH` to a Python package, generate stub files for it. + Type descriptions in docstrings will be used to fill in missing inline type + annotations or to override them. Options: - --version Show the version and exit. - -o, --out-dir DIRECTORY Set output directory explicitly. - --config FILE Set configuration file explicitly. - --group-errors Group errors by type and content. Will delay - showing errors until all files have been processed. - -v, --verbose Log more details. - -h, --help Show this message and exit. + --version Show the version and exit. + -o, --out-dir PATH Set output directory explicitly. Otherwise, stubs are + generated inplace. + --config PATH Set configuration file explicitly. + --group-errors Group identical errors together and list where they + occured. Will delay showing errors until all files have + been processed. Otherwise, simply report errors as the + occur. + --allow-errors INT Allow this many or fewer errors. If docstub reports + more, exit with error code '1'. [default: 0; x>=0] + -v, --verbose Print more details (repeatable). + -h, --help Show this message and exit. ``` diff --git a/doc/user_guide.md b/doc/user_guide.md index 4fd2945..d96c990 100644 --- a/doc/user_guide.md +++ b/doc/user_guide.md @@ -60,6 +60,7 @@ will create `example.pyi` in the same directory + ```python # File generated with docstub @@ -77,6 +78,7 @@ def example_metric( method: Literal["standard", "modified"] = ... ) -> NDArray[float]: ... ``` + There are several interesting things to note here: diff --git a/tests/test_doc.py b/tests/test_doc.py index 5418381..26fc35f 100644 --- a/tests/test_doc.py +++ b/tests/test_doc.py @@ -19,7 +19,7 @@ def test_getting_started_example(tmp_path): # Extract code block for example.py regex_py = ( r"" - r"\n```python\n(.*)\n```\n" + r"\n+```python(.*)```\n+" r"" ) matches_py = re.findall(regex_py, md_content, flags=re.DOTALL) @@ -42,7 +42,7 @@ def test_getting_started_example(tmp_path): # Extract code block for example.pyi from guide regex_pyi = ( r"" - r"\n```python\n(.*)\n```\n" + r"\n+```python(.*)```\n+" r"" ) matches_pyi = re.findall(regex_pyi, md_content, flags=re.DOTALL) From c6a0f911f5fbe21b945add27ab65963221a613c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Thu, 15 May 2025 22:16:18 +0200 Subject: [PATCH 16/29] WIP --- doc/{command_line_reference.md => command_line.md} | 0 doc/{typing_reference.md => typing.md} | 12 +++++++++--- tests/test_doc.py | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) rename doc/{command_line_reference.md => command_line.md} (100%) rename doc/{typing_reference.md => typing.md} (91%) diff --git a/doc/command_line_reference.md b/doc/command_line.md similarity index 100% rename from doc/command_line_reference.md rename to doc/command_line.md diff --git a/doc/typing_reference.md b/doc/typing.md similarity index 91% rename from doc/typing_reference.md rename to doc/typing.md index 5fc8f02..44e1149 100644 --- a/doc/typing_reference.md +++ b/doc/typing.md @@ -6,10 +6,15 @@ ## Docstring annotation syntax -The basic structure of to describe the type of a symbol is as follows: +To extract type information in docstrings, docstub expects these to follow the NumPyDoc style: + ``` +Section name +------------ name : type_description (, optional) (, extra_info) + Verbose ``` + - `name` might be the name of a parameter or attribute. But typing names in other sections like "Returns" or "Yields" is also supported. - `type_description` @@ -20,11 +25,12 @@ name : type_description (, optional) (, extra_info) [Grammar reference](../src/docstub/doctype.lark) -### Or +### Or expression | Docstring type | Python type annotation | |----------------|------------------------| | `X or Y` | `X \| Y` | +| `int or float` | `int \| float` | ### Containers @@ -67,7 +73,7 @@ name : type_description (, optional) (, extra_info) | `{X}` | `Literal[X]` | -### Sphinx-like reference +### reStructuredText role | Docstring type | Python type annotation | |-------------------|------------------------| diff --git a/tests/test_doc.py b/tests/test_doc.py index 26fc35f..684bc75 100644 --- a/tests/test_doc.py +++ b/tests/test_doc.py @@ -59,7 +59,7 @@ def test_command_line_help(): {docstub_main.get_help(ctx)} ``` """.strip() - md_file = PROJECT_ROOT / "doc/command_line_reference.md" + md_file = PROJECT_ROOT / "doc/command_line.md" with md_file.open("r") as io: md_content = io.read() From a4853f74f460db29469081a4b4999cd2641128a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Fri, 16 May 2025 15:51:46 +0200 Subject: [PATCH 17/29] Fill out the reference for the typing syntax --- doc/typing.md | 100 ---------------------------------- doc/typing_syntax.md | 124 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 100 deletions(-) delete mode 100644 doc/typing.md create mode 100644 doc/typing_syntax.md diff --git a/doc/typing.md b/doc/typing.md deleted file mode 100644 index 44e1149..0000000 --- a/doc/typing.md +++ /dev/null @@ -1,100 +0,0 @@ -# Typing with docstub - -> [!NOTE] -> This document is work in progress and might be incomplete. - - -## Docstring annotation syntax - -To extract type information in docstrings, docstub expects these to follow the NumPyDoc style: - -``` -Section name ------------- -name : type_description (, optional) (, extra_info) - Verbose -``` - -- `name` might be the name of a parameter or attribute. - But typing names in other sections like "Returns" or "Yields" is also supported. -- `type_description` -- `optional` -- `extra_info` - - -[Grammar reference](../src/docstub/doctype.lark) - - -### Or expression - -| Docstring type | Python type annotation | -|----------------|------------------------| -| `X or Y` | `X \| Y` | -| `int or float` | `int \| float` | - - -### Containers - -| Docstring type | Python type annotation | -|----------------------------|------------------------| -| `CONTAINER of X` | `CONTAINER[X]` | -| `CONTAINER of (X \| Y)` | `CONTAINER[X \| Y]` | -| `CONTAINER of (X, Y, ...)` | `CONTAINER[X, Y, ...]` | - - -### Shape and dtype syntax for arrays - -`array` and `ndarray`, and `array-like` and `array_like` can be used interchange-ably. - -| Docstring type | Python type annotation | -|-----------------------------|------------------------| -| `array of DTYPE` | `ndarray[DTYPE]` | -| `ndarray of dtype DTYPE` | `ndarray[DTYPE]` | -| `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, typing is not yet possible and the shape doesn't impact the resulting annotation. - -| Docstring type | Python type annotation | -|--------------------------|------------------------| -| `(3,) array of DTYPE` | `ndarray[DTYPE]` | -| `(X, Y) array of DTYPE` | `ndarray[DTYPE]` | -| `([P,] M, N) array-like` | `ArrayLike` | -| `(M, ...) ndarray` | `ArrayLike` | - - -### Literals - -| Docstring type | Python type annotation | -|---------------------|----------------------------| -| `{1, "string", .2}` | `Literal[1, "string", .2]` | -| `{X}` | `Literal[X]` | - - -### reStructuredText role - -| Docstring type | Python type annotation | -|-------------------|------------------------| -| ``:ref:`X` `` | `X` | -| ``:class:`Y.X` `` | `Y.X` | - -Can be used in any context where a qualified name can be used. - - -## Special cases - -### Disable docstub with comment directive - -```python -class Foo: - """Docstring.""" - - # docstub: off - a: int = None - b: str = "" - c: int = None - b: str = "" - # docstub: on -``` diff --git a/doc/typing_syntax.md b/doc/typing_syntax.md new file mode 100644 index 0000000..682aef6 --- /dev/null +++ b/doc/typing_syntax.md @@ -0,0 +1,124 @@ +# 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. + +Docstub defines its own [grammar](../src/docstub/doctype.lark) to parse and transform type information in docstrings into valid type annotations. +This grammar fully supports [Python's conventional typing syntax](https://typing.python.org/en/latest/index.html). +So any type annotation 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 are expected to follow the NumPyDoc style: +``` +Section name +------------ +name : annotation, optional, extra_info + Description. +``` + +- `name` might be the name of a parameter or attribute. + Other sections like "Returns" or "Yields" are supported. +- `annotation` the actual type information that will be transformed into the type annotation. +- `optional` and `extra_info` can be appended to provide additional information. + Their presence and content doesn't currently affect the resulting type annotation. + + +## Unions + +In addition to Python's conventional shorthand `|` syntax for [union types](https://typing.python.org/en/latest/spec/concepts.html#union-types), you can use `or` to join types. + +| Docstring type | Python type annotation | +|----------------|------------------------| +| `X or Y` | `X \| Y` | +| `int or float` | `int \| float` | + + +## Containers + +The content of containers can be typed using a `CONTAINER of X` like form. +This extends the basic subscription syntax for [generics](https://typing.python.org/en/latest/spec/generics.html#generics). + +| Docstring type | Python type annotation | +|-------------------------|------------------------| +| `CONTAINER of X` | `CONTAINER[X]` | +| `CONTAINER of (X or Y)` | `CONTAINER[X \| Y]` | + +For the simple case `CONTAINER of X`, where `X` is a name, you can append `(s)` to indicate the plural form. +E.g., `list of float(s)`. + +Variants of for [**tuples**](https://typing.python.org/en/latest/spec/tuples.html) + +| Docstring type | Python type annotation | +|---------------------|------------------------| +| `tuple of (X, Y)` | `tuple[X, Y]` | +| `tuple of (X, ...)` | `tuple[X, ...]` | + +and **mappings** exist. + +| Docstring type | Python type annotation | +|----------------------|------------------------| +| `MAPPING of {X: Y}` | `MAPPING[X, Y]` | +| `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. + + +## Shape and dtype syntax for arrays + +This expression allows adding shape and datatype information for data structures like [NumPy arrays](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html). + +`array` and `ndarray`, and `array-like` and `array_like` can be used interchange-ably. + +| Docstring type | Python type annotation | +|-----------------------------|------------------------| +| `array of DTYPE` | `ndarray[DTYPE]` | +| `ndarray of dtype DTYPE` | `ndarray[DTYPE]` | +| `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. + +| Docstring type | Python type annotation | +|--------------------------|------------------------| +| `(3,) array of DTYPE` | `ndarray[DTYPE]` | +| `(X, Y) array of DTYPE` | `ndarray[DTYPE]` | +| `([P,] M, N) array-like` | `ArrayLike` | +| `(M, ...) ndarray` | `ArrayLike` | + + +## Literals + +[Literals](https://typing.python.org/en/latest/spec/literal.html#literals) indicate a concrete value instead of type. +Instead of using [`typing.Literal`](https://docs.python.org/3/library/typing.html#typing.Literal), you can enclose literal values in `{...}` in docstrings. + +| Docstring type | Python type annotation | +|----------------|------------------------| +| `{1, 2, 3}` | `Literal[1, 2, 3]` | +| `{1, 2, 3}` | `Literal[1, 2, 3]` | + +> [!TIP] +> Enclosing a single value `{X}` is currently allowed but discouraged. +> Instead consider the more explicit `Literal[X]`. + + +## reStructuredText role + +Since docstrings are also used to generate documentation with Sphinx, you may want to use [restructuredText roles](https://docutils.sourceforge.io/docs/ref/rst/roles.html) in your type annotations. +Docstub allows for this anywhere where a qualified name can be used. + +| Docstring type | Python type annotation | +|----------------------|------------------------| +| `` `X` `` | `X` | +| ``:ref:`X` `` | `X` | +| ``:class:`Y.X` `` | `Y.X` | +| ``:py:class:`Y.X` `` | `Y.X` | From 26fa5ae1dcdf6eefd19d27a1c688d85c459b2fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Fri, 16 May 2025 15:52:09 +0200 Subject: [PATCH 18/29] Improve user guide and README --- README.md | 14 ++++++++------ doc/user_guide.md | 20 ++++++++++++++------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 7c695c0..ce261c2 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,16 @@ # docstub -> [!NOTE] -> In early development! -> Expect to encounter bugs, missing features, and fatal errors. +> [!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 a command-line tool to generate [Python](https://www.python.org) stub files (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) (i.e., PYI files) 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` 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 it translates into valid type annotations. ## Installation & getting started @@ -23,7 +25,7 @@ Feedback to what features might still be missing or where it breaks for you woul Pointers to where the documentation is confusing and unclear. Since docstub is still in early development there isn't an official contribution guide yet. -Docstubs features and API is still being heavily extended and the internal structure is still somewhat in flux. +Features and API are still being heavily extended and the internal structure is still somewhat in flux. That said, if that only entices you, feel free to open a PR. But please do check in with an issue before you do so. diff --git a/doc/user_guide.md b/doc/user_guide.md index d96c990..93ddac8 100644 --- a/doc/user_guide.md +++ b/doc/user_guide.md @@ -1,8 +1,10 @@ # User guide -> [!NOTE] -> In early development! -> Expect to encounter bugs, missing features, and fatal errors. +> [!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. ## Installation @@ -85,22 +87,28 @@ 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 type annotation in the stub file. - -- In a similar manner, `or` can be used as a "natural language" alternative to `|`. + In a similar manner, `or` can be used as a "natural language" alternative to `|`. + You can find more details in [Typing syntax in docstrings](typing_syntax.md). - Optional arguments that default to `None` are recognized and a `| None` is appended automatically if the type doesn't include it already. -- Common container types such as `Iterable` can be used and a necessary import will be added automatically. +- Common container types from Pythons standard library such as `Iterable` can be used and a necessary import will be added automatically. ## Importing types +TBD + ## Adding your own aliases for docstring descriptions +TBD + ## Adopting docstub gradually +TBD + `--group-errors` `--allow-errors` From a5477b2c8083357d9f962d47f3d493aacdc33c3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Fri, 23 May 2025 19:34:44 +0200 Subject: [PATCH 19/29] Fix mypy errors in test_doc.py due to stripped click decorator --- src/docstub/_cli.py | 3 +++ stubtest_allow.txt | 1 - tests/test_doc.py | 5 ++++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/docstub/_cli.py b/src/docstub/_cli.py index 7af74a7..acd4195 100644 --- a/src/docstub/_cli.py +++ b/src/docstub/_cli.py @@ -126,7 +126,10 @@ def report_execution_time(): click.echo(f"Finished in {formated_duration}") +# Preserve click.command below to keep type checker happy +# docstub: off @click.command() +# docstub: on @click.version_option(__version__) @click.argument("root_path", type=click.Path(exists=True), metavar="PACKAGE_PATH") @click.option( diff --git a/stubtest_allow.txt b/stubtest_allow.txt index 016a47f..7a9ed55 100644 --- a/stubtest_allow.txt +++ b/stubtest_allow.txt @@ -1,4 +1,3 @@ docstub\._version\..* docstub\..*\.__match_args__$ docstub._cache.FuncSerializer.__type_params__ -docstub._cli.main diff --git a/tests/test_doc.py b/tests/test_doc.py index 684bc75..d755151 100644 --- a/tests/test_doc.py +++ b/tests/test_doc.py @@ -4,6 +4,7 @@ from pathlib import Path import click +from click.testing import CliRunner from docstub._cli import main as docstub_main @@ -30,7 +31,9 @@ def test_getting_started_example(tmp_path): py_file = tmp_path / "example.py" with py_file.open("x") as io: io.write(py_source) - docstub_main([str(py_file)], standalone_mode=False) + runner = CliRunner() + result = runner.invoke(docstub_main, [str(py_file)]) + assert result.exit_code == 0 # Load created PYI file, this is what we expect to find in the user guide's # code block for example.pyi From 3e133dfda871497c8faa4bae3b54f5a5e26a4b8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Sat, 24 May 2025 11:44:20 +0200 Subject: [PATCH 20/29] Allow repeating --config option --- src/docstub/_cli.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/docstub/_cli.py b/src/docstub/_cli.py index acd4195..75e2f37 100644 --- a/src/docstub/_cli.py +++ b/src/docstub/_cli.py @@ -28,12 +28,12 @@ logger = logging.getLogger(__name__) -def _load_configuration(config_path=None): +def _load_configuration(config_paths=None): """Load and merge configuration from CWD and optional files. Parameters ---------- - config_path : Path + config_paths : list[Path] Returns ------- @@ -53,9 +53,9 @@ def _load_configuration(config_path=None): add_config = Config.from_toml(docstub_toml) config = config.merge(add_config) - if config_path: - logger.info("using %s", config_path) - add_config = Config.from_toml(config_path) + for path in config_paths: + logger.info("using %s", path) + add_config = Config.from_toml(path) config = config.merge(add_config) return config @@ -141,10 +141,13 @@ def report_execution_time(): ) @click.option( "--config", - "config_path", + "config_paths", type=click.Path(exists=True, dir_okay=False), metavar="PATH", - help="Set configuration file explicitly.", + multiple=True, + help="Set one or more configuration file(s) explicitly. " + "Otherwise, it will look for a `pyproject.toml` or `docstub.toml` in the " + "current directory.", ) @click.option( "--group-errors", @@ -165,7 +168,7 @@ def report_execution_time(): @click.option("-v", "--verbose", count=True, help="Print more details (repeatable).") @click.help_option("-h", "--help") @report_execution_time() -def main(root_path, out_dir, config_path, group_errors, allow_errors, verbose): +def main(root_path, out_dir, config_paths, group_errors, allow_errors, verbose): """Generate Python stub files with type annotations from docstrings. Given a path `PACKAGE_PATH` to a Python package, generate stub files for it. @@ -177,7 +180,7 @@ def main(root_path, out_dir, config_path, group_errors, allow_errors, verbose): ---------- root_path : Path out_dir : Path - config_path : Path + config_paths : list[Path] group_errors : bool allow_errors : int verbose : str @@ -194,7 +197,7 @@ def main(root_path, out_dir, config_path, group_errors, allow_errors, verbose): "or type references won't work." ) - config = _load_configuration(config_path) + config = _load_configuration(config_paths) types = common_known_imports() types |= _collect_types(root_path) From ca83ccd7416ca906cd19b6a0442a46254debc6b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Sat, 24 May 2025 11:55:17 +0200 Subject: [PATCH 21/29] Rename type_aliases to type_nicknames to avoid confusion with actual type aliases! --- src/docstub/_analysis.py | 20 ++++++++++---------- src/docstub/_cli.py | 6 ++++-- src/docstub/_config.py | 6 +++--- src/docstub/default_config.toml | 6 +++--- tests/test_analysis.py | 2 +- tests/test_config.py | 2 +- 6 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/docstub/_analysis.py b/src/docstub/_analysis.py index f196624..d40e6db 100644 --- a/src/docstub/_analysis.py +++ b/src/docstub/_analysis.py @@ -416,8 +416,8 @@ class TypeMatcher: Attributes ---------- types : dict[str, KnownImport] - prefixes : dict[str, KnownImport] - aliases : dict[str, str] + type_prefixes : dict[str, KnownImport] + type_nicknames : dict[str, str] successful_queries : int unknown_qualnames : list current_module : Path | None @@ -434,19 +434,19 @@ def __init__( self, *, types=None, - prefixes=None, - aliases=None, + type_prefixes=None, + type_nicknames=None, ): """ Parameters ---------- types : dict[str, KnownImport] - prefixes : dict[str, KnownImport] - aliases : dict[str, str] + type_prefixes : dict[str, KnownImport] + type_nicknames : dict[str, str] """ self.types = types or common_known_imports() - self.prefixes = prefixes or {} - self.aliases = aliases or {} + self.type_prefixes = type_prefixes or {} + self.type_nicknames = type_nicknames or {} self.successful_queries = 0 self.unknown_qualnames = [] @@ -499,7 +499,7 @@ def match(self, search_name): ) # Replace alias - search_name = self.aliases.get(search_name, search_name) + search_name = self.type_nicknames.get(search_name, search_name) if type_origin is None and self.current_module: # Try scope of current module @@ -516,7 +516,7 @@ def match(self, search_name): if type_origin is None: # Try a subset of the qualname (first 'a.b.c', then 'a.b' and 'a') for partial_qualname in reversed(accumulate_qualname(search_name)): - type_origin = self.prefixes.get(partial_qualname) + type_origin = self.type_prefixes.get(partial_qualname) if type_origin: type_name = search_name break diff --git a/src/docstub/_cli.py b/src/docstub/_cli.py index 75e2f37..f396302 100644 --- a/src/docstub/_cli.py +++ b/src/docstub/_cli.py @@ -206,7 +206,7 @@ def main(root_path, out_dir, config_paths, group_errors, allow_errors, verbose): for type_name, module in config.types.items() } - prefixes = { + type_prefixes = { prefix: ( KnownImport(import_name=module, import_alias=prefix) if module != prefix @@ -216,7 +216,9 @@ def main(root_path, out_dir, config_paths, group_errors, allow_errors, verbose): } reporter = GroupedErrorReporter() if group_errors else ErrorReporter() - matcher = TypeMatcher(types=types, prefixes=prefixes, aliases=config.type_aliases) + matcher = TypeMatcher( + types=types, type_prefixes=type_prefixes, type_nicknames=config.type_nicknames + ) stub_transformer = Py2StubTransformer(matcher=matcher, reporter=reporter) if not out_dir: diff --git a/src/docstub/_config.py b/src/docstub/_config.py index af100eb..cb879e2 100644 --- a/src/docstub/_config.py +++ b/src/docstub/_config.py @@ -13,7 +13,7 @@ class Config: types: dict[str, str] = dataclasses.field(default_factory=dict) type_prefixes: dict[str, str] = dataclasses.field(default_factory=dict) - type_aliases: dict[str, str] = dataclasses.field(default_factory=dict) + type_nicknames: dict[str, str] = dataclasses.field(default_factory=dict) _source: tuple[Path, ...] = () @@ -63,7 +63,7 @@ def merge(self, other): new = Config( types=self.types | other.types, type_prefixes=self.type_prefixes | other.type_prefixes, - type_aliases=self.type_aliases | other.type_aliases, + type_nicknames=self.type_nicknames | other.type_nicknames, _source=self._source + other._source, ) logger.debug("merged Config from %s", new._source) @@ -82,7 +82,7 @@ def __repr__(self) -> str: @staticmethod def validate(mapping): - for name in ["types", "type_prefixes", "type_aliases"]: + for name in ["types", "type_prefixes", "type_nicknames"]: table = mapping[name] if not isinstance(table, dict): raise TypeError(f"{name} must be a dict") diff --git a/src/docstub/default_config.toml b/src/docstub/default_config.toml index 5f94ff1..a23ae5b 100644 --- a/src/docstub/default_config.toml +++ b/src/docstub/default_config.toml @@ -35,9 +35,9 @@ np = "numpy" numpy = "numpy" -# Specify human-friendly aliases that can be used in docstrings to describe -# valid Python types or annotations. -[tool.docstub.type_aliases] +# Specify human-friendly nicknames for types that can be used in docstrings to +# describe valid Python types or annotations. +[tool.docstub.type_nicknames] iterable = "Iterable" callable = "Callable" function = "Callable" diff --git a/tests/test_analysis.py b/tests/test_analysis.py index 91a6f09..6ff48b8 100644 --- a/tests/test_analysis.py +++ b/tests/test_analysis.py @@ -160,7 +160,7 @@ def test_query_types(self, search_name, expected_name, expected_origin): ] ) def test_query_prefix(self, search_name, expected_name, expected_origin): - db = TypeMatcher(prefixes=self.type_prefixes.copy()) + db = TypeMatcher(type_prefixes=self.type_prefixes.copy()) type_name, type_origin = db.match(search_name) diff --git a/tests/test_config.py b/tests/test_config.py index 1cb0222..6a8bf12 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -6,4 +6,4 @@ def test_from_default(self): config = Config.from_default() assert len(config.types) > 0 assert len(config.type_prefixes) > 0 - assert len(config.type_aliases) > 0 + assert len(config.type_nicknames) > 0 From a0a58690f85b1b15eaae2e67cbed82f61f4a1222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Sat, 24 May 2025 11:55:53 +0200 Subject: [PATCH 22/29] Raise error when import alias contains "." This is not valid syntax. --- src/docstub/_analysis.py | 2 ++ tests/test_analysis.py | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/docstub/_analysis.py b/src/docstub/_analysis.py index d40e6db..eacac37 100644 --- a/src/docstub/_analysis.py +++ b/src/docstub/_analysis.py @@ -193,6 +193,8 @@ def __post_init__(self): raise ValueError("builtin cannot contain import information") elif self.import_name is None: raise ValueError("non builtin must at least define an `import_name`") + if self.import_alias is not None and "." in self.import_alias: + raise ValueError("`import_alias` can't contain a '.'") def __repr__(self) -> str: if self.builtin_name: diff --git a/tests/test_analysis.py b/tests/test_analysis.py index 6ff48b8..c23bada 100644 --- a/tests/test_analysis.py +++ b/tests/test_analysis.py @@ -5,6 +5,12 @@ from docstub._analysis import KnownImport, TypeCollector, TypeMatcher +class Test_KnownImport: + def test_dot_in_alias(self): + with pytest.raises(ValueError, match=".*can't contain a '\.'"): + KnownImport(import_name="foo.bar.baz", import_alias="bar.baz") + + @pytest.fixture def module_factory(tmp_path): """Fixture to help with creating adhoc modules with a given source. From 6d082b940d2371c60ed5f26b3bdf9dda1fc5c8c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Sat, 24 May 2025 12:09:33 +0200 Subject: [PATCH 23/29] Fill "Adopting docstub gradually" section --- doc/user_guide.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/doc/user_guide.md b/doc/user_guide.md index 93ddac8..075a4f8 100644 --- a/doc/user_guide.md +++ b/doc/user_guide.md @@ -107,8 +107,16 @@ TBD ## Adopting docstub gradually -TBD +Adopting docstub on a large codebase may initially generate many errors. +Two command line options can help addressing these errors gradually: + +* `--group-errors` will group identical errors together. + This helps identifying common groups of errors that may be addressed in one go. -`--group-errors` +* `--allow-errors` puts an upper limit (["ratchet"](https://qntm.org/ratchet)) on the number of allowed errors. + 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. -`--allow-errors` +> [!TIP] Get in touch! +> 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) 🚀. From 50e46bb6e60caf1a20670c0e5b7120a536a0f9f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Sat, 24 May 2025 12:31:56 +0200 Subject: [PATCH 24/29] Separate implicit numpy configuration --- pyproject.toml | 3 ++ src/docstub/_cli.py | 4 ++- src/docstub/_config.py | 14 ++------ src/docstub/config_template.toml | 33 +++++++++++++++++ ...{default_config.toml => numpy_config.toml} | 35 ++----------------- tests/test_config.py | 4 +-- tests/test_doc.py | 3 +- 7 files changed, 46 insertions(+), 50 deletions(-) create mode 100644 src/docstub/config_template.toml rename src/docstub/{default_config.toml => numpy_config.toml} (56%) diff --git a/pyproject.toml b/pyproject.toml index 2efef8b..08011a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -105,6 +105,9 @@ testpaths = [ [tool.coverage] run.source = ["docstub"] +[tool.docstub.types] +Path = "pathlib" + [tool.docstub.type_prefixes] cst = "libcst" lark = "lark" diff --git a/src/docstub/_cli.py b/src/docstub/_cli.py index f396302..2936ee4 100644 --- a/src/docstub/_cli.py +++ b/src/docstub/_cli.py @@ -39,7 +39,9 @@ def _load_configuration(config_paths=None): ------- config : ~.Config """ - config = Config.from_toml(Config.DEFAULT_CONFIG_PATH) + config = Config.from_toml(Config.TEMPLATE_PATH) + numpy_config = Config.from_toml(Config.NUMPY_PATH) + config = config.merge(numpy_config) pyproject_toml = Path.cwd() / "pyproject.toml" if pyproject_toml.is_file(): diff --git a/src/docstub/_config.py b/src/docstub/_config.py index cb879e2..d5df49a 100644 --- a/src/docstub/_config.py +++ b/src/docstub/_config.py @@ -9,7 +9,8 @@ @dataclasses.dataclass(frozen=True, slots=True, kw_only=True) class Config: - DEFAULT_CONFIG_PATH: ClassVar[Path] = Path(__file__).parent / "default_config.toml" + TEMPLATE_PATH: ClassVar[Path] = Path(__file__).parent / "config_template.toml" + NUMPY_PATH: ClassVar[Path] = Path(__file__).parent / "numpy_config.toml" types: dict[str, str] = dataclasses.field(default_factory=dict) type_prefixes: dict[str, str] = dataclasses.field(default_factory=dict) @@ -36,17 +37,6 @@ def from_toml(cls, path): logger.debug("created Config from %s", path) return config - @classmethod - def from_default(cls): - """Create a configuration with default values. - - Returns - ------- - config : Self - """ - config = cls.from_toml(cls.DEFAULT_CONFIG_PATH) - return config - def merge(self, other): """Merge contents with other and return a copy_with Config instance. diff --git a/src/docstub/config_template.toml b/src/docstub/config_template.toml new file mode 100644 index 0000000..a277ba8 --- /dev/null +++ b/src/docstub/config_template.toml @@ -0,0 +1,33 @@ +[tool.docstub] + +# Types and their external modules to use in docstrings. +# Docstub can't yet automatically discover where to import types from other +# packages from. Instead, you can provide this information explicitly. +# Any type on the left side will be associated with the given "module" on the +# right side. +# +# Examples: +# Path = "pathlib" +# Will allow using "Path" and use "from pathlib import Path". +# +# NDArray = "numpy.typing" +# Will allow "NDarray" and use "from numpy.typing import NDArray". +[tool.docstub.types] + +# Prefixes for external modules to match types in docstrings. +# Docstub can't yet automatically discover where to import types from other +# packages from. Instead, you can provide this information explicitly. +# Any type in a docstring whose prefix matches the name given on the left side, +# will be associated with the given "module" on the right side. +# +# Examples: +# np = "numpy" +# Will match `np.uint8` and `np.typing.NDarray` and use "import numpy as np". +# +# plt = "matplotlib.pyplot +# Will match `plt.Figure` use `import matplotlib.pyplot as plt`. +[tool.docstub.type_prefixes] + +# Nicknames for types that can be used in docstrings to describe valid Python +# types or annotations. +[tool.docstub.type_nicknames] diff --git a/src/docstub/default_config.toml b/src/docstub/numpy_config.toml similarity index 56% rename from src/docstub/default_config.toml rename to src/docstub/numpy_config.toml index a23ae5b..087e623 100644 --- a/src/docstub/default_config.toml +++ b/src/docstub/numpy_config.toml @@ -1,51 +1,20 @@ [tool.docstub] # Types and their external modules to use in docstrings. -# Docstub can't yet automatically discover where to import types from other -# packages from. Instead, you can provide this information explicitly. -# Any type on the left side will be associated with the given "module" on the -# right side. -# -# Examples: -# Path = "pathlib" -# Will allow using "Path" and use "from pathlib import Path". -# -# NDArray = "numpy.typing" -# Will allow "NDarray" and use "from numpy.typing import NDArray". [tool.docstub.types] -Path = "pathlib" NDArray = "numpy.typing" ArrayLike = "numpy.typing" # Prefixes for external modules to match types in docstrings. -# Docstub can't yet automatically discover where to import types from other -# packages from. Instead, you can provide this information explicitly. -# Any type in a docstring whose prefix matches the name given on the left side, -# will be associated with the given "module" on the right side. -# -# Examples: -# np = "numpy" -# Will match `np.uint8` and `np.typing.NDarray` and use "import numpy as np". -# -# plt = "matplotlib.pyplot -# Will match `plt.Figure` use `import matplotlib.pyplot as plt`. [tool.docstub.type_prefixes] np = "numpy" numpy = "numpy" -# Specify human-friendly nicknames for types that can be used in docstrings to -# describe valid Python types or annotations. +# Nicknames for types that can be used in docstrings to describe valid Python +# types or annotations. [tool.docstub.type_nicknames] -iterable = "Iterable" -callable = "Callable" -function = "Callable" -func = "Callable" -sequence = "Sequence" -mapping = "Mapping" - -# NumPy scalar = "np.ScalarType" integer = "np.integer" signedinteger = "np.signedinteger" diff --git a/tests/test_config.py b/tests/test_config.py index 6a8bf12..a2f0082 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -2,8 +2,8 @@ class Test_Config: - def test_from_default(self): - config = Config.from_default() + def test_numpy_config(self): + config = Config.from_toml(Config.NUMPY_PATH) assert len(config.types) > 0 assert len(config.type_prefixes) > 0 assert len(config.type_nicknames) > 0 diff --git a/tests/test_doc.py b/tests/test_doc.py index d755151..35c83ee 100644 --- a/tests/test_doc.py +++ b/tests/test_doc.py @@ -32,8 +32,7 @@ def test_getting_started_example(tmp_path): with py_file.open("x") as io: io.write(py_source) runner = CliRunner() - result = runner.invoke(docstub_main, [str(py_file)]) - assert result.exit_code == 0 + run_result = runner.invoke(docstub_main, [str(py_file)]) # noqa: F841 # Load created PYI file, this is what we expect to find in the user guide's # code block for example.pyi From 838f41b5c40ea8628a288eedb43d93caa9836d67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Sat, 24 May 2025 12:33:57 +0200 Subject: [PATCH 25/29] Sort largest error groups last with `--group-errors` option --- src/docstub/_cli.py | 10 +++++++--- src/docstub/_utils.py | 5 ++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/docstub/_cli.py b/src/docstub/_cli.py index 2936ee4..6128096 100644 --- a/src/docstub/_cli.py +++ b/src/docstub/_cli.py @@ -139,7 +139,10 @@ def report_execution_time(): "--out-dir", type=click.Path(file_okay=False), metavar="PATH", - help="Set output directory explicitly. Otherwise, stubs are generated inplace.", + help="Set output directory explicitly. " + "Stubs will be directly written into that directory while preserving the directory " + "structure under `PACKAGE_PATH`. " + "Otherwise, stubs are generated inplace.", ) @click.option( "--config", @@ -154,7 +157,7 @@ def report_execution_time(): @click.option( "--group-errors", is_flag=True, - help="Group identical errors together and list where they occured. " + help="Group identical errors together and list where they occurred. " "Will delay showing errors until all files have been processed. " "Otherwise, simply report errors as the occur.", ) @@ -165,7 +168,8 @@ def report_execution_time(): show_default=True, metavar="INT", help="Allow this many or fewer errors. " - "If docstub reports more, exit with error code '1'.", + "If docstub reports more, exit with error code '1'. " + "This is useful to adopt docstub gradually.", ) @click.option("-v", "--verbose", count=True, help="Print more details (repeatable).") @click.help_option("-h", "--help") diff --git a/src/docstub/_utils.py b/src/docstub/_utils.py index ecf748f..bbd55bd 100644 --- a/src/docstub/_utils.py +++ b/src/docstub/_utils.py @@ -327,7 +327,10 @@ def key(message): groups[group_name] = [] groups[group_name].append(message) - for (short, details), group in groups.items(): + # Show largest groups last + groups_by_size = sorted(groups.items(), key=lambda x: len(x[1])) + + for (short, details), group in groups_by_size: formatted = click.style(short, bold=True) if len(group) > 1: formatted = f"{formatted} (x{len(group)})" From 63bf34f3c7599b9e158c270500a95285c0d80d47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Sat, 24 May 2025 12:59:50 +0200 Subject: [PATCH 26/29] Add section "Using types & nicknames" --- doc/user_guide.md | 49 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/doc/user_guide.md b/doc/user_guide.md index 075a4f8..3de3676 100644 --- a/doc/user_guide.md +++ b/doc/user_guide.md @@ -91,18 +91,57 @@ There are several interesting things to note here: You can find more details in [Typing syntax in docstrings](typing_syntax.md). - Optional arguments that default to `None` are recognized and a `| None` is appended automatically if the type doesn't include it already. + The `optional` or `default = ...` part don't influence the annotation. -- Common container types from Pythons standard library such as `Iterable` can be used and a necessary import will be added automatically. +- Common container types from Python's standard library such as `Iterable` can be used and a necessary import will be added automatically. -## Importing types +## Using types & nicknames -TBD +To translate a type from a docstring into a valid type annotation, docstub needs to know where that type originates from and how to import it. +Out of the box, docstub will know about builtin types such as `int` or `bool` that don't need an import, and types in `typing`, `collections.abc` from Python's standard library. +It will source these from the Python environment it is installed in. +In addition to that, docstub will collect all types in the package directory you are running it on. +However, if you want to use types from third-party libraries you can tell docstub about them in a configuration file. +Docstub will look for a `pyproject.toml` or `docstub.toml` in the current working directory. +Or, you can point docstub at TOML file(s) explicitly using the `--config` option. +In these configuration file(s) you can declare external types directly with -## Adding your own aliases for docstring descriptions +```toml +[tool.docstub.types] +Path = "pathlib" +Figure = "matplotlib.pyplot" +``` + +This will enable using `Path` and `Figure` anywhere in docstrings. +Alternatively, you can declare an entire prefix with + +```toml +[tool.docstub.type_prefixes] +ski = "skimage" +"sklearn.tree" = "sklearn.tree" +``` + +which will enable any type that is prefixed with `ski.` or `sklearn.tree.`, e.g. `ski.transform.AffineTransform` or `sklearn.tree.DecisionTreeClassifier`. -TBD +In both of these cases, docstub doesn't check that these types actually exist. +Testing the generated stubs with a type checker is recommended. + +> [!TIP] Limitations & roadmap +> 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". +You can declare type nicknames in a configuration file with +```toml +[tool.docstub.type_nicknames] +func = "Callable" +``` ## Adopting docstub gradually From b810f00f1450e5530483847c0e1d9ef5cb6d6b9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Sat, 24 May 2025 13:00:10 +0200 Subject: [PATCH 27/29] Update command line reference --- doc/command_line.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/doc/command_line.md b/doc/command_line.md index c6e641f..168893e 100644 --- a/doc/command_line.md +++ b/doc/command_line.md @@ -20,15 +20,20 @@ Usage: docstub [OPTIONS] PACKAGE_PATH Options: --version Show the version and exit. - -o, --out-dir PATH Set output directory explicitly. Otherwise, stubs are - generated inplace. - --config PATH Set configuration file explicitly. + -o, --out-dir PATH Set output directory explicitly. Stubs will be directly + written into that directory while preserving the + directory structure under `PACKAGE_PATH`. Otherwise, + stubs are generated inplace. + --config PATH Set one or more configuration file(s) explicitly. + Otherwise, it will look for a `pyproject.toml` or + `docstub.toml` in the current directory. --group-errors Group identical errors together and list where they - occured. Will delay showing errors until all files have + occurred. Will delay showing errors until all files have been processed. Otherwise, simply report errors as the occur. --allow-errors INT Allow this many or fewer errors. If docstub reports - more, exit with error code '1'. [default: 0; x>=0] + more, exit with error code '1'. This is useful to adopt + docstub gradually. [default: 0; x>=0] -v, --verbose Print more details (repeatable). -h, --help Show this message and exit. ``` From 7044c46652c19b1c5a8ec3d1941fdfe48dc296f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Sat, 24 May 2025 15:33:51 +0200 Subject: [PATCH 28/29] Use "Callable" after removing implicit "callable" --- src/docstub/_cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/docstub/_cache.py b/src/docstub/_cache.py index 607a69f..0a10575 100644 --- a/src/docstub/_cache.py +++ b/src/docstub/_cache.py @@ -96,7 +96,7 @@ def __init__(self, *, func, serializer, cache_dir, name): """ Parameters ---------- - func : callable + func : Callable The function whose output shall be cached. serializer : FuncSerializer An interface that matches the given `func`. It must implement the From 16241aa9d6da7a4b9394371bf76639cedbb59302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Gr=C3=BCter?= Date: Sat, 24 May 2025 15:38:56 +0200 Subject: [PATCH 29/29] Address feedback from code review Co-authored-by: Oriol Abril-Pla --- doc/user_guide.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/user_guide.md b/doc/user_guide.md index 3de3676..5fc2da3 100644 --- a/doc/user_guide.md +++ b/doc/user_guide.md @@ -37,15 +37,15 @@ def example_metric(image, *, mask=None, sigma=1.0, method='standard'): First image. mask : array of dtype uint8, optional Second image. - sigma : float or Iterable of float, optional + sigma : float or Iterable of float, default: 1.0 Sigma value for each dimension in `image`. A single value is broadcast to all dimensions. - method : {'standard', 'modified'}, optional, default = 'standard' + method : {'standard', 'modified'}, default: 'standard' The method to use for calculating the metric. Returns ------- - met : ndarray of dtype float + metric : ndarray of dtype float """ pass ``` @@ -55,7 +55,7 @@ def example_metric(image, *, mask=None, sigma=1.0, method='standard'): Feeding this input to docstub with ```shell -docstub simple_script.py +docstub example.py ``` will create `example.pyi` in the same directory