├── tests ├── __init__.py └── test_numpydoc_decorator.py ├── numpydoc_decorator ├── __init__.py ├── example.py └── impl.py ├── .flake8 ├── .github └── workflows │ ├── linting.yml │ ├── release.yml │ └── tests.yml ├── shell.nix ├── .pre-commit-config.yaml ├── LICENSE ├── pyproject.toml ├── .gitignore ├── README.md └── poetry.lock /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /numpydoc_decorator/__init__.py: -------------------------------------------------------------------------------- 1 | from .impl import DocumentationError, doc # noqa 2 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = 3 | # whitespace before ':' - doesn't work well with black 4 | E203 5 | # module level import not at top of file 6 | E402 7 | # line too long - let black worry about that 8 | E501 9 | # do not assign a lambda expression, use a def 10 | E731 11 | # line break before binary operator 12 | W503 13 | -------------------------------------------------------------------------------- /.github/workflows/linting.yml: -------------------------------------------------------------------------------- 1 | name: linting 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | jobs: 10 | linting: 11 | strategy: 12 | fail-fast: true 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - uses: actions/setup-python@v5 17 | with: 18 | python-version: '3.12' 19 | - uses: pre-commit/action@v3.0.0 20 | - name: Install mypy 21 | run: pip install mypy 22 | - name: Run mypy 23 | run: | 24 | mypy numpydoc_decorator tests --ignore-missing-imports 25 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs ? import (builtins.fetchGit { 3 | url = "https://github.com/NixOS/nixpkgs/"; 4 | ref = "refs/tags/23.11"; 5 | }) {}, 6 | dev ? true, 7 | }: 8 | let 9 | py310 = pkgs.python310; 10 | poetryExtras = if dev then ["dev"] else []; 11 | poetryInstallExtras = ( 12 | if poetryExtras == [] then "" 13 | else pkgs.lib.concatStrings [ " --with=" (pkgs.lib.concatStringsSep "," poetryExtras) ] 14 | ); 15 | in 16 | pkgs.mkShell { 17 | name = "numpy-deco-env"; 18 | buildInputs = with pkgs; [ 19 | poetry 20 | py310 21 | python311 22 | python312 23 | ]; 24 | shellHook = '' 25 | poetry env use "${py310}/bin/python" 26 | poetry install -vv --sync${poetryInstallExtras} 27 | source "$(poetry env info --path)/bin/activate" 28 | ''; 29 | } 30 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # https://pre-commit.com/ 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v4.5.0 5 | hooks: 6 | - id: trailing-whitespace 7 | - id: end-of-file-fixer 8 | - id: check-yaml 9 | # isort should run before black as black sometimes tweaks the isort output 10 | - repo: https://github.com/pycqa/isort 11 | rev: 5.13.2 12 | hooks: 13 | - id: isort 14 | # https://github.com/python/black#version-control-integration 15 | - repo: https://github.com/psf/black 16 | rev: 23.12.0 17 | hooks: 18 | - id: black 19 | - repo: https://github.com/keewis/blackdoc 20 | rev: v0.3.9 21 | hooks: 22 | - id: blackdoc 23 | - repo: https://github.com/pycqa/flake8 24 | rev: 6.1.0 25 | hooks: 26 | - id: flake8 27 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | tags: 5 | - 'v*.*.*' 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout source 11 | uses: actions/checkout@v3 12 | 13 | - name: Install poetry 14 | run: pipx install poetry==1.8.3 15 | 16 | - name: Setup python 17 | uses: actions/setup-python@v5 18 | with: 19 | python-version: '3.12' 20 | cache: 'poetry' 21 | 22 | - name: Configure poetry 23 | run: | 24 | poetry self add "poetry-dynamic-versioning[plugin]" 25 | poetry config pypi-token.pypi ${{ secrets.PYPI_TOKEN }} 26 | 27 | - name: Build package 28 | run: poetry build 29 | 30 | - name: Publish to PyPI 31 | run: poetry publish 32 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | jobs: 10 | tests: 11 | strategy: 12 | fail-fast: true 13 | matrix: 14 | python-version: ['3.10', '3.11', '3.12'] 15 | poetry-version: ['1.8.3'] 16 | os: [ubuntu-latest] 17 | runs-on: ${{ matrix.os }} 18 | steps: 19 | - name: Checkout source 20 | uses: actions/checkout@v3 21 | 22 | - name: Install poetry 23 | run: pipx install poetry==${{ matrix.poetry-version }} 24 | 25 | - name: Setup python 26 | uses: actions/setup-python@v5 27 | with: 28 | python-version: ${{ matrix.python-version }} 29 | cache: 'poetry' 30 | 31 | - name: Install dependencies 32 | run: poetry install --with dev 33 | 34 | - name: Run tests 35 | run: poetry run pytest -v 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Alistair Miles 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "numpydoc_decorator" 3 | version = "0.0.0" 4 | description = "" 5 | authors = ["Alistair Miles "] 6 | readme = "README.md" 7 | packages = [{include = "numpydoc_decorator"}] 8 | 9 | [tool.poetry.dependencies] 10 | python = ">=3.10,<3.13" 11 | typing_extensions = "*" 12 | 13 | [tool.poetry.group.dev] 14 | optional = true 15 | 16 | [tool.poetry.group.dev.dependencies] 17 | black = "*" 18 | codespell = "*" 19 | colorama = "*" 20 | isort = "*" 21 | mypy = "*" 22 | numpy = "*" 23 | numpydoc = "*" 24 | pydantic = "*" 25 | pytest = "*" 26 | pytest-xdist = "*" 27 | testfixtures = "*" 28 | 29 | [build-system] 30 | requires = ["poetry-core"] 31 | build-backend = "poetry.core.masonry.api" 32 | 33 | [tool.codespell] 34 | skip = ".git,.mypy_cache,.vscode,__pycache__,pyproject.toml,poetry.lock" 35 | ignore-words-list = "jupyter,iff" # prevent jupyter -> jupiter or iff -> if 36 | check-filenames = true 37 | uri-ignore-words-list = "*" 38 | 39 | [tool.isort] 40 | profile = "black" 41 | 42 | [tool.poetry-dynamic-versioning] 43 | enable = true 44 | vcs = "git" 45 | style = "semver" 46 | 47 | [tool.pylint] 48 | reports.output-format = "colorized" 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDEs 2 | .idea 3 | .vscode 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | pip-wheel-metadata/ 28 | share/python-wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .nox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | *.py,cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 99 | __pypackages__/ 100 | 101 | # Celery stuff 102 | celerybeat-schedule 103 | celerybeat.pid 104 | 105 | # SageMath parsed files 106 | *.sage.py 107 | 108 | # Environments 109 | .env 110 | .venv 111 | env/ 112 | venv/ 113 | ENV/ 114 | env.bak/ 115 | venv.bak/ 116 | 117 | # Spyder project settings 118 | .spyderproject 119 | .spyproject 120 | 121 | # Rope project settings 122 | .ropeproject 123 | 124 | # mkdocs documentation 125 | /site 126 | 127 | # mypy 128 | .mypy_cache/ 129 | .dmypy.json 130 | dmypy.json 131 | 132 | # Pyre type checker 133 | .pyre/ 134 | -------------------------------------------------------------------------------- /numpydoc_decorator/example.py: -------------------------------------------------------------------------------- 1 | from numpydoc_decorator import doc 2 | 3 | 4 | @doc( 5 | summary="Say hello to someone.", 6 | extended_summary=""" 7 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do 8 | eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad 9 | minim veniam, quis nostrud exercitation ullamco laboris nisi ut 10 | aliquip ex ea commodo consequat. Duis aute irure dolor in 11 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla 12 | pariatur. Excepteur sint occaecat cupidatat non proident, sunt in 13 | culpa qui officia deserunt mollit anim id est laborum. 14 | """, 15 | parameters=dict( 16 | name="The name of the person to greet.", 17 | language="The language in which to greet as an ISO 639-1 code.", 18 | ), 19 | returns="A pleasant greeting.", 20 | raises=dict( 21 | NotImplementedError="If the requested language has not been implemented yet.", 22 | ValueError="If the language is not a valid ISO 639-1 code.", 23 | ), 24 | see_also=dict( 25 | print="You could use this function to print your greeting.", 26 | ), 27 | notes=""" 28 | This function is useful when greeting someone else. If you would 29 | like something to talk about next, you could try [1]_. 30 | """, 31 | references={ 32 | "1": """ 33 | O. McNoleg, "The integration of GIS, remote sensing, expert systems 34 | and adaptive co-kriging for environmental habitat modelling of the 35 | Highland Haggis using object-oriented, fuzzy-logic and neural- 36 | network techniques," Computers & Geosciences, vol. 22, pp. 585-588, 37 | 1996. 38 | """, 39 | }, 40 | examples=""" 41 | Here is how to greet a friend in English: 42 | 43 | >>> print(greet("Ford Prefect")) 44 | Hello Ford Prefect! 45 | 46 | Here is how to greet someone in another language: 47 | 48 | >>> print(greet("Tricia MacMillan", language="fr")) 49 | Salut Tricia MacMillan! 50 | 51 | """, 52 | ) 53 | def greet( 54 | name: str, 55 | language: str = "en", 56 | ) -> str: 57 | if len(language) != 2: 58 | raise ValueError("language must be an ISO 639-1 code") 59 | if language == "en": 60 | return f"Hello {name}!" 61 | elif language == "fr": 62 | return f"Salut {name}!" 63 | else: 64 | raise NotImplementedError(f"language {language} not implemented") 65 | 66 | 67 | from typing_extensions import Annotated 68 | 69 | 70 | class params: 71 | name = Annotated[str, "The name of a person."] 72 | language = Annotated[str, "An ISO 639-1 language code."] 73 | 74 | 75 | @doc( 76 | summary="Say hello to someone you know.", 77 | returns="A personal greeting.", 78 | ) 79 | def say_hello( 80 | name: params.name, 81 | language: params.language, 82 | ) -> str: 83 | if len(language) != 2: 84 | raise ValueError("language must be an ISO 639-1 code") 85 | if language == "en": 86 | return f"Hello {name}!" 87 | elif language == "fr": 88 | return f"Salut {name}!" 89 | else: 90 | raise NotImplementedError(f"language {language} not implemented") 91 | 92 | 93 | @doc( 94 | summary="Say goodbye to someone you know.", 95 | returns="A personal parting.", 96 | ) 97 | def say_goodbye( 98 | name: params.name, 99 | language: params.language, 100 | ) -> str: 101 | if len(language) != 2: 102 | raise ValueError("language must be an ISO 639-1 code") 103 | if language == "en": 104 | return f"Goodbye {name}!" 105 | elif language == "fr": 106 | return f"Au revoir {name}!" 107 | else: 108 | raise NotImplementedError(f"language {language} not implemented") 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # numpydoc_decorator 2 | 3 | This package allows you to build [numpy-style 4 | docstrings](https://numpydoc.readthedocs.io/en/latest/format.html#sections) 5 | programmatically and apply them using a decorator. This can be useful 6 | because: 7 | 8 | - Parts of your documentation, such as parameter descriptions, can be 9 | shared between functions, avoiding the need to repeat yourself. 10 | 11 | - Type information for parameters and return values is automatically 12 | picked up from type annotations and added to the docstring, avoiding 13 | the need to maintain type information in two places. 14 | 15 | ## Installation 16 | 17 | `pip install numpydoc_decorator` 18 | 19 | ## Usage 20 | 21 | ### Documenting a function 22 | 23 | Here is an example of documenting a function: 24 | 25 | ```python 26 | from numpydoc_decorator import doc 27 | 28 | 29 | @doc( 30 | summary="Say hello to someone.", 31 | extended_summary=""" 32 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do 33 | eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad 34 | minim veniam, quis nostrud exercitation ullamco laboris nisi ut 35 | aliquip ex ea commodo consequat. Duis aute irure dolor in 36 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla 37 | pariatur. Excepteur sint occaecat cupidatat non proident, sunt in 38 | culpa qui officia deserunt mollit anim id est laborum. 39 | """, 40 | parameters=dict( 41 | name="The name of the person to greet.", 42 | language="The language in which to greet as an ISO 639-1 code.", 43 | ), 44 | returns="A pleasant greeting.", 45 | raises=dict( 46 | NotImplementedError="If the requested language has not been implemented yet.", 47 | ValueError="If the language is not a valid ISO 639-1 code." 48 | ), 49 | see_also=dict( 50 | print="You could use this function to print your greeting.", 51 | ), 52 | notes=""" 53 | This function is useful when greeting someone else. If you would 54 | like something to talk about next, you could try [1]_. 55 | """, 56 | references={ 57 | "1": """ 58 | O. McNoleg, "The integration of GIS, remote sensing, expert systems 59 | and adaptive co-kriging for environmental habitat modelling of the 60 | Highland Haggis using object-oriented, fuzzy-logic and neural- 61 | network techniques," Computers & Geosciences, vol. 22, pp. 585-588, 62 | 1996. 63 | """, 64 | }, 65 | examples=""" 66 | Here is how to greet a friend in English: 67 | 68 | >>> print(greet("Ford Prefect")) 69 | Hello Ford Prefect! 70 | 71 | Here is how to greet someone in another language: 72 | 73 | >>> print(greet("Tricia MacMillan", language="fr")) 74 | Salut Tricia MacMillan! 75 | 76 | """, 77 | ) 78 | def greet( 79 | name: str, 80 | language: str = "en", 81 | ) -> str: 82 | if len(language) != 2: 83 | raise ValueError("language must be an ISO 639-1 code") 84 | if language == "en": 85 | return f"Hello {name}!" 86 | elif language == "fr": 87 | return f"Salut {name}!" 88 | else: 89 | raise NotImplementedError(f"language {language} not implemented") 90 | ``` 91 | 92 | Here is the docstring that will be created and attached to the 93 | decorated function: 94 | 95 | ``` 96 | >>> print(greet.__doc__) 97 | 98 | Say hello to someone. 99 | 100 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do 101 | eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad 102 | minim veniam, quis nostrud exercitation ullamco laboris nisi ut 103 | aliquip ex ea commodo consequat. Duis aute irure dolor in 104 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla 105 | pariatur. Excepteur sint occaecat cupidatat non proident, sunt in 106 | culpa qui officia deserunt mollit anim id est laborum. 107 | 108 | Parameters 109 | ---------- 110 | name : str 111 | The name of the person to greet. 112 | language : str, optional, default: 'en' 113 | The language in which to greet as an ISO 639-1 code. 114 | 115 | Returns 116 | ------- 117 | str 118 | A pleasant greeting. 119 | 120 | Raises 121 | ------ 122 | NotImplementedError 123 | If the requested language has not been implemented yet. 124 | ValueError 125 | If the language is not a valid ISO 639-1 code. 126 | 127 | See Also 128 | -------- 129 | print : You could use this function to print your greeting. 130 | 131 | Notes 132 | ----- 133 | This function is useful when greeting someone else. If you would like 134 | something to talk about next, you could try [1]_. 135 | 136 | References 137 | ---------- 138 | .. [1] O. McNoleg, "The integration of GIS, remote sensing, expert systems 139 | and adaptive co-kriging for environmental habitat modelling of the 140 | Highland Haggis using object-oriented, fuzzy-logic and neural- network 141 | techniques," Computers & Geosciences, vol. 22, pp. 585-588, 1996. 142 | 143 | Examples 144 | -------- 145 | Here is how to greet a friend in English: 146 | 147 | >>> print(greet("Ford Prefect")) 148 | Hello Ford Prefect! 149 | 150 | Here is how to greet someone in another language: 151 | 152 | >>> print(greet("Tricia MacMillan", language="fr")) 153 | Salut Tricia MacMillan! 154 | 155 | ``` 156 | 157 | ### Shared parameters 158 | 159 | If you have parameters which are common to multiple functions, here 160 | is an approach you can take: 161 | 162 | ```python 163 | from numpydoc_decorator import doc 164 | from typing_extensions import Annotated 165 | 166 | 167 | class params: 168 | name = Annotated[str, "The name of a person."] 169 | language = Annotated[str, "An ISO 639-1 language code."] 170 | 171 | 172 | @doc( 173 | summary="Say hello to someone you know.", 174 | returns="A personal greeting.", 175 | ) 176 | def say_hello( 177 | name: params.name, 178 | language: params.language, 179 | ) -> str: 180 | pass 181 | 182 | 183 | @doc( 184 | summary="Say goodbye to someone you know.", 185 | returns="A personal parting.", 186 | ) 187 | def say_goodbye( 188 | name: params.name, 189 | language: params.language, 190 | ) -> str: 191 | pass 192 | ``` 193 | 194 | Here are the generated docstrings: 195 | 196 | ``` 197 | >>> print(say_hello.__doc__) 198 | 199 | Say hello to someone you know. 200 | 201 | Parameters 202 | ---------- 203 | name : str 204 | The name of a person. 205 | language : str 206 | An ISO 639-1 language code. 207 | 208 | Returns 209 | ------- 210 | str 211 | A personal greeting. 212 | ``` 213 | 214 | ``` 215 | >>> print(say_goodbye.__doc__) 216 | 217 | Say goodbye to someone you know. 218 | 219 | Parameters 220 | ---------- 221 | name : str 222 | The name of a person. 223 | language : str 224 | An ISO 639-1 language code. 225 | 226 | Returns 227 | ------- 228 | str 229 | A personal parting. 230 | ``` 231 | 232 | Alternatively, to be more explicit about the value of the 233 | documentation string for the annotated type, you can use 234 | `typing_extensions.Doc`, e.g.: 235 | 236 | ```python 237 | from numpydoc_decorator import doc 238 | from typing_extensions import Annotated, Doc 239 | 240 | 241 | class params: 242 | name = Annotated[str, Doc("The name of a person.")] 243 | language = Annotated[str, Doc("An ISO 639-1 language code.")] 244 | 245 | 246 | @doc( 247 | summary="Say hello to someone you know.", 248 | ) 249 | def say_hello( 250 | name: params.name, 251 | language: params.language, 252 | ) -> Annotated[str, Doc("A personal greeting.")]: 253 | pass 254 | 255 | 256 | @doc( 257 | summary="Say goodbye to someone you know.", 258 | ) 259 | def say_goodbye( 260 | name: params.name, 261 | language: params.language, 262 | ) -> Annotated[str, Doc("A personal parting.")]: 263 | pass 264 | ``` 265 | 266 | Pydantic fields are also supported, via the `description` attribute: 267 | 268 | ```python 269 | from numpydoc_decorator import doc 270 | from typing_extensions import Annotated 271 | from pydantic import Field 272 | 273 | 274 | class params: 275 | name = Annotated[str, Field(description="The name of a person.")] 276 | language = Annotated[str, Field(description="An ISO 639-1 language code.")] 277 | 278 | 279 | @doc( 280 | summary="Say hello to someone you know.", 281 | returns="A personal greeting.", 282 | ) 283 | def say_hello( 284 | name: params.name, 285 | language: params.language, 286 | ) -> str: 287 | pass 288 | 289 | 290 | @doc( 291 | summary="Say goodbye to someone you know.", 292 | returns="A personal parting.", 293 | ) 294 | def say_goodbye( 295 | name: params.name, 296 | language: params.language, 297 | ) -> str: 298 | pass 299 | ``` 300 | 301 | 302 | ### Internationalisation and localisation of docstrings 303 | 304 | It should be possible to add internationalisation of docstrings via the [gettext](https://docs.python.org/3/library/gettext.html) module. The example below marks some parts of a docstring as available for translation: 305 | 306 | ```python 307 | from numpydoc_decorator import doc 308 | import gettext 309 | # Initialise gettext somehow. 310 | _ = gettext.gettext 311 | 312 | @doc( 313 | summary=_("Echo some text."), 314 | parameters=dict( 315 | text=_("The text you want to echo."), 316 | ), 317 | ) 318 | def echo( 319 | text: str, 320 | ) -> str: 321 | print(text) 322 | 323 | ``` 324 | 325 | Because the docstrings will be constructed at the point when the module is loaded by a user, the docstrings should then be built for the user's current locale. This is obviously something that only makes sense when your package is designed for interactive use. 326 | 327 | Caveat: I haven't tried this, any suggestions for best practices welcome. 328 | 329 | ## Notes 330 | 331 | There are probably lots of edge cases that this package has not 332 | covered yet. If you find something doesn't work as expected, or 333 | deviates from the [numpydoc style guide](https://numpydoc.readthedocs.io/en/latest/format.html) 334 | in an unreasonable way, please feel free to submit a pull request. 335 | 336 | Note that this package does deviate from the numpydoc style guide 337 | under some circumstances. For example, if a function does not have 338 | any type annotations, then there will be no type information in the 339 | docstring. The rationale for this is that all type information, if 340 | provided, should be provided through type annotations. However, some 341 | functions may choose not to annotate types for some or all parameters, 342 | but we still want to document them as best we can. 343 | 344 | ### Specific case notes 345 | * Data Classes: This package should work with [data classes](https://peps.python.org/pep-0557/) as implemented in the [`dataclasses` package](https://docs.python.org/3/library/dataclasses.html). It is, however, important to place the `@doc` _above/before_ the `@dataclass`. 346 | -------------------------------------------------------------------------------- /numpydoc_decorator/impl.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import operator 3 | import types as _types 4 | import typing 5 | from collections.abc import Generator, Iterable, Iterator, Sequence 6 | from inspect import Parameter, Signature, cleandoc, signature 7 | from textwrap import dedent, fill, indent 8 | from typing import Callable, Dict, ForwardRef, List, Mapping, Optional 9 | from typing import Sequence as SequenceType 10 | from typing import Tuple, Union 11 | 12 | from typing_extensions import Annotated, Doc, Literal, _AnnotatedAlias 13 | from typing_extensions import get_args as typing_get_args 14 | from typing_extensions import get_origin as typing_get_origin 15 | 16 | NoneType = type(None) 17 | 18 | try: 19 | # check whether numpy is installed 20 | import numpy 21 | from numpy.typing import ArrayLike, DTypeLike 22 | except ImportError: 23 | # numpy is optional, this package should still work if not installed 24 | numpy = None # type: ignore 25 | ArrayLike = None # type: ignore 26 | DTypeLike = None # type: ignore 27 | 28 | 29 | newline = "\n" 30 | 31 | 32 | class DocumentationError(Exception): 33 | pass 34 | 35 | 36 | def punctuate(s: str): 37 | # This is possibly controversial, should we be enforcing punctuation? Will 38 | # do so for now, as it is easy to forget a full stop at the end of a 39 | # piece of documentation, and it looks better if punctuation is consistent. 40 | if s: 41 | s = s.strip() 42 | s = s[0].capitalize() + s[1:] 43 | if s[-1] not in ".!?:": 44 | s += "." 45 | return s 46 | 47 | 48 | def format_paragraph(s: str, width=72): 49 | # N.B., width of 72 is recommended in PEP8. It's also the default width 50 | # of the help tab in colab, so keeping under 72 ensures lines don't get 51 | # broken visually there, which can be confusing. 52 | 53 | return fill(punctuate(dedent(s.strip(newline))), width=width) + newline 54 | 55 | 56 | def format_indented_paragraph(s: str): 57 | # Reduce width to 68 to account for indent. 58 | return indent(format_paragraph(s, width=68), prefix=" ") 59 | 60 | 61 | def format_paragraphs(s: str): 62 | prep = dedent(s.strip(newline)) 63 | paragraphs = prep.split(newline + newline) 64 | docstring = "" 65 | for paragraph in paragraphs: 66 | if ( 67 | paragraph.startswith(" ") 68 | or paragraph.startswith("..") 69 | or paragraph.startswith(">") 70 | or paragraph.startswith("[") 71 | ): 72 | # assume code or similar, leave this as-is 73 | docstring += paragraph + newline + newline 74 | else: 75 | # assume text, fill 76 | docstring += fill(punctuate(paragraph)) + newline + newline 77 | return docstring 78 | 79 | 80 | def format_indented_paragraphs(s: str): 81 | return indent(format_paragraphs(s), prefix=" ") 82 | 83 | 84 | def format_parameters(parameters: Mapping[str, str], sig: Signature): 85 | docstring = "" 86 | # display parameters in order given in function signature 87 | for param_name, param in sig.parameters.items(): 88 | if param_name == "self": 89 | # assume this is a method, don't document self parameter 90 | continue 91 | 92 | if param_name not in parameters: 93 | # allow for documentation of parameters and other parameters separately 94 | continue 95 | 96 | # add parameter name, accounting for variable parameters 97 | if param.kind == Parameter.VAR_POSITIONAL: 98 | # *args 99 | docstring += "*" + param_name 100 | 101 | elif param.kind == Parameter.VAR_KEYWORD: 102 | # **kwargs 103 | docstring += "**" + param_name 104 | 105 | else: 106 | # standard parameter 107 | docstring += param_name + " :" 108 | 109 | # handle type annotation 110 | if param.annotation is not Parameter.empty: 111 | docstring += " " + humanize_type(param.annotation) 112 | 113 | # handle default value 114 | if param.default is not Parameter.empty: 115 | if param.annotation is not Parameter.empty: 116 | docstring += "," 117 | docstring += " optional" 118 | if param.default is not None: 119 | docstring += f", default: {param.default!r}" 120 | 121 | # add parameter description 122 | docstring += newline 123 | param_doc = parameters[param_name] 124 | docstring += format_indented_paragraphs(param_doc).strip(newline) 125 | docstring += newline 126 | 127 | return docstring 128 | 129 | 130 | def humanize_type(t): 131 | # Here we attempt to provide some kind of human-readable representation 132 | # of types used in type hints. 133 | 134 | t_orig = typing_get_origin(t) 135 | t_args = typing_get_args(t) 136 | 137 | if t == NoneType: 138 | return "None" 139 | 140 | elif isinstance(t, str): 141 | return t 142 | 143 | elif isinstance(t, ForwardRef): 144 | return t.__forward_arg__ 145 | 146 | elif numpy and t == ArrayLike: 147 | return "array_like" 148 | 149 | elif numpy and t == Optional[ArrayLike]: 150 | return "array_like or None" 151 | 152 | elif numpy and t == DTypeLike: 153 | return "data-type" 154 | 155 | elif numpy and t == Optional[DTypeLike]: 156 | return "data-type or None" 157 | 158 | elif t_orig == Annotated: 159 | # special handling for annotated types 160 | x = t_args[0] 161 | return humanize_type(x) 162 | 163 | elif t_orig == Union and t_args: 164 | # special handling for union types 165 | return " or ".join([humanize_type(x) for x in t_args]) 166 | 167 | elif t_orig == Optional and t_args: 168 | x = t_args[0] 169 | return humanize_type(x) + " or None" 170 | 171 | elif t_orig == Literal and t_args: 172 | # humanize Literal types 173 | return "{" + ", ".join([repr(i) for i in t_args]) + "}" 174 | 175 | elif t_orig in [list, List, Sequence, SequenceType] and t_args: 176 | # humanize sequence types 177 | x = t_args[0] 178 | return humanize_type(t_orig).lower() + " of " + humanize_type(x) 179 | 180 | elif t_orig in [tuple, Tuple] and t_args and Ellipsis in t_args: 181 | # humanize variable length tuples 182 | x = t_args[0] 183 | return "tuple of " + humanize_type(x) 184 | 185 | elif t_orig and t_args: 186 | # deal with any other generic types 187 | return ( 188 | f"{humanize_type(t_orig)}[{', '.join([humanize_type(t) for t in t_args])}]" 189 | ) 190 | 191 | elif hasattr(t, "__name__"): 192 | # assume some other kind of class 193 | return t.__name__ 194 | 195 | else: 196 | # fallback 197 | s = repr(t) 198 | if s.startswith("typing."): 199 | s = s[7:] 200 | return s 201 | 202 | 203 | def format_returns(returns: Union[str, bool, Mapping[str, str]], sig: Signature): 204 | if returns is True: 205 | return format_returns_auto(sig.return_annotation) 206 | if isinstance(returns, str): 207 | return format_returns_unnamed(returns, sig.return_annotation) 208 | elif isinstance(returns, Mapping): 209 | return format_returns_named(returns, sig.return_annotation) 210 | else: 211 | raise TypeError("returns must be str or Mapping") 212 | 213 | 214 | def format_returns_auto(return_annotation): 215 | assert return_annotation is not Parameter.empty 216 | docstring = humanize_type(return_annotation) + newline 217 | return docstring 218 | 219 | 220 | def format_returns_unnamed(returns: str, return_annotation): 221 | if return_annotation is Parameter.empty: 222 | # just assume it's a description of the return value 223 | docstring = format_paragraph(returns) 224 | else: 225 | # provide the type 226 | docstring = humanize_type(return_annotation) + newline 227 | docstring += format_indented_paragraph(returns) 228 | docstring += newline 229 | return docstring 230 | 231 | 232 | def format_returns_named(returns: Mapping[str, str], return_annotation): 233 | docstring = "" 234 | 235 | if return_annotation is Parameter.empty: 236 | # trust the documentation regarding number of return values 237 | return_types = (Parameter.empty,) * len(returns) 238 | 239 | # handle possibility of multiple return values 240 | elif typing_get_origin(return_annotation) in [tuple, Tuple]: 241 | return_type_args = typing_get_args(return_annotation) 242 | if return_type_args and Ellipsis not in return_type_args: 243 | # treat as multiple return values 244 | return_types = return_type_args 245 | else: 246 | # treat as a single return value 247 | return_types = (return_annotation,) 248 | 249 | else: 250 | # assume a single return value 251 | return_types = (return_annotation,) 252 | 253 | if len(returns) > len(return_types): 254 | raise DocumentationError( 255 | f"more values documented {list(returns)} than types {return_types}" 256 | ) 257 | 258 | if len(returns) < len(return_types): 259 | raise DocumentationError( 260 | f"more types {return_types} than values documented {list(returns)}" 261 | ) 262 | 263 | for (return_name, return_doc), return_type in zip(returns.items(), return_types): 264 | if return_type is Parameter.empty: 265 | docstring += return_name.strip() + " :" 266 | else: 267 | if isinstance(return_name, str): 268 | docstring += return_name.strip() + " : " 269 | docstring += humanize_type(return_type) 270 | docstring += newline 271 | if isinstance(return_doc, str): 272 | docstring += format_indented_paragraph(return_doc) 273 | docstring += newline 274 | 275 | return docstring 276 | 277 | 278 | def get_yield_annotation(sig: Signature): 279 | return_annotation = sig.return_annotation 280 | 281 | if return_annotation is Parameter.empty: 282 | # no return annotation 283 | return Parameter.empty 284 | 285 | ret_orig = typing_get_origin(return_annotation) 286 | 287 | # check return type is compatible with yields 288 | if ret_orig not in [Generator, Iterator, Iterable]: 289 | raise DocumentationError( 290 | f"return type {ret_orig!r} is not compatible with yields" 291 | ) 292 | 293 | # extract yield annotation if possible 294 | ret_args = typing_get_args(return_annotation) 295 | 296 | if not ret_args: 297 | # no yield annotation 298 | return Parameter.empty 299 | 300 | else: 301 | yield_annotation = ret_args[0] 302 | return yield_annotation 303 | 304 | 305 | def get_send_annotation(sig: Signature): 306 | return_annotation = sig.return_annotation 307 | 308 | if return_annotation is Parameter.empty: 309 | # no return annotation 310 | return Parameter.empty 311 | 312 | return_type_origin = typing_get_origin(return_annotation) 313 | 314 | # check return type is compatible with yields 315 | if return_type_origin is not Generator: 316 | raise DocumentationError( 317 | f"return type {return_type_origin!r} is not compatible with receives" 318 | ) 319 | 320 | # extract yield annotation if possible 321 | return_type_args = typing_get_args(return_annotation) 322 | 323 | if not return_type_args: 324 | # no yield annotation 325 | return Parameter.empty 326 | 327 | else: 328 | send_annotation = return_type_args[1] 329 | return send_annotation 330 | 331 | 332 | def format_yields(yields: Union[str, Mapping[str, str]], sig: Signature): 333 | # yields section is basically the same as the returns section, except we 334 | # need to access the YieldType annotation from within the return annotation 335 | if isinstance(yields, str): 336 | return format_returns_unnamed(yields, get_yield_annotation(sig)) 337 | elif isinstance(yields, Mapping): 338 | return format_returns_named(yields, get_yield_annotation(sig)) 339 | else: 340 | raise TypeError("yields must be str or Mapping") 341 | 342 | 343 | def format_receives(receives: Union[str, Mapping[str, str]], sig: Signature): 344 | # receives section is basically the same as the returns section, except we 345 | # need to access the SendType annotation from within the return annotation 346 | if isinstance(receives, str): 347 | return format_returns_unnamed(receives, get_send_annotation(sig)) 348 | elif isinstance(receives, Mapping): 349 | return format_returns_named(receives, get_send_annotation(sig)) 350 | else: 351 | raise TypeError("receives must be str or Mapping") 352 | 353 | 354 | def format_raises(raises: Mapping[str, str]): 355 | docstring = "" 356 | for error, description in raises.items(): 357 | docstring += error + newline 358 | docstring += format_indented_paragraph(description) 359 | return docstring 360 | 361 | 362 | def format_maybe_code(obj): 363 | return getattr(obj, "__qualname__", str(obj)) 364 | 365 | 366 | def format_see_also(see_also): 367 | if isinstance(see_also, (list, tuple)): 368 | # assume a sequence of functions 369 | docstring = "" 370 | for item in see_also: 371 | docstring += format_maybe_code(item).strip() + newline 372 | return docstring 373 | 374 | elif isinstance(see_also, Mapping): 375 | # assume functions with descriptions 376 | docstring = "" 377 | for name, description in see_also.items(): 378 | docstring += format_maybe_code(name).strip() 379 | if description: 380 | docstring += " :" 381 | if len(description) < 70: 382 | docstring += " " + punctuate(description.strip()) + newline 383 | else: 384 | docstring += newline 385 | docstring += format_indented_paragraph(description) 386 | else: 387 | docstring += newline 388 | return docstring 389 | 390 | else: 391 | # assume a single function 392 | return format_maybe_code(see_also).strip() + newline 393 | 394 | 395 | def format_references(references: Mapping[str, str]): 396 | docstring = "" 397 | for ref, desc in references.items(): 398 | docstring += f".. [{ref}] " 399 | desc = format_indented_paragraph(desc).strip() 400 | docstring += desc + newline 401 | return docstring 402 | 403 | 404 | def unpack_optional(t): 405 | """Unpack an Optional type.""" 406 | t_orig = typing_get_origin(t) 407 | t_args = typing_get_args(t) 408 | if t_orig == Optional: 409 | return t_args[0] 410 | if t_orig == Union and len(t_args) == 2 and t_args[1] == NoneType: 411 | # compatibility for PY37 412 | return t_args[0] 413 | return t 414 | 415 | 416 | def strip_extras(t): 417 | """Strips Annotated from a given type. Borrowed from typing_extensions.""" 418 | if isinstance(t, _AnnotatedAlias): 419 | return strip_extras(t.__origin__) 420 | if isinstance(t, typing._GenericAlias): 421 | stripped_args = tuple(strip_extras(a) for a in t.__args__) 422 | if stripped_args == t.__args__: 423 | return t 424 | return t.copy_with(stripped_args) 425 | if hasattr(_types, "GenericAlias") and isinstance(t, _types.GenericAlias): 426 | stripped_args = tuple(strip_extras(a) for a in t.__args__) 427 | if stripped_args == t.__args__: 428 | return t 429 | return _types.GenericAlias(t.__origin__, stripped_args) 430 | if hasattr(_types, "UnionType") and isinstance(t, _types.UnionType): 431 | stripped_args = tuple(strip_extras(a) for a in t.__args__) 432 | if stripped_args == t.__args__: 433 | return t 434 | return functools.reduce(operator.or_, stripped_args) 435 | 436 | return t 437 | 438 | 439 | def get_annotated_doc(t, default=None): 440 | t_orig = typing_get_origin(t) 441 | t_args = typing_get_args(t) 442 | if t_orig == Annotated: 443 | # look for a Doc annotation 444 | for x in t_args[1:]: 445 | if isinstance(x, Doc): 446 | return x.documentation 447 | # look for something like a pydantic Field 448 | for x in t_args[1:]: 449 | if hasattr(x, "description") and x.description: 450 | return x.description 451 | # look for a bare string, assume first provides documentation 452 | for x in t_args[1:]: 453 | if isinstance(x, str): 454 | return x 455 | 456 | return default 457 | 458 | 459 | def auto_returns(returns, return_annotation): 460 | ret_orig = typing_get_origin(return_annotation) 461 | ret_args = typing_get_args(return_annotation) 462 | ret_multi = ret_orig in (Tuple, tuple) and Ellipsis not in ret_args 463 | if ret_multi: 464 | return auto_returns_multi(returns, ret_args) 465 | else: 466 | return auto_returns_single(returns, return_annotation) 467 | 468 | 469 | def auto_returns_single(returns, return_annotation): 470 | if returns is None: 471 | returns_doc = get_annotated_doc(return_annotation, True) 472 | else: 473 | returns_doc = returns 474 | return returns_doc 475 | 476 | 477 | def auto_returns_multi(returns, ret_args): 478 | if returns is None: 479 | # use integers as names for anonymous return values 480 | returns = tuple(range(len(ret_args))) 481 | 482 | if isinstance(returns, tuple): 483 | # assume returns_doc provides names for return values 484 | ret_names = tuple(returns) 485 | returns_doc = dict() 486 | for n, t in zip(ret_names, ret_args): 487 | returns_doc[n] = get_annotated_doc(t, True) 488 | 489 | else: 490 | returns_doc = returns 491 | 492 | return returns_doc 493 | 494 | 495 | def _doc( 496 | summary: str, 497 | deprecation: Optional[Mapping[str, str]] = None, 498 | extended_summary: Optional[str] = None, 499 | parameters: Optional[Mapping[str, str]] = None, 500 | returns: Optional[Union[str, Tuple[str, ...], Mapping[str, str]]] = None, 501 | yields: Optional[Union[str, Mapping[str, str]]] = None, 502 | receives: Optional[Union[str, Mapping[str, str]]] = None, 503 | other_parameters: Optional[Mapping[str, str]] = None, 504 | raises: Optional[Mapping[str, str]] = None, 505 | warns: Optional[Mapping[str, str]] = None, 506 | warnings: Optional[str] = None, 507 | see_also: Optional[ 508 | Union[str, SequenceType[str], Mapping[str, Optional[str]]] 509 | ] = None, 510 | notes: Optional[str] = None, 511 | references: Optional[Mapping[str, str]] = None, 512 | examples: Optional[str] = None, 513 | include_extras: bool = False, 514 | ) -> Callable[[Callable], Callable]: 515 | # sanity checks 516 | if returns and yields: 517 | raise DocumentationError("cannot have both returns and yields") 518 | if receives and not yields: 519 | raise DocumentationError("if receives, must also have yields") 520 | 521 | def decorator(f: Callable) -> Callable: 522 | # set up utility variables 523 | param_docs: Dict[str, str] = dict() 524 | if parameters: 525 | param_docs.update(parameters) 526 | other_param_docs: Dict[str, str] = dict() 527 | if other_parameters: 528 | other_param_docs.update(other_parameters) 529 | docstring = "" 530 | sig = signature(f) 531 | 532 | # accommodate use of Annotated types for parameters documentation 533 | for param_name, param in sig.parameters.items(): 534 | t = unpack_optional(param.annotation) 535 | param_doc = get_annotated_doc(t) 536 | if param_doc: 537 | param_docs.setdefault(param_name, param_doc) 538 | 539 | # accommodate use of Annotated types for returns documentation 540 | return_annotation = sig.return_annotation 541 | if ( 542 | return_annotation is not Parameter.empty 543 | and return_annotation is not None 544 | and return_annotation != NoneType 545 | and yields is None 546 | ): 547 | returns_doc = auto_returns(returns, return_annotation) 548 | else: 549 | returns_doc = returns 550 | 551 | # check for missing parameters 552 | all_param_docs: Dict[str, str] = dict() 553 | all_param_docs.update(param_docs) 554 | all_param_docs.update(other_param_docs) 555 | for e in sig.parameters: 556 | if e != "self" and e not in all_param_docs: 557 | raise DocumentationError(f"Parameter {e} not documented.") 558 | 559 | # N.B., intentionally allow extra parameters which are not in the 560 | # signature - this can be convenient for the user. 561 | 562 | # add summary 563 | if summary: 564 | docstring += format_paragraph(summary) 565 | docstring += newline 566 | 567 | # add deprecation warning 568 | if deprecation: 569 | docstring += f".. deprecated:: {deprecation['version']}" + newline 570 | docstring += format_indented_paragraph(deprecation["reason"]) 571 | docstring += newline 572 | 573 | # add extended summary 574 | if extended_summary: 575 | docstring += format_paragraph(extended_summary) 576 | docstring += newline 577 | 578 | # add parameters section 579 | if param_docs: 580 | docstring += "Parameters" + newline 581 | docstring += "----------" + newline 582 | docstring += format_parameters(param_docs, sig) 583 | docstring += newline 584 | 585 | # add returns section 586 | if returns_doc: 587 | docstring += "Returns" + newline 588 | docstring += "-------" + newline 589 | docstring += format_returns(returns_doc, sig) 590 | 591 | # add yields section 592 | if yields: 593 | docstring += "Yields" + newline 594 | docstring += "------" + newline 595 | docstring += format_yields(yields, sig) 596 | 597 | # add receives section 598 | if receives: 599 | docstring += "Receives" + newline 600 | docstring += "--------" + newline 601 | docstring += format_receives(receives, sig) 602 | 603 | # add other parameters section 604 | if other_param_docs: 605 | docstring += "Other Parameters" + newline 606 | docstring += "----------------" + newline 607 | docstring += format_parameters(other_param_docs, sig) 608 | docstring += newline 609 | 610 | # add raises section 611 | if raises: 612 | docstring += "Raises" + newline 613 | docstring += "------" + newline 614 | docstring += format_raises(raises) 615 | docstring += newline 616 | 617 | # add warns section 618 | if warns: 619 | docstring += "Warns" + newline 620 | docstring += "-----" + newline 621 | docstring += format_raises(warns) 622 | docstring += newline 623 | 624 | # add warnings section 625 | if warnings: 626 | docstring += "Warnings" + newline 627 | docstring += "--------" + newline 628 | docstring += format_paragraph(warnings) 629 | docstring += newline 630 | 631 | # add see also section 632 | if see_also: 633 | docstring += "See Also" + newline 634 | docstring += "--------" + newline 635 | docstring += format_see_also(see_also) 636 | docstring += newline 637 | 638 | # add notes section 639 | if notes: 640 | docstring += "Notes" + newline 641 | docstring += "-----" + newline 642 | docstring += format_paragraphs(notes) 643 | 644 | # add references section 645 | if references: 646 | docstring += "References" + newline 647 | docstring += "----------" + newline 648 | docstring += format_references(references) 649 | docstring += newline 650 | 651 | # add examples section 652 | if examples: 653 | docstring += "Examples" + newline 654 | docstring += "--------" + newline 655 | docstring += format_paragraphs(examples) 656 | 657 | # final cleanup 658 | docstring = newline + cleandoc(docstring) + newline 659 | 660 | # attach the docstring 661 | f.__doc__ = docstring 662 | 663 | # strip Annotated types, these are unreadable in built-in help() function 664 | if not include_extras: 665 | f.__annotations__ = { 666 | k: strip_extras(v) for k, v in f.__annotations__.items() 667 | } 668 | 669 | return f 670 | 671 | return decorator 672 | 673 | 674 | # eat our own dogfood 675 | _docstring = _doc( 676 | summary=""" 677 | Provide documentation for a function or method, to be formatted as a 678 | numpy-style docstring (numpydoc). 679 | """, 680 | parameters=dict( 681 | summary=""" 682 | A one-line summary that does not use variable names or the function name. 683 | """, 684 | deprecation=""" 685 | Warn users that the object is deprecated. Should include `version` and 686 | `reason` keys. 687 | """, 688 | extended_summary=""" 689 | A few sentences giving an extended description. 690 | """, 691 | parameters=""" 692 | Description of the function arguments and keywords. 693 | """, 694 | returns=""" 695 | Explanation of the returned values. 696 | """, 697 | yields=""" 698 | Explanation of the yielded values. This is relevant to generators only. 699 | """, 700 | receives=""" 701 | Explanation of parameters passed to a generator’s `.send()` method. 702 | """, 703 | other_parameters=""" 704 | An optional section used to describe infrequently used parameters. 705 | """, 706 | raises=""" 707 | An optional section detailing which errors get raised and under what 708 | conditions. 709 | """, 710 | warns=""" 711 | An optional section detailing which warnings get raised and under what 712 | conditions. 713 | """, 714 | warnings=""" 715 | An optional section with cautions to the user in free text/reST. 716 | """, 717 | see_also=""" 718 | An optional section used to refer to related code. 719 | """, 720 | notes=""" 721 | An optional section that provides additional information about the code, 722 | possibly including a discussion of the algorithm. 723 | """, 724 | references=""" 725 | References cited in the Notes section may be listed here. 726 | """, 727 | examples=""" 728 | An optional section for examples, using the doctest format. 729 | """, 730 | include_extras=""" 731 | If True, preserve any Annotated types in the annotations on the 732 | decorated function. 733 | """, 734 | ), 735 | returns=""" 736 | A decorator function which can be applied to a function that you want 737 | to document. 738 | """, 739 | raises=dict( 740 | DocumentationError=""" 741 | An error is raised if there are any problems with the provided documentation, 742 | such as missing parameters or parameters not consistent with the 743 | function's type annotations. 744 | """ 745 | ), 746 | ) 747 | doc = _docstring(_doc) 748 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "alabaster" 5 | version = "1.0.0" 6 | description = "A light, configurable Sphinx theme" 7 | optional = false 8 | python-versions = ">=3.10" 9 | files = [ 10 | {file = "alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b"}, 11 | {file = "alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e"}, 12 | ] 13 | 14 | [[package]] 15 | name = "annotated-types" 16 | version = "0.7.0" 17 | description = "Reusable constraint types to use with typing.Annotated" 18 | optional = false 19 | python-versions = ">=3.8" 20 | files = [ 21 | {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, 22 | {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, 23 | ] 24 | 25 | [[package]] 26 | name = "babel" 27 | version = "2.16.0" 28 | description = "Internationalization utilities" 29 | optional = false 30 | python-versions = ">=3.8" 31 | files = [ 32 | {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, 33 | {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, 34 | ] 35 | 36 | [package.extras] 37 | dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] 38 | 39 | [[package]] 40 | name = "black" 41 | version = "24.8.0" 42 | description = "The uncompromising code formatter." 43 | optional = false 44 | python-versions = ">=3.8" 45 | files = [ 46 | {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, 47 | {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, 48 | {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"}, 49 | {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, 50 | {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, 51 | {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, 52 | {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"}, 53 | {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, 54 | {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, 55 | {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, 56 | {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"}, 57 | {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, 58 | {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, 59 | {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, 60 | {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"}, 61 | {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, 62 | {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, 63 | {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, 64 | {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"}, 65 | {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, 66 | {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, 67 | {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, 68 | ] 69 | 70 | [package.dependencies] 71 | click = ">=8.0.0" 72 | mypy-extensions = ">=0.4.3" 73 | packaging = ">=22.0" 74 | pathspec = ">=0.9.0" 75 | platformdirs = ">=2" 76 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 77 | typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} 78 | 79 | [package.extras] 80 | colorama = ["colorama (>=0.4.3)"] 81 | d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] 82 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 83 | uvloop = ["uvloop (>=0.15.2)"] 84 | 85 | [[package]] 86 | name = "certifi" 87 | version = "2024.7.4" 88 | description = "Python package for providing Mozilla's CA Bundle." 89 | optional = false 90 | python-versions = ">=3.6" 91 | files = [ 92 | {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, 93 | {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, 94 | ] 95 | 96 | [[package]] 97 | name = "charset-normalizer" 98 | version = "3.3.2" 99 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 100 | optional = false 101 | python-versions = ">=3.7.0" 102 | files = [ 103 | {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, 104 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, 105 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, 106 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, 107 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, 108 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, 109 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, 110 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, 111 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, 112 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, 113 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, 114 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, 115 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, 116 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, 117 | {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, 118 | {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, 119 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, 120 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, 121 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, 122 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, 123 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, 124 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, 125 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, 126 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, 127 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, 128 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, 129 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, 130 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, 131 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, 132 | {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, 133 | {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, 134 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, 135 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, 136 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, 137 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, 138 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, 139 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, 140 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, 141 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, 142 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, 143 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, 144 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, 145 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, 146 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, 147 | {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, 148 | {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, 149 | {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, 150 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, 151 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, 152 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, 153 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, 154 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, 155 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, 156 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, 157 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, 158 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, 159 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, 160 | {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, 161 | {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, 162 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, 163 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, 164 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, 165 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, 166 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, 167 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, 168 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, 169 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, 170 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, 171 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, 172 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, 173 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, 174 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, 175 | {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, 176 | {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, 177 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, 178 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, 179 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, 180 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, 181 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, 182 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, 183 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, 184 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, 185 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, 186 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, 187 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, 188 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, 189 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, 190 | {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, 191 | {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, 192 | {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, 193 | ] 194 | 195 | [[package]] 196 | name = "click" 197 | version = "8.1.7" 198 | description = "Composable command line interface toolkit" 199 | optional = false 200 | python-versions = ">=3.7" 201 | files = [ 202 | {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, 203 | {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, 204 | ] 205 | 206 | [package.dependencies] 207 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 208 | 209 | [[package]] 210 | name = "codespell" 211 | version = "2.3.0" 212 | description = "Codespell" 213 | optional = false 214 | python-versions = ">=3.8" 215 | files = [ 216 | {file = "codespell-2.3.0-py3-none-any.whl", hash = "sha256:a9c7cef2501c9cfede2110fd6d4e5e62296920efe9abfb84648df866e47f58d1"}, 217 | {file = "codespell-2.3.0.tar.gz", hash = "sha256:360c7d10f75e65f67bad720af7007e1060a5d395670ec11a7ed1fed9dd17471f"}, 218 | ] 219 | 220 | [package.extras] 221 | dev = ["Pygments", "build", "chardet", "pre-commit", "pytest", "pytest-cov", "pytest-dependency", "ruff", "tomli", "twine"] 222 | hard-encoding-detection = ["chardet"] 223 | toml = ["tomli"] 224 | types = ["chardet (>=5.1.0)", "mypy", "pytest", "pytest-cov", "pytest-dependency"] 225 | 226 | [[package]] 227 | name = "colorama" 228 | version = "0.4.6" 229 | description = "Cross-platform colored terminal text." 230 | optional = false 231 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 232 | files = [ 233 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 234 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 235 | ] 236 | 237 | [[package]] 238 | name = "docutils" 239 | version = "0.21.2" 240 | description = "Docutils -- Python Documentation Utilities" 241 | optional = false 242 | python-versions = ">=3.9" 243 | files = [ 244 | {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, 245 | {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, 246 | ] 247 | 248 | [[package]] 249 | name = "exceptiongroup" 250 | version = "1.2.2" 251 | description = "Backport of PEP 654 (exception groups)" 252 | optional = false 253 | python-versions = ">=3.7" 254 | files = [ 255 | {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, 256 | {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, 257 | ] 258 | 259 | [package.extras] 260 | test = ["pytest (>=6)"] 261 | 262 | [[package]] 263 | name = "execnet" 264 | version = "2.1.1" 265 | description = "execnet: rapid multi-Python deployment" 266 | optional = false 267 | python-versions = ">=3.8" 268 | files = [ 269 | {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, 270 | {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, 271 | ] 272 | 273 | [package.extras] 274 | testing = ["hatch", "pre-commit", "pytest", "tox"] 275 | 276 | [[package]] 277 | name = "idna" 278 | version = "3.8" 279 | description = "Internationalized Domain Names in Applications (IDNA)" 280 | optional = false 281 | python-versions = ">=3.6" 282 | files = [ 283 | {file = "idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac"}, 284 | {file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"}, 285 | ] 286 | 287 | [[package]] 288 | name = "imagesize" 289 | version = "1.4.1" 290 | description = "Getting image size from png/jpeg/jpeg2000/gif file" 291 | optional = false 292 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 293 | files = [ 294 | {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, 295 | {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, 296 | ] 297 | 298 | [[package]] 299 | name = "iniconfig" 300 | version = "2.0.0" 301 | description = "brain-dead simple config-ini parsing" 302 | optional = false 303 | python-versions = ">=3.7" 304 | files = [ 305 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 306 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 307 | ] 308 | 309 | [[package]] 310 | name = "isort" 311 | version = "5.13.2" 312 | description = "A Python utility / library to sort Python imports." 313 | optional = false 314 | python-versions = ">=3.8.0" 315 | files = [ 316 | {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, 317 | {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, 318 | ] 319 | 320 | [package.extras] 321 | colors = ["colorama (>=0.4.6)"] 322 | 323 | [[package]] 324 | name = "jinja2" 325 | version = "3.1.4" 326 | description = "A very fast and expressive template engine." 327 | optional = false 328 | python-versions = ">=3.7" 329 | files = [ 330 | {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, 331 | {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, 332 | ] 333 | 334 | [package.dependencies] 335 | MarkupSafe = ">=2.0" 336 | 337 | [package.extras] 338 | i18n = ["Babel (>=2.7)"] 339 | 340 | [[package]] 341 | name = "markupsafe" 342 | version = "2.1.5" 343 | description = "Safely add untrusted strings to HTML/XML markup." 344 | optional = false 345 | python-versions = ">=3.7" 346 | files = [ 347 | {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, 348 | {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, 349 | {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, 350 | {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, 351 | {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, 352 | {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, 353 | {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, 354 | {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, 355 | {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, 356 | {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, 357 | {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, 358 | {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, 359 | {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, 360 | {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, 361 | {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, 362 | {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, 363 | {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, 364 | {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, 365 | {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, 366 | {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, 367 | {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, 368 | {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, 369 | {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, 370 | {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, 371 | {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, 372 | {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, 373 | {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, 374 | {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, 375 | {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, 376 | {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, 377 | {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, 378 | {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, 379 | {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, 380 | {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, 381 | {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, 382 | {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, 383 | {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, 384 | {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, 385 | {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, 386 | {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, 387 | {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, 388 | {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, 389 | {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, 390 | {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, 391 | {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, 392 | {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, 393 | {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, 394 | {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, 395 | {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, 396 | {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, 397 | {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, 398 | {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, 399 | {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, 400 | {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, 401 | {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, 402 | {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, 403 | {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, 404 | {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, 405 | {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, 406 | {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, 407 | ] 408 | 409 | [[package]] 410 | name = "mypy" 411 | version = "1.11.2" 412 | description = "Optional static typing for Python" 413 | optional = false 414 | python-versions = ">=3.8" 415 | files = [ 416 | {file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"}, 417 | {file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"}, 418 | {file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383"}, 419 | {file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8"}, 420 | {file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7"}, 421 | {file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385"}, 422 | {file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca"}, 423 | {file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104"}, 424 | {file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4"}, 425 | {file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6"}, 426 | {file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318"}, 427 | {file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36"}, 428 | {file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987"}, 429 | {file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca"}, 430 | {file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70"}, 431 | {file = "mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b"}, 432 | {file = "mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86"}, 433 | {file = "mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce"}, 434 | {file = "mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1"}, 435 | {file = "mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b"}, 436 | {file = "mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6"}, 437 | {file = "mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70"}, 438 | {file = "mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d"}, 439 | {file = "mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d"}, 440 | {file = "mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24"}, 441 | {file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12"}, 442 | {file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79"}, 443 | ] 444 | 445 | [package.dependencies] 446 | mypy-extensions = ">=1.0.0" 447 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 448 | typing-extensions = ">=4.6.0" 449 | 450 | [package.extras] 451 | dmypy = ["psutil (>=4.0)"] 452 | install-types = ["pip"] 453 | mypyc = ["setuptools (>=50)"] 454 | reports = ["lxml"] 455 | 456 | [[package]] 457 | name = "mypy-extensions" 458 | version = "1.0.0" 459 | description = "Type system extensions for programs checked with the mypy type checker." 460 | optional = false 461 | python-versions = ">=3.5" 462 | files = [ 463 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 464 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 465 | ] 466 | 467 | [[package]] 468 | name = "numpy" 469 | version = "2.1.0" 470 | description = "Fundamental package for array computing in Python" 471 | optional = false 472 | python-versions = ">=3.10" 473 | files = [ 474 | {file = "numpy-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6326ab99b52fafdcdeccf602d6286191a79fe2fda0ae90573c5814cd2b0bc1b8"}, 475 | {file = "numpy-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0937e54c09f7a9a68da6889362ddd2ff584c02d015ec92672c099b61555f8911"}, 476 | {file = "numpy-2.1.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:30014b234f07b5fec20f4146f69e13cfb1e33ee9a18a1879a0142fbb00d47673"}, 477 | {file = "numpy-2.1.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:899da829b362ade41e1e7eccad2cf274035e1cb36ba73034946fccd4afd8606b"}, 478 | {file = "numpy-2.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08801848a40aea24ce16c2ecde3b756f9ad756586fb2d13210939eb69b023f5b"}, 479 | {file = "numpy-2.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:398049e237d1aae53d82a416dade04defed1a47f87d18d5bd615b6e7d7e41d1f"}, 480 | {file = "numpy-2.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0abb3916a35d9090088a748636b2c06dc9a6542f99cd476979fb156a18192b84"}, 481 | {file = "numpy-2.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:10e2350aea18d04832319aac0f887d5fcec1b36abd485d14f173e3e900b83e33"}, 482 | {file = "numpy-2.1.0-cp310-cp310-win32.whl", hash = "sha256:f6b26e6c3b98adb648243670fddc8cab6ae17473f9dc58c51574af3e64d61211"}, 483 | {file = "numpy-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:f505264735ee074250a9c78247ee8618292091d9d1fcc023290e9ac67e8f1afa"}, 484 | {file = "numpy-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:76368c788ccb4f4782cf9c842b316140142b4cbf22ff8db82724e82fe1205dce"}, 485 | {file = "numpy-2.1.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f8e93a01a35be08d31ae33021e5268f157a2d60ebd643cfc15de6ab8e4722eb1"}, 486 | {file = "numpy-2.1.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:9523f8b46485db6939bd069b28b642fec86c30909cea90ef550373787f79530e"}, 487 | {file = "numpy-2.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54139e0eb219f52f60656d163cbe67c31ede51d13236c950145473504fa208cb"}, 488 | {file = "numpy-2.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5ebbf9fbdabed208d4ecd2e1dfd2c0741af2f876e7ae522c2537d404ca895c3"}, 489 | {file = "numpy-2.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:378cb4f24c7d93066ee4103204f73ed046eb88f9ad5bb2275bb9fa0f6a02bd36"}, 490 | {file = "numpy-2.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8f699a709120b220dfe173f79c73cb2a2cab2c0b88dd59d7b49407d032b8ebd"}, 491 | {file = "numpy-2.1.0-cp311-cp311-win32.whl", hash = "sha256:ffbd6faeb190aaf2b5e9024bac9622d2ee549b7ec89ef3a9373fa35313d44e0e"}, 492 | {file = "numpy-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0af3a5987f59d9c529c022c8c2a64805b339b7ef506509fba7d0556649b9714b"}, 493 | {file = "numpy-2.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fe76d75b345dc045acdbc006adcb197cc680754afd6c259de60d358d60c93736"}, 494 | {file = "numpy-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f358ea9e47eb3c2d6eba121ab512dfff38a88db719c38d1e67349af210bc7529"}, 495 | {file = "numpy-2.1.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:dd94ce596bda40a9618324547cfaaf6650b1a24f5390350142499aa4e34e53d1"}, 496 | {file = "numpy-2.1.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b47c551c6724960479cefd7353656498b86e7232429e3a41ab83be4da1b109e8"}, 497 | {file = "numpy-2.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0756a179afa766ad7cb6f036de622e8a8f16ffdd55aa31f296c870b5679d745"}, 498 | {file = "numpy-2.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24003ba8ff22ea29a8c306e61d316ac74111cebf942afbf692df65509a05f111"}, 499 | {file = "numpy-2.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b34fa5e3b5d6dc7e0a4243fa0f81367027cb6f4a7215a17852979634b5544ee0"}, 500 | {file = "numpy-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c4f982715e65036c34897eb598d64aef15150c447be2cfc6643ec7a11af06574"}, 501 | {file = "numpy-2.1.0-cp312-cp312-win32.whl", hash = "sha256:c4cd94dfefbefec3f8b544f61286584292d740e6e9d4677769bc76b8f41deb02"}, 502 | {file = "numpy-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0cdef204199278f5c461a0bed6ed2e052998276e6d8ab2963d5b5c39a0500bc"}, 503 | {file = "numpy-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8ab81ccd753859ab89e67199b9da62c543850f819993761c1e94a75a814ed667"}, 504 | {file = "numpy-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:442596f01913656d579309edcd179a2a2f9977d9a14ff41d042475280fc7f34e"}, 505 | {file = "numpy-2.1.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:848c6b5cad9898e4b9ef251b6f934fa34630371f2e916261070a4eb9092ffd33"}, 506 | {file = "numpy-2.1.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:54c6a63e9d81efe64bfb7bcb0ec64332a87d0b87575f6009c8ba67ea6374770b"}, 507 | {file = "numpy-2.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:652e92fc409e278abdd61e9505649e3938f6d04ce7ef1953f2ec598a50e7c195"}, 508 | {file = "numpy-2.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ab32eb9170bf8ffcbb14f11613f4a0b108d3ffee0832457c5d4808233ba8977"}, 509 | {file = "numpy-2.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:8fb49a0ba4d8f41198ae2d52118b050fd34dace4b8f3fb0ee34e23eb4ae775b1"}, 510 | {file = "numpy-2.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:44e44973262dc3ae79e9063a1284a73e09d01b894b534a769732ccd46c28cc62"}, 511 | {file = "numpy-2.1.0-cp313-cp313-win32.whl", hash = "sha256:ab83adc099ec62e044b1fbb3a05499fa1e99f6d53a1dde102b2d85eff66ed324"}, 512 | {file = "numpy-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:de844aaa4815b78f6023832590d77da0e3b6805c644c33ce94a1e449f16d6ab5"}, 513 | {file = "numpy-2.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:343e3e152bf5a087511cd325e3b7ecfd5b92d369e80e74c12cd87826e263ec06"}, 514 | {file = "numpy-2.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f07fa2f15dabe91259828ce7d71b5ca9e2eb7c8c26baa822c825ce43552f4883"}, 515 | {file = "numpy-2.1.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5474dad8c86ee9ba9bb776f4b99ef2d41b3b8f4e0d199d4f7304728ed34d0300"}, 516 | {file = "numpy-2.1.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:1f817c71683fd1bb5cff1529a1d085a57f02ccd2ebc5cd2c566f9a01118e3b7d"}, 517 | {file = "numpy-2.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a3336fbfa0d38d3deacd3fe7f3d07e13597f29c13abf4d15c3b6dc2291cbbdd"}, 518 | {file = "numpy-2.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a894c51fd8c4e834f00ac742abad73fc485df1062f1b875661a3c1e1fb1c2f6"}, 519 | {file = "numpy-2.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:9156ca1f79fc4acc226696e95bfcc2b486f165a6a59ebe22b2c1f82ab190384a"}, 520 | {file = "numpy-2.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:624884b572dff8ca8f60fab591413f077471de64e376b17d291b19f56504b2bb"}, 521 | {file = "numpy-2.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:15ef8b2177eeb7e37dd5ef4016f30b7659c57c2c0b57a779f1d537ff33a72c7b"}, 522 | {file = "numpy-2.1.0-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:e5f0642cdf4636198a4990de7a71b693d824c56a757862230454629cf62e323d"}, 523 | {file = "numpy-2.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15976718c004466406342789f31b6673776360f3b1e3c575f25302d7e789575"}, 524 | {file = "numpy-2.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6c1de77ded79fef664d5098a66810d4d27ca0224e9051906e634b3f7ead134c2"}, 525 | {file = "numpy-2.1.0.tar.gz", hash = "sha256:7dc90da0081f7e1da49ec4e398ede6a8e9cc4f5ebe5f9e06b443ed889ee9aaa2"}, 526 | ] 527 | 528 | [[package]] 529 | name = "numpydoc" 530 | version = "1.8.0" 531 | description = "Sphinx extension to support docstrings in Numpy format" 532 | optional = false 533 | python-versions = ">=3.9" 534 | files = [ 535 | {file = "numpydoc-1.8.0-py3-none-any.whl", hash = "sha256:72024c7fd5e17375dec3608a27c03303e8ad00c81292667955c6fea7a3ccf541"}, 536 | {file = "numpydoc-1.8.0.tar.gz", hash = "sha256:022390ab7464a44f8737f79f8b31ce1d3cfa4b4af79ccaa1aac5e8368db587fb"}, 537 | ] 538 | 539 | [package.dependencies] 540 | sphinx = ">=6" 541 | tabulate = ">=0.8.10" 542 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 543 | 544 | [package.extras] 545 | developer = ["pre-commit (>=3.3)", "tomli"] 546 | doc = ["intersphinx-registry", "matplotlib (>=3.5)", "numpy (>=1.22)", "pydata-sphinx-theme (>=0.13.3)", "sphinx (>=7)"] 547 | test = ["matplotlib", "pytest", "pytest-cov"] 548 | 549 | [[package]] 550 | name = "packaging" 551 | version = "24.1" 552 | description = "Core utilities for Python packages" 553 | optional = false 554 | python-versions = ">=3.8" 555 | files = [ 556 | {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, 557 | {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, 558 | ] 559 | 560 | [[package]] 561 | name = "pathspec" 562 | version = "0.12.1" 563 | description = "Utility library for gitignore style pattern matching of file paths." 564 | optional = false 565 | python-versions = ">=3.8" 566 | files = [ 567 | {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, 568 | {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, 569 | ] 570 | 571 | [[package]] 572 | name = "platformdirs" 573 | version = "4.2.2" 574 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." 575 | optional = false 576 | python-versions = ">=3.8" 577 | files = [ 578 | {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, 579 | {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, 580 | ] 581 | 582 | [package.extras] 583 | docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] 584 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] 585 | type = ["mypy (>=1.8)"] 586 | 587 | [[package]] 588 | name = "pluggy" 589 | version = "1.5.0" 590 | description = "plugin and hook calling mechanisms for python" 591 | optional = false 592 | python-versions = ">=3.8" 593 | files = [ 594 | {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, 595 | {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, 596 | ] 597 | 598 | [package.extras] 599 | dev = ["pre-commit", "tox"] 600 | testing = ["pytest", "pytest-benchmark"] 601 | 602 | [[package]] 603 | name = "pydantic" 604 | version = "2.8.2" 605 | description = "Data validation using Python type hints" 606 | optional = false 607 | python-versions = ">=3.8" 608 | files = [ 609 | {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, 610 | {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, 611 | ] 612 | 613 | [package.dependencies] 614 | annotated-types = ">=0.4.0" 615 | pydantic-core = "2.20.1" 616 | typing-extensions = {version = ">=4.6.1", markers = "python_version < \"3.13\""} 617 | 618 | [package.extras] 619 | email = ["email-validator (>=2.0.0)"] 620 | 621 | [[package]] 622 | name = "pydantic-core" 623 | version = "2.20.1" 624 | description = "Core functionality for Pydantic validation and serialization" 625 | optional = false 626 | python-versions = ">=3.8" 627 | files = [ 628 | {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, 629 | {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, 630 | {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, 631 | {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, 632 | {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, 633 | {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, 634 | {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, 635 | {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, 636 | {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, 637 | {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, 638 | {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, 639 | {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, 640 | {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, 641 | {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, 642 | {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, 643 | {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, 644 | {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, 645 | {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, 646 | {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, 647 | {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, 648 | {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, 649 | {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, 650 | {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, 651 | {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, 652 | {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, 653 | {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, 654 | {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, 655 | {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, 656 | {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, 657 | {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, 658 | {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, 659 | {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, 660 | {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, 661 | {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, 662 | {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, 663 | {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, 664 | {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, 665 | {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, 666 | {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, 667 | {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, 668 | {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, 669 | {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, 670 | {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, 671 | {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, 672 | {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, 673 | {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, 674 | {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, 675 | {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, 676 | {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, 677 | {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, 678 | {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, 679 | {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, 680 | {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, 681 | {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, 682 | {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, 683 | {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, 684 | {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, 685 | {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, 686 | {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, 687 | {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, 688 | {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, 689 | {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, 690 | {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, 691 | {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, 692 | {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, 693 | {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, 694 | {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, 695 | {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, 696 | {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, 697 | {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, 698 | {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, 699 | {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, 700 | {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, 701 | {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, 702 | {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, 703 | {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, 704 | {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, 705 | {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, 706 | {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, 707 | {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, 708 | {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, 709 | {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, 710 | {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, 711 | {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, 712 | {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, 713 | {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, 714 | {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, 715 | {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, 716 | {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, 717 | ] 718 | 719 | [package.dependencies] 720 | typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" 721 | 722 | [[package]] 723 | name = "pygments" 724 | version = "2.18.0" 725 | description = "Pygments is a syntax highlighting package written in Python." 726 | optional = false 727 | python-versions = ">=3.8" 728 | files = [ 729 | {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, 730 | {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, 731 | ] 732 | 733 | [package.extras] 734 | windows-terminal = ["colorama (>=0.4.6)"] 735 | 736 | [[package]] 737 | name = "pytest" 738 | version = "8.3.2" 739 | description = "pytest: simple powerful testing with Python" 740 | optional = false 741 | python-versions = ">=3.8" 742 | files = [ 743 | {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, 744 | {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, 745 | ] 746 | 747 | [package.dependencies] 748 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 749 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 750 | iniconfig = "*" 751 | packaging = "*" 752 | pluggy = ">=1.5,<2" 753 | tomli = {version = ">=1", markers = "python_version < \"3.11\""} 754 | 755 | [package.extras] 756 | dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] 757 | 758 | [[package]] 759 | name = "pytest-xdist" 760 | version = "3.6.1" 761 | description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" 762 | optional = false 763 | python-versions = ">=3.8" 764 | files = [ 765 | {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"}, 766 | {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, 767 | ] 768 | 769 | [package.dependencies] 770 | execnet = ">=2.1" 771 | pytest = ">=7.0.0" 772 | 773 | [package.extras] 774 | psutil = ["psutil (>=3.0)"] 775 | setproctitle = ["setproctitle"] 776 | testing = ["filelock"] 777 | 778 | [[package]] 779 | name = "requests" 780 | version = "2.32.3" 781 | description = "Python HTTP for Humans." 782 | optional = false 783 | python-versions = ">=3.8" 784 | files = [ 785 | {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, 786 | {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, 787 | ] 788 | 789 | [package.dependencies] 790 | certifi = ">=2017.4.17" 791 | charset-normalizer = ">=2,<4" 792 | idna = ">=2.5,<4" 793 | urllib3 = ">=1.21.1,<3" 794 | 795 | [package.extras] 796 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 797 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 798 | 799 | [[package]] 800 | name = "snowballstemmer" 801 | version = "2.2.0" 802 | description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." 803 | optional = false 804 | python-versions = "*" 805 | files = [ 806 | {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, 807 | {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, 808 | ] 809 | 810 | [[package]] 811 | name = "sphinx" 812 | version = "8.0.2" 813 | description = "Python documentation generator" 814 | optional = false 815 | python-versions = ">=3.10" 816 | files = [ 817 | {file = "sphinx-8.0.2-py3-none-any.whl", hash = "sha256:56173572ae6c1b9a38911786e206a110c9749116745873feae4f9ce88e59391d"}, 818 | {file = "sphinx-8.0.2.tar.gz", hash = "sha256:0cce1ddcc4fd3532cf1dd283bc7d886758362c5c1de6598696579ce96d8ffa5b"}, 819 | ] 820 | 821 | [package.dependencies] 822 | alabaster = ">=0.7.14" 823 | babel = ">=2.13" 824 | colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} 825 | docutils = ">=0.20,<0.22" 826 | imagesize = ">=1.3" 827 | Jinja2 = ">=3.1" 828 | packaging = ">=23.0" 829 | Pygments = ">=2.17" 830 | requests = ">=2.30.0" 831 | snowballstemmer = ">=2.2" 832 | sphinxcontrib-applehelp = "*" 833 | sphinxcontrib-devhelp = "*" 834 | sphinxcontrib-htmlhelp = ">=2.0.0" 835 | sphinxcontrib-jsmath = "*" 836 | sphinxcontrib-qthelp = "*" 837 | sphinxcontrib-serializinghtml = ">=1.1.9" 838 | tomli = {version = ">=2", markers = "python_version < \"3.11\""} 839 | 840 | [package.extras] 841 | docs = ["sphinxcontrib-websupport"] 842 | lint = ["flake8 (>=6.0)", "mypy (==1.11.0)", "pytest (>=6.0)", "ruff (==0.5.5)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-Pillow (==10.2.0.20240520)", "types-Pygments (==2.18.0.20240506)", "types-colorama (==0.4.15.20240311)", "types-defusedxml (==0.7.0.20240218)", "types-docutils (==0.21.0.20240724)", "types-requests (>=2.30.0)"] 843 | test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] 844 | 845 | [[package]] 846 | name = "sphinxcontrib-applehelp" 847 | version = "2.0.0" 848 | description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" 849 | optional = false 850 | python-versions = ">=3.9" 851 | files = [ 852 | {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, 853 | {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, 854 | ] 855 | 856 | [package.extras] 857 | lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] 858 | standalone = ["Sphinx (>=5)"] 859 | test = ["pytest"] 860 | 861 | [[package]] 862 | name = "sphinxcontrib-devhelp" 863 | version = "2.0.0" 864 | description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" 865 | optional = false 866 | python-versions = ">=3.9" 867 | files = [ 868 | {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, 869 | {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, 870 | ] 871 | 872 | [package.extras] 873 | lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] 874 | standalone = ["Sphinx (>=5)"] 875 | test = ["pytest"] 876 | 877 | [[package]] 878 | name = "sphinxcontrib-htmlhelp" 879 | version = "2.1.0" 880 | description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" 881 | optional = false 882 | python-versions = ">=3.9" 883 | files = [ 884 | {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, 885 | {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, 886 | ] 887 | 888 | [package.extras] 889 | lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] 890 | standalone = ["Sphinx (>=5)"] 891 | test = ["html5lib", "pytest"] 892 | 893 | [[package]] 894 | name = "sphinxcontrib-jsmath" 895 | version = "1.0.1" 896 | description = "A sphinx extension which renders display math in HTML via JavaScript" 897 | optional = false 898 | python-versions = ">=3.5" 899 | files = [ 900 | {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, 901 | {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, 902 | ] 903 | 904 | [package.extras] 905 | test = ["flake8", "mypy", "pytest"] 906 | 907 | [[package]] 908 | name = "sphinxcontrib-qthelp" 909 | version = "2.0.0" 910 | description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" 911 | optional = false 912 | python-versions = ">=3.9" 913 | files = [ 914 | {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, 915 | {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, 916 | ] 917 | 918 | [package.extras] 919 | lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] 920 | standalone = ["Sphinx (>=5)"] 921 | test = ["defusedxml (>=0.7.1)", "pytest"] 922 | 923 | [[package]] 924 | name = "sphinxcontrib-serializinghtml" 925 | version = "2.0.0" 926 | description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" 927 | optional = false 928 | python-versions = ">=3.9" 929 | files = [ 930 | {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, 931 | {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, 932 | ] 933 | 934 | [package.extras] 935 | lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] 936 | standalone = ["Sphinx (>=5)"] 937 | test = ["pytest"] 938 | 939 | [[package]] 940 | name = "tabulate" 941 | version = "0.9.0" 942 | description = "Pretty-print tabular data" 943 | optional = false 944 | python-versions = ">=3.7" 945 | files = [ 946 | {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, 947 | {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, 948 | ] 949 | 950 | [package.extras] 951 | widechars = ["wcwidth"] 952 | 953 | [[package]] 954 | name = "testfixtures" 955 | version = "8.3.0" 956 | description = "A collection of helpers and mock objects for unit tests and doc tests." 957 | optional = false 958 | python-versions = ">=3.7" 959 | files = [ 960 | {file = "testfixtures-8.3.0-py3-none-any.whl", hash = "sha256:3d1e0e0005c4d6ac2a2ab27916704c6471047f0d2f78f2e54adf20abdacc7b10"}, 961 | {file = "testfixtures-8.3.0.tar.gz", hash = "sha256:d4c0b84af2f267610f908009b50d6f983a4e58ade22c67bab6787b5a402d59c0"}, 962 | ] 963 | 964 | [package.extras] 965 | build = ["setuptools-git", "twine", "wheel"] 966 | docs = ["django", "furo", "sphinx", "sybil (>=6)", "twisted"] 967 | test = ["django", "mypy", "pytest (>=7.1)", "pytest-cov", "pytest-django", "sybil (>=6)", "twisted"] 968 | 969 | [[package]] 970 | name = "tomli" 971 | version = "2.0.1" 972 | description = "A lil' TOML parser" 973 | optional = false 974 | python-versions = ">=3.7" 975 | files = [ 976 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 977 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 978 | ] 979 | 980 | [[package]] 981 | name = "typing-extensions" 982 | version = "4.12.2" 983 | description = "Backported and Experimental Type Hints for Python 3.8+" 984 | optional = false 985 | python-versions = ">=3.8" 986 | files = [ 987 | {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, 988 | {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, 989 | ] 990 | 991 | [[package]] 992 | name = "urllib3" 993 | version = "2.2.2" 994 | description = "HTTP library with thread-safe connection pooling, file post, and more." 995 | optional = false 996 | python-versions = ">=3.8" 997 | files = [ 998 | {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, 999 | {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, 1000 | ] 1001 | 1002 | [package.extras] 1003 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] 1004 | h2 = ["h2 (>=4,<5)"] 1005 | socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] 1006 | zstd = ["zstandard (>=0.18.0)"] 1007 | 1008 | [metadata] 1009 | lock-version = "2.0" 1010 | python-versions = ">=3.10,<3.13" 1011 | content-hash = "d71efd3a0bbf931f3b2ba95d5ed8de7806a8dfc7405d27a7f84edec02c08057f" 1012 | -------------------------------------------------------------------------------- /tests/test_numpydoc_decorator.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Mapping 2 | from dataclasses import dataclass 3 | from inspect import cleandoc, getdoc 4 | from typing import ( 5 | Dict, 6 | ForwardRef, 7 | Generator, 8 | Iterable, 9 | Iterator, 10 | List, 11 | Optional, 12 | Sequence, 13 | Set, 14 | Tuple, 15 | Union, 16 | ) 17 | 18 | import numpy 19 | import pytest 20 | from numpy.typing import ArrayLike, DTypeLike 21 | from numpydoc.docscrape import FunctionDoc 22 | from numpydoc.validate import validate as numpydoc_validate 23 | from testfixtures import compare 24 | from typing_extensions import Annotated, Literal 25 | 26 | from numpydoc_decorator import DocumentationError, doc 27 | 28 | 29 | def validate(f, allow: Optional[Set[str]] = None) -> None: 30 | # noinspection PyTypeChecker 31 | report: Mapping = numpydoc_validate(FunctionDoc(f)) 32 | errors = report["errors"] 33 | 34 | # numpydoc validation errors that we will ignore in all cases 35 | ignored_errors = { 36 | # Summary should fit in a single line 37 | "SS06", 38 | # No extended summary found 39 | "ES01", 40 | # Parameter has no type 41 | "PR04", 42 | # The first line of the Returns section should contain only the type, 43 | # unless multiple values are being returned 44 | "RT02", 45 | # See Also section not found 46 | "SA01", 47 | # Missing description for See Also reference 48 | "SA04", 49 | # No examples section found 50 | "EX01", 51 | } 52 | 53 | # ignore errors in some specific cases 54 | if allow: 55 | ignored_errors.update(allow) 56 | 57 | for code, desc in errors: 58 | if code not in ignored_errors: 59 | assert False, (code, desc) 60 | 61 | 62 | def test_basic(): 63 | # noinspection PyUnusedLocal 64 | @doc( 65 | summary="A function with simple parameters.", 66 | parameters=dict( 67 | bar="This is very bar.", 68 | baz="This is totally baz.", 69 | ), 70 | returns=dict( 71 | qux="Amazingly qux.", 72 | ), 73 | ) 74 | def foo(bar, baz): 75 | pass 76 | 77 | expected = cleandoc( 78 | """ 79 | A function with simple parameters. 80 | 81 | Parameters 82 | ---------- 83 | bar : 84 | This is very bar. 85 | baz : 86 | This is totally baz. 87 | 88 | Returns 89 | ------- 90 | qux : 91 | Amazingly qux. 92 | """ 93 | ) 94 | actual = getdoc(foo) 95 | compare(actual, expected) 96 | validate(foo) 97 | 98 | 99 | def test_long_summary(): 100 | # noinspection PyUnusedLocal 101 | @doc( 102 | summary="A function with simple parameters and a very long summary. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", # noqa 103 | parameters=dict( 104 | bar="This is very bar.", 105 | baz="This is totally baz.", 106 | ), 107 | returns=dict( 108 | qux="Amazingly qux.", 109 | ), 110 | ) 111 | def foo(bar, baz): 112 | pass 113 | 114 | expected = cleandoc( 115 | """ 116 | A function with simple parameters and a very long summary. Lorem ipsum 117 | dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor 118 | incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, 119 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo 120 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit 121 | esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat 122 | cupidatat non proident, sunt in culpa qui officia deserunt mollit anim 123 | id est laborum. 124 | 125 | Parameters 126 | ---------- 127 | bar : 128 | This is very bar. 129 | baz : 130 | This is totally baz. 131 | 132 | Returns 133 | ------- 134 | qux : 135 | Amazingly qux. 136 | """ 137 | ) 138 | actual = getdoc(foo) 139 | compare(actual, expected) 140 | validate(foo) 141 | 142 | 143 | def test_long_param_doc(): 144 | # noinspection PyUnusedLocal 145 | @doc( 146 | summary="A function with simple parameters and a very long param doc.", 147 | parameters=dict( 148 | bar="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", # noqa 149 | baz="This is totally baz.", 150 | ), 151 | returns=dict( 152 | qux="Amazingly qux.", 153 | ), 154 | ) 155 | def foo(bar, baz): 156 | pass 157 | 158 | expected = cleandoc( 159 | """ 160 | A function with simple parameters and a very long param doc. 161 | 162 | Parameters 163 | ---------- 164 | bar : 165 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do 166 | eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad 167 | minim veniam, quis nostrud exercitation ullamco laboris nisi ut 168 | aliquip ex ea commodo consequat. Duis aute irure dolor in 169 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla 170 | pariatur. Excepteur sint occaecat cupidatat non proident, sunt in 171 | culpa qui officia deserunt mollit anim id est laborum. 172 | baz : 173 | This is totally baz. 174 | 175 | Returns 176 | ------- 177 | qux : 178 | Amazingly qux. 179 | """ 180 | ) 181 | actual = getdoc(foo) 182 | compare(actual, expected) 183 | validate(foo) 184 | 185 | 186 | def test_missing_param(): 187 | with pytest.raises(DocumentationError): 188 | # noinspection PyUnusedLocal 189 | @doc( 190 | summary="A function with simple parameters.", 191 | parameters=dict( 192 | bar="This is very bar.", 193 | # baz parameter is missing 194 | ), 195 | ) 196 | def foo(bar, baz): 197 | pass 198 | 199 | 200 | def test_missing_params(): 201 | with pytest.raises(DocumentationError): 202 | # noinspection PyUnusedLocal 203 | @doc( 204 | summary="A function with simple parameters.", 205 | # no parameters given at all 206 | ) 207 | def foo(bar, baz): 208 | pass 209 | 210 | 211 | def test_extra_param(): 212 | # Be relaxed about this, can be convenient because it allows chucking in 213 | # everything from a bag of parameters, and only those which are in the 214 | # function signature are used. 215 | 216 | # noinspection PyUnusedLocal 217 | @doc( 218 | summary="A function with simple parameters.", 219 | parameters=dict( 220 | bar="This is very bar.", 221 | baz="This is totally baz.", 222 | spam="This parameter is not in the signature.", 223 | ), 224 | returns=dict( 225 | qux="Amazingly qux.", 226 | ), 227 | ) 228 | def foo(bar, baz): 229 | pass 230 | 231 | expected = cleandoc( 232 | """ 233 | A function with simple parameters. 234 | 235 | Parameters 236 | ---------- 237 | bar : 238 | This is very bar. 239 | baz : 240 | This is totally baz. 241 | 242 | Returns 243 | ------- 244 | qux : 245 | Amazingly qux. 246 | """ 247 | ) 248 | actual = getdoc(foo) 249 | compare(actual, expected) 250 | validate(foo) 251 | 252 | 253 | def test_parameters_order(): 254 | # noinspection PyUnusedLocal 255 | @doc( 256 | summary="A function with typed parameters.", 257 | parameters=dict( 258 | # given in different order from signature 259 | baz="This is totally baz.", 260 | bar="This is very bar.", 261 | ), 262 | ) 263 | def foo(bar, baz): 264 | pass 265 | 266 | expected = cleandoc( 267 | """ 268 | A function with typed parameters. 269 | 270 | Parameters 271 | ---------- 272 | bar : 273 | This is very bar. 274 | baz : 275 | This is totally baz. 276 | 277 | """ 278 | ) 279 | actual = getdoc(foo) 280 | compare(actual, expected) 281 | validate(foo) 282 | 283 | 284 | def test_parameters_typed(): 285 | # Support use of Annotated to keep type information and 286 | # documentation together, assuming second argument provides 287 | # the documentation. 288 | 289 | # noinspection PyUnusedLocal 290 | @doc( 291 | summary="A function with typed parameters.", 292 | parameters=dict( 293 | bar="This is very bar.", 294 | baz="This is totally baz.", 295 | qux="Many values.", 296 | spam="You'll love it.", 297 | eggs="Good with spam.", 298 | bacon="Good with eggs.", 299 | sausage="Good with bacon.", 300 | lobster="Good with sausage.", 301 | thermidor="A type of lobster dish.", 302 | # other parameters not needed, will be picked up from Annotated type 303 | ), 304 | ) 305 | def foo( 306 | bar: int, 307 | baz: str, 308 | qux: Sequence, 309 | spam: Union[list, str], 310 | eggs: Dict[str, Sequence], 311 | bacon: Literal["xxx", "yyy", "zzz"], 312 | sausage: List[int], 313 | lobster: Tuple[float, ...], 314 | thermidor: Sequence[str], 315 | norwegian_blue: Annotated[str, "This is an ex-parrot."], 316 | lumberjack_song: Optional[ 317 | Annotated[int, "I sleep all night and I work all day."] 318 | ], 319 | ): 320 | pass 321 | 322 | expected = cleandoc( 323 | """ 324 | A function with typed parameters. 325 | 326 | Parameters 327 | ---------- 328 | bar : int 329 | This is very bar. 330 | baz : str 331 | This is totally baz. 332 | qux : Sequence 333 | Many values. 334 | spam : list or str 335 | You'll love it. 336 | eggs : dict[str, Sequence] 337 | Good with spam. 338 | bacon : {'xxx', 'yyy', 'zzz'} 339 | Good with eggs. 340 | sausage : list of int 341 | Good with bacon. 342 | lobster : tuple of float 343 | Good with sausage. 344 | thermidor : sequence of str 345 | A type of lobster dish. 346 | norwegian_blue : str 347 | This is an ex-parrot. 348 | lumberjack_song : int or None 349 | I sleep all night and I work all day. 350 | 351 | """ 352 | ) 353 | actual = getdoc(foo) 354 | compare(actual, expected) 355 | validate(foo) 356 | 357 | # check annotated types are stripped 358 | compare(foo.__annotations__["norwegian_blue"], str) 359 | compare(foo.__annotations__["lumberjack_song"], Optional[int]) 360 | 361 | 362 | def test_parameters_all_annotated(): 363 | # Support use of Annotated to keep type information and 364 | # documentation together, assuming second argument provides 365 | # the documentation. 366 | 367 | @doc( 368 | summary="A function with annotated parameters.", 369 | ) 370 | def foo( 371 | bar: Annotated[int, "This is very bar."], 372 | baz: Annotated[str, "This is totally baz."], 373 | qux: Annotated[Sequence, "Many values."], 374 | spam: Annotated[Union[list, str], "You'll love it."], 375 | eggs: Annotated[Dict[str, Sequence], "Good with spam."], 376 | bacon: Annotated[Literal["xxx", "yyy", "zzz"], "Good with eggs."], 377 | sausage: Annotated[List[int], "Good with bacon."], 378 | lobster: Annotated[Tuple[float, ...], "Good with sausage."], 379 | thermidor: Annotated[Sequence[str], "A type of lobster dish."], 380 | norwegian_blue: Annotated[str, "This is an ex-parrot."], 381 | lumberjack_song: Optional[ 382 | Annotated[int, "I sleep all night and I work all day."] 383 | ], 384 | ): 385 | pass 386 | 387 | expected = cleandoc( 388 | """ 389 | A function with annotated parameters. 390 | 391 | Parameters 392 | ---------- 393 | bar : int 394 | This is very bar. 395 | baz : str 396 | This is totally baz. 397 | qux : Sequence 398 | Many values. 399 | spam : list or str 400 | You'll love it. 401 | eggs : dict[str, Sequence] 402 | Good with spam. 403 | bacon : {'xxx', 'yyy', 'zzz'} 404 | Good with eggs. 405 | sausage : list of int 406 | Good with bacon. 407 | lobster : tuple of float 408 | Good with sausage. 409 | thermidor : sequence of str 410 | A type of lobster dish. 411 | norwegian_blue : str 412 | This is an ex-parrot. 413 | lumberjack_song : int or None 414 | I sleep all night and I work all day. 415 | 416 | """ 417 | ) 418 | actual = getdoc(foo) 419 | compare(actual, expected) 420 | validate(foo) 421 | 422 | # check annotated types are stripped 423 | expected_annotations = { 424 | "bar": int, 425 | "baz": str, 426 | "qux": Sequence, 427 | "spam": Union[list, str], 428 | "eggs": Dict[str, Sequence], 429 | "bacon": Literal["xxx", "yyy", "zzz"], 430 | "sausage": List[int], 431 | "lobster": Tuple[float, ...], 432 | "thermidor": Sequence[str], 433 | "norwegian_blue": str, 434 | "lumberjack_song": Optional[int], 435 | } 436 | compare(foo.__annotations__, expected_annotations) 437 | 438 | 439 | def test_returns_none(): 440 | # noinspection PyUnusedLocal 441 | @doc( 442 | summary="A function.", 443 | ) 444 | def foo() -> None: 445 | pass 446 | 447 | expected = cleandoc( 448 | """ 449 | A function. 450 | """ 451 | ) 452 | actual = getdoc(foo) 453 | compare(actual, expected) 454 | validate(foo, allow={"RT03"}) 455 | 456 | 457 | def test_returns_unnamed(): 458 | # noinspection PyUnusedLocal 459 | @doc( 460 | summary="A function.", 461 | returns="Amazingly qux.", 462 | ) 463 | def foo(): 464 | return 42 465 | 466 | # this isn't strictly kosher as numpydoc demands type is always given 467 | expected = cleandoc( 468 | """ 469 | A function. 470 | 471 | Returns 472 | ------- 473 | Amazingly qux. 474 | """ 475 | ) 476 | actual = getdoc(foo) 477 | compare(actual, expected) 478 | validate(foo, allow={"RT03"}) 479 | 480 | 481 | def test_returns_unnamed_typed(): 482 | # noinspection PyUnusedLocal 483 | @doc( 484 | summary="A function.", 485 | returns="Amazingly qux.", 486 | ) 487 | def foo() -> int: 488 | return 42 489 | 490 | expected = cleandoc( 491 | """ 492 | A function. 493 | 494 | Returns 495 | ------- 496 | int 497 | Amazingly qux. 498 | """ 499 | ) 500 | actual = getdoc(foo) 501 | compare(actual, expected) 502 | validate(foo) 503 | 504 | 505 | def test_returns_unnamed_typed_auto(): 506 | # noinspection PyUnusedLocal 507 | @doc( 508 | summary="A function.", 509 | ) 510 | def foo() -> int: 511 | return 42 512 | 513 | expected = cleandoc( 514 | """ 515 | A function. 516 | 517 | Returns 518 | ------- 519 | int 520 | """ 521 | ) 522 | actual = getdoc(foo) 523 | compare(actual, expected) 524 | validate(foo, allow={"RT03"}) 525 | 526 | 527 | def test_returns_unnamed_typed_annotated(): 528 | # noinspection PyUnusedLocal 529 | @doc( 530 | summary="A function.", 531 | ) 532 | def foo() -> Annotated[int, "Amazingly qux."]: 533 | return 42 534 | 535 | expected = cleandoc( 536 | """ 537 | A function. 538 | 539 | Returns 540 | ------- 541 | int 542 | Amazingly qux. 543 | """ 544 | ) 545 | actual = getdoc(foo) 546 | compare(actual, expected) 547 | validate(foo) 548 | 549 | # check that annotated types have been stripped 550 | expected_annotations = {"return": int} 551 | compare(foo.__annotations__, expected_annotations) 552 | 553 | 554 | def test_returns_unnamed_multi_typed(): 555 | # noinspection PyUnusedLocal 556 | @doc( 557 | summary="A function.", 558 | ) 559 | def foo() -> Tuple[int, str]: 560 | return 42, "foo" 561 | 562 | expected = cleandoc( 563 | """ 564 | A function. 565 | 566 | Returns 567 | ------- 568 | int 569 | str 570 | """ 571 | ) 572 | actual = getdoc(foo) 573 | compare(actual, expected) 574 | validate(foo, allow=["RT03"]) 575 | 576 | 577 | def test_returns_unnamed_multi_typed_annotated(): 578 | # noinspection PyUnusedLocal 579 | @doc( 580 | summary="A function.", 581 | ) 582 | def foo() -> Tuple[Annotated[int, "The answer."], Annotated[str, "The question."]]: 583 | return 42, "foo" 584 | 585 | expected = cleandoc( 586 | """ 587 | A function. 588 | 589 | Returns 590 | ------- 591 | int 592 | The answer. 593 | str 594 | The question. 595 | """ 596 | ) 597 | actual = getdoc(foo) 598 | compare(actual, expected) 599 | validate(foo) 600 | 601 | # check type annotations are stripped 602 | expected_annotations = {"return": Tuple[int, str]} 603 | compare(foo.__annotations__, expected_annotations) 604 | 605 | 606 | def test_returns_named(): 607 | @doc( 608 | summary="A function.", 609 | returns=dict( 610 | qux="Amazingly qux.", 611 | ), 612 | ) 613 | def foo(): 614 | return 42 615 | 616 | # this isn't strictly kosher as numpydoc demands type is always given 617 | expected = cleandoc( 618 | """ 619 | A function. 620 | 621 | Returns 622 | ------- 623 | qux : 624 | Amazingly qux. 625 | """ 626 | ) 627 | actual = getdoc(foo) 628 | compare(actual, expected) 629 | validate(foo) 630 | 631 | 632 | def test_returns_named_typed(): 633 | # noinspection PyUnusedLocal 634 | @doc( 635 | summary="A function.", 636 | returns=dict( 637 | qux="Amazingly qux.", 638 | ), 639 | ) 640 | def foo() -> int: 641 | return 42 642 | 643 | expected = cleandoc( 644 | """ 645 | A function. 646 | 647 | Returns 648 | ------- 649 | qux : int 650 | Amazingly qux. 651 | """ 652 | ) 653 | actual = getdoc(foo) 654 | compare(actual, expected) 655 | validate(foo) 656 | 657 | 658 | def test_returns_named_multi(): 659 | @doc( 660 | summary="A function.", 661 | returns=dict( 662 | spam="You'll love it.", 663 | eggs="Good with spam.", 664 | ), 665 | ) 666 | def foo(): 667 | return "hello", 42 668 | 669 | expected = cleandoc( 670 | """ 671 | A function. 672 | 673 | Returns 674 | ------- 675 | spam : 676 | You'll love it. 677 | eggs : 678 | Good with spam. 679 | 680 | """ 681 | ) 682 | actual = getdoc(foo) 683 | compare(actual, expected) 684 | validate(foo) 685 | 686 | 687 | def test_returns_named_multi_typed(): 688 | @doc( 689 | summary="A function.", 690 | returns=dict( 691 | spam="You'll love it.", 692 | eggs="Good with spam.", 693 | ), 694 | ) 695 | def foo() -> Tuple[str, int]: 696 | return "hello", 42 697 | 698 | expected = cleandoc( 699 | """ 700 | A function. 701 | 702 | Returns 703 | ------- 704 | spam : str 705 | You'll love it. 706 | eggs : int 707 | Good with spam. 708 | 709 | """ 710 | ) 711 | actual = getdoc(foo) 712 | compare(actual, expected) 713 | validate(foo) 714 | 715 | 716 | def test_returns_named_multi_typed_annotated(): 717 | @doc( 718 | summary="A function.", 719 | returns=("spam", "eggs"), 720 | ) 721 | def foo() -> ( 722 | Tuple[Annotated[str, "You'll love it."], Annotated[int, "Good with spam."]] 723 | ): 724 | return "hello", 42 725 | 726 | expected = cleandoc( 727 | """ 728 | A function. 729 | 730 | Returns 731 | ------- 732 | spam : str 733 | You'll love it. 734 | eggs : int 735 | Good with spam. 736 | 737 | """ 738 | ) 739 | actual = getdoc(foo) 740 | compare(actual, expected) 741 | validate(foo) 742 | 743 | # check type annotations are stripped 744 | expected_annotations = {"return": Tuple[str, int]} 745 | compare(foo.__annotations__, expected_annotations) 746 | 747 | 748 | def test_returns_named_multi_typed_ellipsis(): 749 | @doc( 750 | summary="A function.", 751 | returns=dict( 752 | spam="The more the better.", 753 | ), 754 | ) 755 | def foo() -> Tuple[str, ...]: 756 | return "spam", "spam", "spam", "spam" 757 | 758 | expected = cleandoc( 759 | """ 760 | A function. 761 | 762 | Returns 763 | ------- 764 | spam : tuple of str 765 | The more the better. 766 | 767 | """ 768 | ) 769 | actual = getdoc(foo) 770 | compare(actual, expected) 771 | validate(foo) 772 | 773 | 774 | def test_returns_extra_name(): 775 | # here there are more return values documented than there are types 776 | with pytest.raises(DocumentationError): 777 | # noinspection PyUnusedLocal 778 | @doc( 779 | summary="A function with simple parameters.", 780 | returns=dict( 781 | spam="You'll love it.", 782 | eggs="Good with spam.", 783 | ), 784 | ) 785 | def foo() -> str: 786 | return "dinner" 787 | 788 | 789 | def test_returns_extra_names(): 790 | # here there are more return values documented than there are types 791 | with pytest.raises(DocumentationError): 792 | # noinspection PyUnusedLocal 793 | @doc( 794 | summary="A function with simple parameters.", 795 | returns=dict( 796 | spam="You'll love it.", 797 | eggs="Good with spam.", 798 | toast="Good with eggs.", 799 | ), 800 | ) 801 | def foo() -> Tuple[str, str]: 802 | return "spam", "eggs" 803 | 804 | 805 | def test_returns_extra_type(): 806 | # here there are more return types than return values documented 807 | with pytest.raises(DocumentationError): 808 | # noinspection PyUnusedLocal 809 | @doc( 810 | summary="A function with simple parameters.", 811 | returns=dict( 812 | spam="You'll love it.", 813 | ), 814 | ) 815 | def foo() -> Tuple[str, str]: 816 | return "spam", "eggs" 817 | 818 | 819 | def test_returns_extra_types(): 820 | # here there are more return types than return values documented 821 | with pytest.raises(DocumentationError): 822 | # noinspection PyUnusedLocal 823 | @doc( 824 | summary="A function with simple parameters.", 825 | returns=dict( 826 | spam="You'll love it.", 827 | eggs="Good with spam.", 828 | ), 829 | ) 830 | def foo() -> Tuple[str, str, str]: 831 | return "spam", "eggs", "toast" 832 | 833 | 834 | def test_deprecation(): 835 | # noinspection PyUnusedLocal 836 | @doc( 837 | summary="A deprecated function.", 838 | deprecation=dict( 839 | version="1.6.0", 840 | reason="`ndobj_old` will be removed in NumPy 2.0.0, it is replaced by `ndobj_new` because the latter works also with array subclasses.", # noqa 841 | ), 842 | parameters=dict( 843 | bar="This is very bar.", 844 | baz="This is totally baz.", 845 | ), 846 | returns=dict( 847 | qux="Amazingly qux.", 848 | ), 849 | ) 850 | def foo(bar, baz): 851 | pass 852 | 853 | expected = cleandoc( 854 | """ 855 | A deprecated function. 856 | 857 | .. deprecated:: 1.6.0 858 | `ndobj_old` will be removed in NumPy 2.0.0, it is replaced by 859 | `ndobj_new` because the latter works also with array subclasses. 860 | 861 | Parameters 862 | ---------- 863 | bar : 864 | This is very bar. 865 | baz : 866 | This is totally baz. 867 | 868 | Returns 869 | ------- 870 | qux : 871 | Amazingly qux. 872 | """ 873 | ) 874 | actual = getdoc(foo) 875 | compare(actual, expected) 876 | validate(foo) 877 | 878 | 879 | def test_extended_summary(): 880 | # noinspection PyUnusedLocal 881 | @doc( 882 | summary="A function worth talking about.", 883 | extended_summary="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", # noqa 884 | parameters=dict( 885 | bar="This is very bar.", 886 | baz="This is totally baz.", 887 | ), 888 | returns=dict( 889 | qux="Amazingly qux.", 890 | ), 891 | ) 892 | def foo(bar, baz): 893 | pass 894 | 895 | expected = cleandoc( 896 | """ 897 | A function worth talking about. 898 | 899 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod 900 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim 901 | veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea 902 | commodo consequat. Duis aute irure dolor in reprehenderit in voluptate 903 | velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint 904 | occaecat cupidatat non proident, sunt in culpa qui officia deserunt 905 | mollit anim id est laborum. 906 | 907 | Parameters 908 | ---------- 909 | bar : 910 | This is very bar. 911 | baz : 912 | This is totally baz. 913 | 914 | Returns 915 | ------- 916 | qux : 917 | Amazingly qux. 918 | """ 919 | ) 920 | actual = getdoc(foo) 921 | compare(actual, expected) 922 | validate(foo) 923 | 924 | 925 | def test_method(): 926 | class Foo: 927 | # noinspection PyUnusedLocal 928 | @doc( 929 | summary="A method with simple parameters.", 930 | parameters=dict( 931 | bar="This is very bar.", 932 | baz="This is totally baz.", 933 | ), 934 | returns=dict( 935 | qux="Amazingly qux.", 936 | ), 937 | ) 938 | def m(self, bar, baz): 939 | pass 940 | 941 | foo = Foo() 942 | 943 | expected = cleandoc( 944 | """ 945 | A method with simple parameters. 946 | 947 | Parameters 948 | ---------- 949 | bar : 950 | This is very bar. 951 | baz : 952 | This is totally baz. 953 | 954 | Returns 955 | ------- 956 | qux : 957 | Amazingly qux. 958 | """ 959 | ) 960 | actual = getdoc(Foo.m) 961 | compare(actual, expected) 962 | actual = getdoc(foo.m) 963 | compare(actual, expected) 964 | validate(foo.m) 965 | 966 | 967 | def test_parameters_defaults_untyped(): 968 | # noinspection PyUnusedLocal 969 | @doc( 970 | summary="A function with parameters and default values.", 971 | parameters=dict( 972 | bar="This is very bar.", 973 | baz="This is totally baz.", 974 | qux="Amazingly qux.", 975 | spam="You'll love it.", 976 | eggs="Good with spam.", 977 | ), 978 | ) 979 | def foo(bar, baz="spam", qux=42, spam=True, eggs=None): 980 | pass 981 | 982 | expected = cleandoc( 983 | """ 984 | A function with parameters and default values. 985 | 986 | Parameters 987 | ---------- 988 | bar : 989 | This is very bar. 990 | baz : optional, default: 'spam' 991 | This is totally baz. 992 | qux : optional, default: 42 993 | Amazingly qux. 994 | spam : optional, default: True 995 | You'll love it. 996 | eggs : optional 997 | Good with spam. 998 | 999 | """ 1000 | ) 1001 | actual = getdoc(foo) 1002 | compare(actual, expected) 1003 | # not strictly kosher as parameters are untyped 1004 | validate(foo, allow={"PR02"}) 1005 | 1006 | 1007 | def test_parameters_defaults_typed(): 1008 | # noinspection PyUnusedLocal 1009 | @doc( 1010 | summary="A function with parameters and default values.", 1011 | parameters=dict( 1012 | bar="This is very bar.", 1013 | baz="This is totally baz.", 1014 | qux="Amazingly qux.", 1015 | spam="You'll love it.", 1016 | eggs="Good with spam.", 1017 | ), 1018 | ) 1019 | def foo( 1020 | bar: str, 1021 | baz: str = "spam", 1022 | qux: int = 42, 1023 | spam: bool = True, 1024 | eggs: Optional[Sequence] = None, 1025 | ): 1026 | pass 1027 | 1028 | expected = cleandoc( 1029 | """ 1030 | A function with parameters and default values. 1031 | 1032 | Parameters 1033 | ---------- 1034 | bar : str 1035 | This is very bar. 1036 | baz : str, optional, default: 'spam' 1037 | This is totally baz. 1038 | qux : int, optional, default: 42 1039 | Amazingly qux. 1040 | spam : bool, optional, default: True 1041 | You'll love it. 1042 | eggs : Sequence or None, optional 1043 | Good with spam. 1044 | 1045 | """ 1046 | ) 1047 | actual = getdoc(foo) 1048 | compare(actual, expected) 1049 | validate(foo) 1050 | 1051 | 1052 | def test_var_args_kwargs(): 1053 | # noinspection PyUnusedLocal 1054 | @doc( 1055 | summary="A function with variable parameters.", 1056 | parameters=dict( 1057 | bar="This is very bar.", 1058 | baz="This is totally baz.", 1059 | args="Anything else you feel like.", 1060 | kwargs="Passed through to somewhere else.", 1061 | ), 1062 | ) 1063 | def foo(bar, baz, *args, **kwargs): 1064 | pass 1065 | 1066 | expected = cleandoc( 1067 | """ 1068 | A function with variable parameters. 1069 | 1070 | Parameters 1071 | ---------- 1072 | bar : 1073 | This is very bar. 1074 | baz : 1075 | This is totally baz. 1076 | *args 1077 | Anything else you feel like. 1078 | **kwargs 1079 | Passed through to somewhere else. 1080 | 1081 | """ 1082 | ) 1083 | actual = getdoc(foo) 1084 | compare(actual, expected) 1085 | validate(foo) 1086 | 1087 | 1088 | def test_var_args_kwargs_names(): 1089 | # noinspection PyUnusedLocal 1090 | @doc( 1091 | summary="A function with variable parameters.", 1092 | parameters=dict( 1093 | bar="This is very bar.", 1094 | baz="This is totally baz.", 1095 | arguments="Anything else you feel like.", 1096 | keyword_arguments="Passed through to somewhere else.", 1097 | ), 1098 | ) 1099 | def foo(bar, baz, *arguments, **keyword_arguments): 1100 | pass 1101 | 1102 | expected = cleandoc( 1103 | """ 1104 | A function with variable parameters. 1105 | 1106 | Parameters 1107 | ---------- 1108 | bar : 1109 | This is very bar. 1110 | baz : 1111 | This is totally baz. 1112 | *arguments 1113 | Anything else you feel like. 1114 | **keyword_arguments 1115 | Passed through to somewhere else. 1116 | 1117 | """ 1118 | ) 1119 | actual = getdoc(foo) 1120 | compare(actual, expected) 1121 | validate(foo) 1122 | 1123 | 1124 | def test_keyword_only_args(): 1125 | # noinspection PyUnusedLocal 1126 | @doc( 1127 | summary="A function with keyword only args.", 1128 | parameters=dict( 1129 | bar="This is very bar.", 1130 | baz="This is totally baz.", 1131 | qux="Amazingly qux.", 1132 | ), 1133 | ) 1134 | def foo(bar, *, baz, qux): 1135 | pass 1136 | 1137 | expected = cleandoc( 1138 | """ 1139 | A function with keyword only args. 1140 | 1141 | Parameters 1142 | ---------- 1143 | bar : 1144 | This is very bar. 1145 | baz : 1146 | This is totally baz. 1147 | qux : 1148 | Amazingly qux. 1149 | 1150 | """ 1151 | ) 1152 | actual = getdoc(foo) 1153 | compare(actual, expected) 1154 | validate(foo) 1155 | 1156 | 1157 | def test_yields_unnamed(): 1158 | # noinspection PyUnusedLocal 1159 | @doc( 1160 | summary="A function.", 1161 | yields="Amazingly qux.", 1162 | ) 1163 | def foo(): 1164 | yield 42 1165 | 1166 | # this isn't strictly kosher as numpydoc demands type is always given 1167 | expected = cleandoc( 1168 | """ 1169 | A function. 1170 | 1171 | Yields 1172 | ------ 1173 | Amazingly qux. 1174 | """ 1175 | ) 1176 | actual = getdoc(foo) 1177 | compare(actual, expected) 1178 | validate(foo) 1179 | 1180 | 1181 | def test_yields_unnamed_typed_bare(): 1182 | # noinspection PyUnusedLocal 1183 | @doc( 1184 | summary="A function.", 1185 | yields="Amazingly qux.", 1186 | ) 1187 | def foo() -> Iterable: 1188 | yield 42 1189 | 1190 | expected = cleandoc( 1191 | """ 1192 | A function. 1193 | 1194 | Yields 1195 | ------ 1196 | Amazingly qux. 1197 | """ 1198 | ) 1199 | actual = getdoc(foo) 1200 | compare(actual, expected) 1201 | validate(foo) 1202 | 1203 | 1204 | @pytest.mark.parametrize("T", [Iterator, Iterable]) 1205 | def test_yields_unnamed_typed_iterator(T): 1206 | # noinspection PyUnresolvedReferences 1207 | @doc( 1208 | summary="A function.", 1209 | yields="Amazingly qux.", 1210 | ) 1211 | def foo() -> T[int]: 1212 | yield 42 1213 | 1214 | expected = cleandoc( 1215 | """ 1216 | A function. 1217 | 1218 | Yields 1219 | ------ 1220 | int 1221 | Amazingly qux. 1222 | """ 1223 | ) 1224 | actual = getdoc(foo) 1225 | compare(actual, expected) 1226 | validate(foo) 1227 | 1228 | 1229 | def test_yields_unnamed_typed_generator(): 1230 | # noinspection PyUnusedLocal 1231 | @doc( 1232 | summary="A function.", 1233 | yields="Amazingly qux.", 1234 | ) 1235 | def foo() -> Generator[int, None, None]: 1236 | yield 42 1237 | 1238 | expected = cleandoc( 1239 | """ 1240 | A function. 1241 | 1242 | Yields 1243 | ------ 1244 | int 1245 | Amazingly qux. 1246 | """ 1247 | ) 1248 | actual = getdoc(foo) 1249 | compare(actual, expected) 1250 | validate(foo) 1251 | 1252 | 1253 | def test_yields_named(): 1254 | @doc( 1255 | summary="A function.", 1256 | yields=dict( 1257 | bar="This is very bar.", 1258 | ), 1259 | ) 1260 | def foo(): 1261 | yield 42 1262 | 1263 | # this isn't strictly kosher as numpydoc demands type is always given 1264 | expected = cleandoc( 1265 | """ 1266 | A function. 1267 | 1268 | Yields 1269 | ------ 1270 | bar : 1271 | This is very bar. 1272 | 1273 | """ 1274 | ) 1275 | actual = getdoc(foo) 1276 | compare(actual, expected) 1277 | validate(foo) 1278 | 1279 | 1280 | def test_yields_named_typed(): 1281 | # noinspection PyUnusedLocal 1282 | @doc( 1283 | summary="A function.", 1284 | yields=dict( 1285 | bar="This is very bar.", 1286 | ), 1287 | ) 1288 | def foo() -> Iterable[int]: 1289 | yield 42 1290 | 1291 | expected = cleandoc( 1292 | """ 1293 | A function. 1294 | 1295 | Yields 1296 | ------ 1297 | bar : int 1298 | This is very bar. 1299 | 1300 | """ 1301 | ) 1302 | actual = getdoc(foo) 1303 | compare(actual, expected) 1304 | validate(foo) 1305 | 1306 | 1307 | def test_yields_named_multi(): 1308 | @doc( 1309 | summary="A function.", 1310 | yields=dict( 1311 | spam="You'll love it.", 1312 | eggs="Good with spam.", 1313 | ), 1314 | ) 1315 | def foo(): 1316 | yield "hello", 42 1317 | 1318 | expected = cleandoc( 1319 | """ 1320 | A function. 1321 | 1322 | Yields 1323 | ------ 1324 | spam : 1325 | You'll love it. 1326 | eggs : 1327 | Good with spam. 1328 | 1329 | """ 1330 | ) 1331 | actual = getdoc(foo) 1332 | compare(actual, expected) 1333 | validate(foo) 1334 | 1335 | 1336 | def test_yields_named_multi_typed(): 1337 | @doc( 1338 | summary="A function.", 1339 | yields=dict( 1340 | spam="You'll love it.", 1341 | eggs="Good with spam.", 1342 | ), 1343 | ) 1344 | def foo() -> Iterable[Tuple[str, int]]: 1345 | yield "hello", 42 1346 | 1347 | expected = cleandoc( 1348 | """ 1349 | A function. 1350 | 1351 | Yields 1352 | ------ 1353 | spam : str 1354 | You'll love it. 1355 | eggs : int 1356 | Good with spam. 1357 | 1358 | """ 1359 | ) 1360 | actual = getdoc(foo) 1361 | compare(actual, expected) 1362 | validate(foo) 1363 | 1364 | 1365 | def test_yields_named_multi_typed_ellipsis(): 1366 | @doc( 1367 | summary="A function.", 1368 | yields=dict( 1369 | spam="The more the better.", 1370 | ), 1371 | ) 1372 | def foo() -> Iterable[Tuple[str, ...]]: 1373 | yield "spam", "spam", "spam", "spam" 1374 | 1375 | expected = cleandoc( 1376 | """ 1377 | A function. 1378 | 1379 | Yields 1380 | ------ 1381 | spam : tuple of str 1382 | The more the better. 1383 | 1384 | """ 1385 | ) 1386 | actual = getdoc(foo) 1387 | compare(actual, expected) 1388 | validate(foo) 1389 | 1390 | 1391 | def test_yields_extra_name(): 1392 | # here there are more yield values documented than there are types 1393 | with pytest.raises(DocumentationError): 1394 | # noinspection PyUnusedLocal 1395 | @doc( 1396 | summary="A function with simple parameters.", 1397 | yields=dict( 1398 | spam="You'll love it.", 1399 | eggs="Good with spam.", 1400 | ), 1401 | ) 1402 | def foo() -> Iterable[str]: 1403 | yield "dinner" 1404 | 1405 | 1406 | def test_yields_extra_names(): 1407 | # here there are more yield values documented than there are types 1408 | with pytest.raises(DocumentationError): 1409 | # noinspection PyUnusedLocal 1410 | @doc( 1411 | summary="A function with simple parameters.", 1412 | yields=dict( 1413 | spam="You'll love it.", 1414 | eggs="Good with spam.", 1415 | toast="Good with eggs.", 1416 | ), 1417 | ) 1418 | def foo() -> Iterable[Tuple[str, str]]: 1419 | yield "spam", "eggs" 1420 | 1421 | 1422 | def test_yields_extra_type(): 1423 | # here there are more yield types than yield values documented 1424 | with pytest.raises(DocumentationError): 1425 | # noinspection PyUnusedLocal 1426 | @doc( 1427 | summary="A function with simple parameters.", 1428 | yields=dict( 1429 | spam="You'll love it.", 1430 | ), 1431 | ) 1432 | def foo() -> Iterable[Tuple[str, str]]: 1433 | yield "spam", "eggs" 1434 | 1435 | 1436 | def test_yields_extra_types(): 1437 | # here there are more yield types than yield values documented 1438 | with pytest.raises(DocumentationError): 1439 | # noinspection PyUnusedLocal 1440 | @doc( 1441 | summary="A function with simple parameters.", 1442 | yields=dict( 1443 | spam="You'll love it.", 1444 | eggs="Good with spam.", 1445 | ), 1446 | ) 1447 | def foo() -> Iterable[Tuple[str, str, str]]: 1448 | yield "spam", "eggs", "toast" 1449 | 1450 | 1451 | def test_yields_bad_type(): 1452 | with pytest.raises(DocumentationError): 1453 | # noinspection PyUnusedLocal 1454 | @doc( 1455 | summary="A function with simple parameters.", 1456 | yields=dict( 1457 | spam="You'll love it.", 1458 | ), 1459 | ) 1460 | def foo() -> Tuple[str, str]: 1461 | return "spam", "eggs" 1462 | 1463 | 1464 | def test_returns_yields(): 1465 | with pytest.raises(DocumentationError): 1466 | # noinspection PyUnusedLocal 1467 | @doc( 1468 | summary="A function with simple parameters.", 1469 | returns=dict( 1470 | spam="You'll love it.", 1471 | ), 1472 | yields=dict( 1473 | spam="You'll love it.", 1474 | ), 1475 | ) 1476 | def foo() -> Iterable[str]: 1477 | yield "spam" 1478 | yield "eggs" 1479 | yield "toast" 1480 | 1481 | 1482 | def test_receives_unnamed(): 1483 | # noinspection PyUnusedLocal 1484 | @doc( 1485 | summary="A function.", 1486 | yields="Egg, bacon, sausage, and Spam.", 1487 | receives="Lobster thermidor.", 1488 | ) 1489 | def foo(): 1490 | x = yield 42 # noqa 1491 | 1492 | # this isn't strictly kosher as numpydoc demands type is always given 1493 | expected = cleandoc( 1494 | """ 1495 | A function. 1496 | 1497 | Yields 1498 | ------ 1499 | Egg, bacon, sausage, and Spam. 1500 | 1501 | Receives 1502 | -------- 1503 | Lobster thermidor. 1504 | 1505 | """ 1506 | ) 1507 | actual = getdoc(foo) 1508 | compare(actual, expected) 1509 | # receives section not recognised by numpydoc validate 1510 | validate(foo, allow={"GL06", "GL07"}) 1511 | 1512 | 1513 | def test_receives_unnamed_typed_bare(): 1514 | # noinspection PyUnusedLocal 1515 | @doc( 1516 | summary="A function.", 1517 | yields="Egg, bacon, sausage, and Spam.", 1518 | receives="Lobster thermidor.", 1519 | ) 1520 | def foo() -> Generator: 1521 | x = yield 42 # noqa 1522 | 1523 | expected = cleandoc( 1524 | """ 1525 | A function. 1526 | 1527 | Yields 1528 | ------ 1529 | Egg, bacon, sausage, and Spam. 1530 | 1531 | Receives 1532 | -------- 1533 | Lobster thermidor. 1534 | 1535 | """ 1536 | ) 1537 | actual = getdoc(foo) 1538 | compare(actual, expected) 1539 | # receives section not recognised by numpydoc validate 1540 | validate(foo, allow={"GL06", "GL07"}) 1541 | 1542 | 1543 | def test_receives_unnamed_typed(): 1544 | # noinspection PyUnusedLocal 1545 | @doc( 1546 | summary="A function.", 1547 | yields="Egg, bacon, sausage, and Spam.", 1548 | receives="Lobster thermidor.", 1549 | ) 1550 | def foo() -> Generator[int, float, None]: 1551 | x = yield 42 # noqa 1552 | 1553 | expected = cleandoc( 1554 | """ 1555 | A function. 1556 | 1557 | Yields 1558 | ------ 1559 | int 1560 | Egg, bacon, sausage, and Spam. 1561 | 1562 | Receives 1563 | -------- 1564 | float 1565 | Lobster thermidor. 1566 | 1567 | """ 1568 | ) 1569 | actual = getdoc(foo) 1570 | compare(actual, expected) 1571 | # receives section not recognised by numpydoc validate 1572 | validate(foo, allow={"GL06", "GL07"}) 1573 | 1574 | 1575 | def test_receives_named(): 1576 | @doc( 1577 | summary="A function.", 1578 | yields=dict( 1579 | bar="This is very bar.", 1580 | ), 1581 | receives=dict( 1582 | baz="This is totally baz.", 1583 | ), 1584 | ) 1585 | def foo(): 1586 | x = yield 42 # noqa 1587 | 1588 | # this isn't strictly kosher as numpydoc demands type is always given 1589 | expected = cleandoc( 1590 | """ 1591 | A function. 1592 | 1593 | Yields 1594 | ------ 1595 | bar : 1596 | This is very bar. 1597 | 1598 | Receives 1599 | -------- 1600 | baz : 1601 | This is totally baz. 1602 | 1603 | """ 1604 | ) 1605 | actual = getdoc(foo) 1606 | compare(actual, expected) 1607 | # receives section not recognised by numpydoc validate 1608 | validate(foo, allow={"GL06", "GL07"}) 1609 | 1610 | 1611 | def test_receives_named_typed(): 1612 | # noinspection PyUnusedLocal 1613 | @doc( 1614 | summary="A function.", 1615 | yields=dict( 1616 | bar="This is very bar.", 1617 | ), 1618 | receives=dict( 1619 | baz="This is totally baz.", 1620 | ), 1621 | ) 1622 | def foo() -> Generator[int, float, None]: 1623 | x = yield 42 # noqa 1624 | 1625 | expected = cleandoc( 1626 | """ 1627 | A function. 1628 | 1629 | Yields 1630 | ------ 1631 | bar : int 1632 | This is very bar. 1633 | 1634 | Receives 1635 | -------- 1636 | baz : float 1637 | This is totally baz. 1638 | 1639 | """ 1640 | ) 1641 | actual = getdoc(foo) 1642 | compare(actual, expected) 1643 | # receives section not recognised by numpydoc validate 1644 | validate(foo, allow={"GL06", "GL07"}) 1645 | 1646 | 1647 | def test_receives_named_multi(): 1648 | @doc( 1649 | summary="A function.", 1650 | yields=dict( 1651 | bar="This is very bar.", 1652 | baz="This is totally baz.", 1653 | ), 1654 | receives=dict( 1655 | spam="You'll love it.", 1656 | eggs="Good with spam.", 1657 | ), 1658 | ) 1659 | def foo(): 1660 | _, _ = yield "hello", 42 1661 | 1662 | expected = cleandoc( 1663 | """ 1664 | A function. 1665 | 1666 | Yields 1667 | ------ 1668 | bar : 1669 | This is very bar. 1670 | baz : 1671 | This is totally baz. 1672 | 1673 | Receives 1674 | -------- 1675 | spam : 1676 | You'll love it. 1677 | eggs : 1678 | Good with spam. 1679 | 1680 | """ 1681 | ) 1682 | actual = getdoc(foo) 1683 | compare(actual, expected) 1684 | # receives section not recognised by numpydoc validate 1685 | validate(foo, allow={"GL06", "GL07"}) 1686 | 1687 | 1688 | def test_receives_named_multi_typed(): 1689 | @doc( 1690 | summary="A function.", 1691 | yields=dict( 1692 | bar="This is very bar.", 1693 | baz="This is totally baz.", 1694 | ), 1695 | receives=dict( 1696 | spam="You'll love it.", 1697 | eggs="Good with spam.", 1698 | ), 1699 | ) 1700 | def foo() -> Generator[Tuple[str, int], Tuple[float, bool], None]: 1701 | _, _ = yield "hello", 42 1702 | 1703 | expected = cleandoc( 1704 | """ 1705 | A function. 1706 | 1707 | Yields 1708 | ------ 1709 | bar : str 1710 | This is very bar. 1711 | baz : int 1712 | This is totally baz. 1713 | 1714 | Receives 1715 | -------- 1716 | spam : float 1717 | You'll love it. 1718 | eggs : bool 1719 | Good with spam. 1720 | 1721 | """ 1722 | ) 1723 | actual = getdoc(foo) 1724 | compare(actual, expected) 1725 | # receives section not recognised by numpydoc validate 1726 | validate(foo, allow={"GL06", "GL07"}) 1727 | 1728 | 1729 | def test_receives_named_multi_typed_ellipsis(): 1730 | @doc( 1731 | summary="A function.", 1732 | yields=dict( 1733 | spam="The more the better.", 1734 | ), 1735 | receives=dict(eggs="Good with spam."), 1736 | ) 1737 | def foo() -> Generator[Tuple[str, ...], Tuple[float, ...], None]: 1738 | _ = yield "spam", "spam", "spam", "spam" # noqa 1739 | 1740 | expected = cleandoc( 1741 | """ 1742 | A function. 1743 | 1744 | Yields 1745 | ------ 1746 | spam : tuple of str 1747 | The more the better. 1748 | 1749 | Receives 1750 | -------- 1751 | eggs : tuple of float 1752 | Good with spam. 1753 | 1754 | """ 1755 | ) 1756 | actual = getdoc(foo) 1757 | compare(actual, expected) 1758 | # receives section not recognised by numpydoc validate 1759 | validate(foo, allow={"GL06", "GL07"}) 1760 | 1761 | 1762 | def test_receives_extra_name(): 1763 | # here there are more receive values documented than there are types 1764 | with pytest.raises(DocumentationError): 1765 | # noinspection PyUnusedLocal 1766 | @doc( 1767 | summary="A function with simple parameters.", 1768 | yields=dict(bar="Totally bar."), 1769 | receives=dict( 1770 | spam="You'll love it.", 1771 | eggs="Good with spam.", 1772 | ), 1773 | ) 1774 | def foo() -> Generator[str, float, None]: 1775 | x = yield "dinner" # noqa 1776 | 1777 | 1778 | def test_receives_extra_names(): 1779 | # here there are more yield values documented than there are types 1780 | with pytest.raises(DocumentationError): 1781 | # noinspection PyUnusedLocal 1782 | @doc( 1783 | summary="A function with simple parameters.", 1784 | yields=dict(bar="Totally bar."), 1785 | receives=dict( 1786 | spam="You'll love it.", 1787 | eggs="Good with spam.", 1788 | bacon="Good with eggs.", 1789 | ), 1790 | ) 1791 | def foo() -> Generator[int, Tuple[str, str], None]: 1792 | foo, bar = yield 42 1793 | spam, eggs = yield 84 1794 | 1795 | 1796 | def test_receives_extra_type(): 1797 | # here there are more yield types than yield values documented 1798 | with pytest.raises(DocumentationError): 1799 | # noinspection PyUnusedLocal 1800 | @doc( 1801 | summary="A function with simple parameters.", 1802 | yields=dict(bar="Totally bar."), 1803 | receives=dict( 1804 | spam="You'll love it.", 1805 | ), 1806 | ) 1807 | def foo() -> Generator[str, Tuple[str, str], None]: 1808 | x = yield "spam" # noqa 1809 | 1810 | 1811 | def test_receives_extra_types(): 1812 | # here there are more receive types than yield values documented 1813 | with pytest.raises(DocumentationError): 1814 | # noinspection PyUnusedLocal 1815 | @doc( 1816 | summary="A function with simple parameters.", 1817 | yields=dict(bar="Totally bar."), 1818 | receives=dict( 1819 | spam="You'll love it.", 1820 | eggs="Good with spam.", 1821 | ), 1822 | ) 1823 | def foo() -> Generator[str, Tuple[str, str, str], None]: 1824 | x = yield "spam" # noqa 1825 | 1826 | 1827 | def test_receives_no_yields(): 1828 | with pytest.raises(DocumentationError): 1829 | # noinspection PyUnusedLocal 1830 | @doc( 1831 | summary="A function with simple parameters.", 1832 | receives=dict( 1833 | spam="You'll love it.", 1834 | ), 1835 | ) 1836 | def foo() -> Generator[str, str, None]: 1837 | x = yield "spam" # noqa 1838 | 1839 | 1840 | def test_other_parameters(): 1841 | # noinspection PyUnusedLocal 1842 | @doc( 1843 | summary="A function with other parameters.", 1844 | parameters=dict( 1845 | bar="This is very bar.", 1846 | baz="This is totally baz.", 1847 | ), 1848 | other_parameters=dict( 1849 | spam="You'll love it.", 1850 | eggs="Good with spam.", 1851 | ), 1852 | returns=dict( 1853 | qux="Amazingly qux.", 1854 | ), 1855 | ) 1856 | def foo(bar, baz, spam, eggs): 1857 | pass 1858 | 1859 | expected = cleandoc( 1860 | """ 1861 | A function with other parameters. 1862 | 1863 | Parameters 1864 | ---------- 1865 | bar : 1866 | This is very bar. 1867 | baz : 1868 | This is totally baz. 1869 | 1870 | Returns 1871 | ------- 1872 | qux : 1873 | Amazingly qux. 1874 | 1875 | Other Parameters 1876 | ---------------- 1877 | spam : 1878 | You'll love it. 1879 | eggs : 1880 | Good with spam. 1881 | 1882 | """ 1883 | ) 1884 | actual = getdoc(foo) 1885 | compare(actual, expected) 1886 | validate(foo) 1887 | 1888 | 1889 | def test_other_parameters_typed(): 1890 | # noinspection PyUnusedLocal 1891 | @doc( 1892 | summary="A function with other parameters.", 1893 | parameters=dict( 1894 | bar="This is very bar.", 1895 | baz="This is totally baz.", 1896 | ), 1897 | other_parameters=dict( 1898 | spam="You'll love it.", 1899 | eggs="Good with spam.", 1900 | ), 1901 | returns=dict( 1902 | qux="Amazingly qux.", 1903 | ), 1904 | ) 1905 | def foo(bar: int, baz: str, spam: float, eggs: Tuple[int]) -> bool: 1906 | return True 1907 | 1908 | expected = cleandoc( 1909 | """ 1910 | A function with other parameters. 1911 | 1912 | Parameters 1913 | ---------- 1914 | bar : int 1915 | This is very bar. 1916 | baz : str 1917 | This is totally baz. 1918 | 1919 | Returns 1920 | ------- 1921 | qux : bool 1922 | Amazingly qux. 1923 | 1924 | Other Parameters 1925 | ---------------- 1926 | spam : float 1927 | You'll love it. 1928 | eggs : tuple[int] 1929 | Good with spam. 1930 | 1931 | """ 1932 | ) 1933 | actual = getdoc(foo) 1934 | compare(actual, expected) 1935 | validate(foo) 1936 | 1937 | 1938 | def test_extra_other_param(): 1939 | # noinspection PyUnusedLocal 1940 | @doc( 1941 | summary="A function with other parameters.", 1942 | parameters=dict( 1943 | bar="This is very bar.", 1944 | baz="This is totally baz.", 1945 | ), 1946 | returns=dict( 1947 | qux="Amazingly qux.", 1948 | ), 1949 | other_parameters=dict( 1950 | spam="You'll love it.", 1951 | eggs="Good with spam.", 1952 | ), 1953 | ) 1954 | def foo(bar, baz, spam): 1955 | pass 1956 | 1957 | expected = cleandoc( 1958 | """ 1959 | A function with other parameters. 1960 | 1961 | Parameters 1962 | ---------- 1963 | bar : 1964 | This is very bar. 1965 | baz : 1966 | This is totally baz. 1967 | 1968 | Returns 1969 | ------- 1970 | qux : 1971 | Amazingly qux. 1972 | 1973 | Other Parameters 1974 | ---------------- 1975 | spam : 1976 | You'll love it. 1977 | 1978 | """ 1979 | ) 1980 | actual = getdoc(foo) 1981 | compare(actual, expected) 1982 | validate(foo) 1983 | 1984 | 1985 | def test_missing_other_param(): 1986 | with pytest.raises(DocumentationError): 1987 | # noinspection PyUnusedLocal 1988 | @doc( 1989 | summary="A function with simple parameters.", 1990 | parameters=dict( 1991 | bar="This is very bar.", 1992 | baz="This is totally baz.", 1993 | ), 1994 | returns=dict( 1995 | qux="Amazingly qux.", 1996 | ), 1997 | other_parameters=dict( 1998 | spam="You'll love it.", 1999 | # eggs param is missing 2000 | ), 2001 | ) 2002 | def foo(bar, baz, spam, eggs): 2003 | pass 2004 | 2005 | 2006 | def test_raises_warns_warnings(): 2007 | # noinspection PyUnusedLocal 2008 | @doc( 2009 | summary="A function with simple parameters.", 2010 | parameters=dict( 2011 | bar="This is very bar.", 2012 | baz="This is totally baz.", 2013 | ), 2014 | returns=dict( 2015 | qux="Amazingly qux.", 2016 | ), 2017 | raises=dict( 2018 | FileNotFoundError="If the file isn't there.", 2019 | OSError="If something really bad goes wrong.", 2020 | ), 2021 | warns=dict( 2022 | UserWarning="If you do something silly.", 2023 | ResourceWarning="If you use too much memory.", 2024 | ), 2025 | warnings="Here be dragons!", 2026 | ) 2027 | def foo(bar, baz): 2028 | pass 2029 | 2030 | expected = cleandoc( 2031 | """ 2032 | A function with simple parameters. 2033 | 2034 | Parameters 2035 | ---------- 2036 | bar : 2037 | This is very bar. 2038 | baz : 2039 | This is totally baz. 2040 | 2041 | Returns 2042 | ------- 2043 | qux : 2044 | Amazingly qux. 2045 | 2046 | Raises 2047 | ------ 2048 | FileNotFoundError 2049 | If the file isn't there. 2050 | OSError 2051 | If something really bad goes wrong. 2052 | 2053 | Warns 2054 | ----- 2055 | UserWarning 2056 | If you do something silly. 2057 | ResourceWarning 2058 | If you use too much memory. 2059 | 2060 | Warnings 2061 | -------- 2062 | Here be dragons! 2063 | 2064 | """ 2065 | ) 2066 | actual = getdoc(foo) 2067 | compare(actual, expected) 2068 | validate(foo) 2069 | 2070 | 2071 | def test_see_also_string(): 2072 | # noinspection PyUnusedLocal 2073 | @doc( 2074 | summary="A function with simple parameters.", 2075 | parameters=dict( 2076 | bar="This is very bar.", 2077 | baz="This is totally baz.", 2078 | ), 2079 | returns=dict( 2080 | qux="Amazingly qux.", 2081 | ), 2082 | see_also="spam", 2083 | ) 2084 | def foo(bar, baz): 2085 | pass 2086 | 2087 | expected = cleandoc( 2088 | """ 2089 | A function with simple parameters. 2090 | 2091 | Parameters 2092 | ---------- 2093 | bar : 2094 | This is very bar. 2095 | baz : 2096 | This is totally baz. 2097 | 2098 | Returns 2099 | ------- 2100 | qux : 2101 | Amazingly qux. 2102 | 2103 | See Also 2104 | -------- 2105 | spam 2106 | 2107 | """ 2108 | ) 2109 | actual = getdoc(foo) 2110 | compare(actual, expected) 2111 | validate(foo) 2112 | 2113 | 2114 | def test_see_also_sequence(): 2115 | # noinspection PyUnusedLocal 2116 | @doc( 2117 | summary="A function with simple parameters.", 2118 | parameters=dict( 2119 | bar="This is very bar.", 2120 | baz="This is totally baz.", 2121 | ), 2122 | returns=dict( 2123 | qux="Amazingly qux.", 2124 | ), 2125 | see_also=["spam", "eggs", print], 2126 | ) 2127 | def foo(bar, baz): 2128 | pass 2129 | 2130 | expected = cleandoc( 2131 | """ 2132 | A function with simple parameters. 2133 | 2134 | Parameters 2135 | ---------- 2136 | bar : 2137 | This is very bar. 2138 | baz : 2139 | This is totally baz. 2140 | 2141 | Returns 2142 | ------- 2143 | qux : 2144 | Amazingly qux. 2145 | 2146 | See Also 2147 | -------- 2148 | spam 2149 | eggs 2150 | print 2151 | 2152 | """ 2153 | ) 2154 | actual = getdoc(foo) 2155 | compare(actual, expected) 2156 | validate(foo) 2157 | 2158 | 2159 | def test_see_also_mapping(): 2160 | # noinspection PyUnusedLocal 2161 | @doc( 2162 | summary="A function with simple parameters.", 2163 | parameters=dict( 2164 | bar="This is very bar.", 2165 | baz="This is totally baz.", 2166 | ), 2167 | returns=dict( 2168 | qux="Amazingly qux.", 2169 | ), 2170 | see_also=dict( 2171 | spam="You'll love it.", 2172 | eggs="Good with spam.", 2173 | bacon=None, 2174 | lobster_thermidor="Aux crevettes with a Mornay sauce, garnished with truffle pâté, brandy, and a fried egg on top, and Spam.", # noqa 2175 | ), 2176 | ) 2177 | def foo(bar, baz): 2178 | pass 2179 | 2180 | expected = cleandoc( 2181 | """ 2182 | A function with simple parameters. 2183 | 2184 | Parameters 2185 | ---------- 2186 | bar : 2187 | This is very bar. 2188 | baz : 2189 | This is totally baz. 2190 | 2191 | Returns 2192 | ------- 2193 | qux : 2194 | Amazingly qux. 2195 | 2196 | See Also 2197 | -------- 2198 | spam : You'll love it. 2199 | eggs : Good with spam. 2200 | bacon 2201 | lobster_thermidor : 2202 | Aux crevettes with a Mornay sauce, garnished with truffle pâté, 2203 | brandy, and a fried egg on top, and Spam. 2204 | 2205 | """ 2206 | ) 2207 | actual = getdoc(foo) 2208 | compare(actual, expected) 2209 | validate(foo) 2210 | 2211 | 2212 | def test_notes(): 2213 | # noinspection PyUnusedLocal 2214 | @doc( 2215 | summary="A function with simple parameters.", 2216 | parameters=dict( 2217 | bar="This is very bar.", 2218 | baz="This is totally baz.", 2219 | ), 2220 | returns=dict( 2221 | qux="Amazingly qux.", 2222 | ), 2223 | notes=r""" 2224 | The FFT is a fast implementation of the discrete Fourier transform: 2225 | 2226 | .. math:: X(e^{j\omega } ) = x(n)e^{ - j\omega n} 2227 | 2228 | The discrete-time Fourier time-convolution property states that: 2229 | 2230 | .. math:: 2231 | 2232 | x(n) * y(n) \Leftrightarrow X(e^{j\omega } )Y(e^{j\omega } ) 2233 | 2234 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 2235 | 2236 | The value of :math:`\omega` is larger than 5. 2237 | """, # noqa 2238 | ) 2239 | def foo(bar, baz): 2240 | pass 2241 | 2242 | expected = cleandoc( 2243 | r""" 2244 | A function with simple parameters. 2245 | 2246 | Parameters 2247 | ---------- 2248 | bar : 2249 | This is very bar. 2250 | baz : 2251 | This is totally baz. 2252 | 2253 | Returns 2254 | ------- 2255 | qux : 2256 | Amazingly qux. 2257 | 2258 | Notes 2259 | ----- 2260 | The FFT is a fast implementation of the discrete Fourier transform: 2261 | 2262 | .. math:: X(e^{j\omega } ) = x(n)e^{ - j\omega n} 2263 | 2264 | The discrete-time Fourier time-convolution property states that: 2265 | 2266 | .. math:: 2267 | 2268 | x(n) * y(n) \Leftrightarrow X(e^{j\omega } )Y(e^{j\omega } ) 2269 | 2270 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do 2271 | eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad 2272 | minim veniam, quis nostrud exercitation ullamco laboris nisi ut 2273 | aliquip ex ea commodo consequat. Duis aute irure dolor in 2274 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla 2275 | pariatur. Excepteur sint occaecat cupidatat non proident, sunt in 2276 | culpa qui officia deserunt mollit anim id est laborum. 2277 | 2278 | The value of :math:`\omega` is larger than 5. 2279 | 2280 | """ 2281 | ) 2282 | actual = getdoc(foo) 2283 | compare(actual, expected) 2284 | validate(foo) 2285 | 2286 | 2287 | def test_references(): 2288 | # noinspection PyUnusedLocal 2289 | @doc( 2290 | summary="A function with simple parameters.", 2291 | parameters=dict( 2292 | bar="This is very bar.", 2293 | baz="This is totally baz.", 2294 | ), 2295 | returns=dict( 2296 | qux="Amazingly qux.", 2297 | ), 2298 | notes=""" 2299 | The algorithm is cool and is described in [1]_ and [CIT2002]_. 2300 | """, 2301 | references={ 2302 | "1": 'O. McNoleg, "The integration of GIS, remote sensing, expert systems and adaptive co-kriging for environmental habitat modelling of the Highland Haggis using object-oriented, fuzzy-logic and neural-network techniques," Computers & Geosciences, vol. 22, pp. 585-588, 1996.', # noqa 2303 | "CIT2002": "Book or article reference, URL or whatever.", 2304 | }, 2305 | ) 2306 | def foo(bar, baz): 2307 | pass 2308 | 2309 | expected = cleandoc( 2310 | """ 2311 | A function with simple parameters. 2312 | 2313 | Parameters 2314 | ---------- 2315 | bar : 2316 | This is very bar. 2317 | baz : 2318 | This is totally baz. 2319 | 2320 | Returns 2321 | ------- 2322 | qux : 2323 | Amazingly qux. 2324 | 2325 | Notes 2326 | ----- 2327 | The algorithm is cool and is described in [1]_ and [CIT2002]_. 2328 | 2329 | References 2330 | ---------- 2331 | .. [1] O. McNoleg, "The integration of GIS, remote sensing, expert systems 2332 | and adaptive co-kriging for environmental habitat modelling of the 2333 | Highland Haggis using object-oriented, fuzzy-logic and neural- 2334 | network techniques," Computers & Geosciences, vol. 22, pp. 585-588, 2335 | 1996. 2336 | .. [CIT2002] Book or article reference, URL or whatever. 2337 | 2338 | """ 2339 | ) 2340 | actual = getdoc(foo) 2341 | compare(actual, expected) 2342 | validate(foo) 2343 | 2344 | 2345 | def test_examples(): 2346 | # noinspection PyUnusedLocal 2347 | @doc( 2348 | summary="A function with simple parameters.", 2349 | parameters=dict( 2350 | bar="This is very bar.", 2351 | baz="This is totally baz.", 2352 | ), 2353 | returns=dict( 2354 | qux="Amazingly qux.", 2355 | ), 2356 | examples=""" 2357 | These are written in doctest format, and should illustrate how to use the function. 2358 | 2359 | >>> a = [1, 2, 3] 2360 | >>> print([x + 3 for x in a]) 2361 | [4, 5, 6] 2362 | >>> print("a\\nb") 2363 | a 2364 | b 2365 | 2366 | Here are some more examples. 2367 | 2368 | >>> np.add(1, 2) 2369 | 3 2370 | 2371 | Comment explaining the second example. 2372 | 2373 | >>> np.add([1, 2], [3, 4]) 2374 | array([4, 6]) 2375 | 2376 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 2377 | 2378 | >>> np.add([[1, 2], [3, 4]], [[5, 6], [7, 8]]) 2379 | array([[ 6, 8], 2380 | [10, 12]]) 2381 | 2382 | """, # noqa 2383 | ) 2384 | def foo(bar, baz): 2385 | pass 2386 | 2387 | expected = cleandoc( 2388 | """ 2389 | A function with simple parameters. 2390 | 2391 | Parameters 2392 | ---------- 2393 | bar : 2394 | This is very bar. 2395 | baz : 2396 | This is totally baz. 2397 | 2398 | Returns 2399 | ------- 2400 | qux : 2401 | Amazingly qux. 2402 | 2403 | Examples 2404 | -------- 2405 | These are written in doctest format, and should illustrate how to use 2406 | the function. 2407 | 2408 | >>> a = [1, 2, 3] 2409 | >>> print([x + 3 for x in a]) 2410 | [4, 5, 6] 2411 | >>> print("a\\nb") 2412 | a 2413 | b 2414 | 2415 | Here are some more examples. 2416 | 2417 | >>> np.add(1, 2) 2418 | 3 2419 | 2420 | Comment explaining the second example. 2421 | 2422 | >>> np.add([1, 2], [3, 4]) 2423 | array([4, 6]) 2424 | 2425 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do 2426 | eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad 2427 | minim veniam, quis nostrud exercitation ullamco laboris nisi ut 2428 | aliquip ex ea commodo consequat. Duis aute irure dolor in 2429 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla 2430 | pariatur. Excepteur sint occaecat cupidatat non proident, sunt in 2431 | culpa qui officia deserunt mollit anim id est laborum. 2432 | 2433 | >>> np.add([[1, 2], [3, 4]], [[5, 6], [7, 8]]) 2434 | array([[ 6, 8], 2435 | [10, 12]]) 2436 | 2437 | """ 2438 | ) 2439 | actual = getdoc(foo) 2440 | compare(actual, expected) 2441 | validate(foo) 2442 | 2443 | 2444 | # noinspection PyUnusedLocal 2445 | @doc( 2446 | summary="Compute the arithmetic mean along the specified axis.", 2447 | extended_summary=""" 2448 | Returns the average of the array elements. The average is taken over 2449 | the flattened array by default, otherwise over the specified axis. 2450 | `float64` intermediate and return values are used for integer inputs. 2451 | """, 2452 | parameters=dict( 2453 | a=""" 2454 | Array containing numbers whose mean is desired. If `a` is not an 2455 | array, a conversion is attempted. 2456 | """, 2457 | axis=""" 2458 | Axis or axes along which the means are computed. The default is to 2459 | compute the mean of the flattened array. 2460 | 2461 | .. versionadded:: 1.7.0 2462 | 2463 | If this is a tuple of ints, a mean is performed over multiple axes, 2464 | instead of a single axis or all the axes as before. 2465 | """, 2466 | dtype=""" 2467 | Type to use in computing the mean. For integer inputs, the default 2468 | is `float64`; for floating point inputs, it is the same as the 2469 | input dtype. 2470 | """, 2471 | out=""" 2472 | Alternate output array in which to place the result. The default 2473 | is ``None``; if provided, it must have the same shape as the 2474 | expected output, but the type will be cast if necessary. 2475 | See :ref:`ufuncs-output-type` for more details. 2476 | """, 2477 | keepdims=""" 2478 | If this is set to True, the axes which are reduced are left 2479 | in the result as dimensions with size one. With this option, 2480 | the result will broadcast correctly against the input array. 2481 | 2482 | If the default value is passed, then `keepdims` will not be 2483 | passed through to the `mean` method of sub-classes of 2484 | `ndarray`, however any non-default value will be. If the 2485 | sub-class' method does not implement `keepdims` any 2486 | exceptions will be raised. 2487 | """, 2488 | where=""" 2489 | Elements to include in the mean. See `~numpy.ufunc.reduce` for details. 2490 | 2491 | .. versionadded:: 1.20.0 2492 | 2493 | """, 2494 | ), 2495 | returns=dict( 2496 | m=""" 2497 | If `out=None`, returns a new array containing the mean values, 2498 | otherwise a reference to the output array is returned. 2499 | """ 2500 | ), 2501 | see_also=dict( 2502 | average="Weighted average", 2503 | std=None, 2504 | var=None, 2505 | nanmean=None, 2506 | nanstd=None, 2507 | nanvar=None, 2508 | ), 2509 | notes=""" 2510 | The arithmetic mean is the sum of the elements along the axis divided 2511 | by the number of elements. 2512 | 2513 | Note that for floating-point input, the mean is computed using the 2514 | same precision the input has. Depending on the input data, this can 2515 | cause the results to be inaccurate, especially for `float32` (see 2516 | example below). Specifying a higher-precision accumulator using the 2517 | `dtype` keyword can alleviate this issue. 2518 | 2519 | By default, `float16` results are computed using `float32` intermediates 2520 | for extra precision. 2521 | """, 2522 | examples=""" 2523 | >>> a = np.array([[1, 2], [3, 4]]) 2524 | >>> np.mean(a) 2525 | 2.5 2526 | >>> np.mean(a, axis=0) 2527 | array([2., 3.]) 2528 | >>> np.mean(a, axis=1) 2529 | array([1.5, 3.5]) 2530 | 2531 | In single precision, `mean` can be inaccurate: 2532 | 2533 | >>> a = np.zeros((2, 512 * 512), dtype=np.float32) 2534 | >>> a[0, :] = 1.0 2535 | >>> a[1, :] = 0.1 2536 | >>> np.mean(a) 2537 | 0.54999924 2538 | 2539 | Computing the mean in float64 is more accurate: 2540 | 2541 | >>> np.mean(a, dtype=np.float64) 2542 | 0.55000000074505806 # may vary 2543 | 2544 | Specifying a where argument: 2545 | 2546 | >>> a = np.array([[5, 9, 13], [14, 10, 12], [11, 15, 19]]) 2547 | >>> np.mean(a) 2548 | 12.0 2549 | >>> np.mean(a, where=[[True], [False], [False]]) 2550 | 9.0 2551 | """, 2552 | ) 2553 | def numpy_mean( 2554 | a: ArrayLike, 2555 | axis: Optional[Union[int, Tuple[int, ...]]] = None, 2556 | dtype: Optional[DTypeLike] = None, 2557 | out: Optional[numpy.ndarray] = None, 2558 | keepdims: Optional[bool] = None, 2559 | *, 2560 | where: Optional[ArrayLike] = None, 2561 | ) -> numpy.ndarray: 2562 | return numpy.zeros(42) 2563 | 2564 | 2565 | def test_numpy_mean(): 2566 | expected = cleandoc( 2567 | """ 2568 | Compute the arithmetic mean along the specified axis. 2569 | 2570 | Returns the average of the array elements. The average is taken over 2571 | the flattened array by default, otherwise over the specified axis. 2572 | `float64` intermediate and return values are used for integer inputs. 2573 | 2574 | Parameters 2575 | ---------- 2576 | a : array_like 2577 | Array containing numbers whose mean is desired. If `a` is not an 2578 | array, a conversion is attempted. 2579 | axis : int or tuple of int or None, optional 2580 | Axis or axes along which the means are computed. The default is to 2581 | compute the mean of the flattened array. 2582 | 2583 | .. versionadded:: 1.7.0 2584 | 2585 | If this is a tuple of ints, a mean is performed over multiple axes, 2586 | instead of a single axis or all the axes as before. 2587 | dtype : data-type, optional 2588 | Type to use in computing the mean. For integer inputs, the default is 2589 | `float64`; for floating point inputs, it is the same as the input 2590 | dtype. 2591 | out : ndarray or None, optional 2592 | Alternate output array in which to place the result. The default is 2593 | ``None``; if provided, it must have the same shape as the expected 2594 | output, but the type will be cast if necessary. See :ref:`ufuncs- 2595 | output-type` for more details. 2596 | keepdims : bool or None, optional 2597 | If this is set to True, the axes which are reduced are left in the 2598 | result as dimensions with size one. With this option, the result will 2599 | broadcast correctly against the input array. 2600 | 2601 | If the default value is passed, then `keepdims` will not be passed 2602 | through to the `mean` method of sub-classes of `ndarray`, however any 2603 | non-default value will be. If the sub-class' method does not 2604 | implement `keepdims` any exceptions will be raised. 2605 | where : array_like or None, optional 2606 | Elements to include in the mean. See `~numpy.ufunc.reduce` for 2607 | details. 2608 | 2609 | .. versionadded:: 1.20.0 2610 | 2611 | Returns 2612 | ------- 2613 | m : ndarray 2614 | If `out=None`, returns a new array containing the mean values, 2615 | otherwise a reference to the output array is returned. 2616 | 2617 | See Also 2618 | -------- 2619 | average : Weighted average. 2620 | std 2621 | var 2622 | nanmean 2623 | nanstd 2624 | nanvar 2625 | 2626 | Notes 2627 | ----- 2628 | The arithmetic mean is the sum of the elements along the axis divided 2629 | by the number of elements. 2630 | 2631 | Note that for floating-point input, the mean is computed using the 2632 | same precision the input has. Depending on the input data, this can 2633 | cause the results to be inaccurate, especially for `float32` (see 2634 | example below). Specifying a higher-precision accumulator using the 2635 | `dtype` keyword can alleviate this issue. 2636 | 2637 | By default, `float16` results are computed using `float32` 2638 | intermediates for extra precision. 2639 | 2640 | Examples 2641 | -------- 2642 | >>> a = np.array([[1, 2], [3, 4]]) 2643 | >>> np.mean(a) 2644 | 2.5 2645 | >>> np.mean(a, axis=0) 2646 | array([2., 3.]) 2647 | >>> np.mean(a, axis=1) 2648 | array([1.5, 3.5]) 2649 | 2650 | In single precision, `mean` can be inaccurate: 2651 | 2652 | >>> a = np.zeros((2, 512 * 512), dtype=np.float32) 2653 | >>> a[0, :] = 1.0 2654 | >>> a[1, :] = 0.1 2655 | >>> np.mean(a) 2656 | 0.54999924 2657 | 2658 | Computing the mean in float64 is more accurate: 2659 | 2660 | >>> np.mean(a, dtype=np.float64) 2661 | 0.55000000074505806 # may vary 2662 | 2663 | Specifying a where argument: 2664 | 2665 | >>> a = np.array([[5, 9, 13], [14, 10, 12], [11, 15, 19]]) 2666 | >>> np.mean(a) 2667 | 12.0 2668 | >>> np.mean(a, where=[[True], [False], [False]]) 2669 | 9.0 2670 | 2671 | """ 2672 | ) 2673 | 2674 | actual = getdoc(numpy_mean) 2675 | compare(actual, expected) 2676 | validate(numpy_mean) 2677 | 2678 | 2679 | def test_example(): 2680 | from numpydoc_decorator.example import greet 2681 | 2682 | expected = cleandoc( 2683 | """ 2684 | Say hello to someone. 2685 | 2686 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod 2687 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim 2688 | veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea 2689 | commodo consequat. Duis aute irure dolor in reprehenderit in voluptate 2690 | velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint 2691 | occaecat cupidatat non proident, sunt in culpa qui officia deserunt 2692 | mollit anim id est laborum. 2693 | 2694 | Parameters 2695 | ---------- 2696 | name : str 2697 | The name of the person to greet. 2698 | language : str, optional, default: 'en' 2699 | The language in which to greet as an ISO 639-1 code. 2700 | 2701 | Returns 2702 | ------- 2703 | str 2704 | A pleasant greeting. 2705 | 2706 | Raises 2707 | ------ 2708 | NotImplementedError 2709 | If the requested language has not been implemented yet. 2710 | ValueError 2711 | If the language is not a valid ISO 639-1 code. 2712 | 2713 | See Also 2714 | -------- 2715 | print : You could use this function to print your greeting. 2716 | 2717 | Notes 2718 | ----- 2719 | This function is useful when greeting someone else. If you would like 2720 | something to talk about next, you could try [1]_. 2721 | 2722 | References 2723 | ---------- 2724 | .. [1] O. McNoleg, "The integration of GIS, remote sensing, expert systems 2725 | and adaptive co-kriging for environmental habitat modelling of the 2726 | Highland Haggis using object-oriented, fuzzy-logic and neural- 2727 | network techniques," Computers & Geosciences, vol. 22, pp. 585-588, 2728 | 1996. 2729 | 2730 | Examples 2731 | -------- 2732 | Here is how to greet a friend in English: 2733 | 2734 | >>> print(greet("Ford Prefect")) 2735 | Hello Ford Prefect! 2736 | 2737 | Here is how to greet someone in another language: 2738 | 2739 | >>> print(greet("Tricia MacMillan", language="fr")) 2740 | Salut Tricia MacMillan! 2741 | 2742 | """ 2743 | ) 2744 | 2745 | actual = getdoc(greet) 2746 | compare(actual, expected) 2747 | validate(greet) 2748 | 2749 | 2750 | def test_forward_refs(): 2751 | # Here we test whether forward references to classes defined after 2752 | # the decorated function are supported. 2753 | 2754 | # N.B., some of the forward references cause flake8 to be unhappy, 2755 | # but don't seem to be a problem. Hence some use of noqa below. 2756 | 2757 | @doc( 2758 | summary="A function with typed parameters and forward references.", 2759 | parameters=dict( 2760 | bar="This is very bar.", 2761 | baz="This is totally baz.", 2762 | qux="Many values.", 2763 | spam="You'll love it.", 2764 | eggs="Good with spam.", 2765 | bacon="Good with eggs.", 2766 | sausage="Good with bacon.", 2767 | lobster="Good with sausage.", 2768 | thermidor="A type of lobster dish.", 2769 | # other parameters not needed, will be picked up from Annotated type 2770 | ), 2771 | ) 2772 | def foo( 2773 | bar: int, 2774 | baz: str, 2775 | qux: "Thing", 2776 | spam: Union[str, "Thing"], # noqa 2777 | eggs: Dict[str, "Thing"], # noqa 2778 | bacon: Literal["xxx", "yyy", "zzz"], 2779 | sausage: List["Thing"], # noqa 2780 | lobster: Tuple["Thing", ...], # noqa 2781 | thermidor: Sequence["Thing"], # noqa 2782 | norwegian_blue: Annotated["Thing", "This is an ex-parrot."], # noqa 2783 | lumberjack_song: Optional[ 2784 | Annotated["Thing", "I sleep all night and I work all day."] # noqa 2785 | ], 2786 | ): 2787 | pass 2788 | 2789 | class Thing: 2790 | pass 2791 | 2792 | expected = cleandoc( 2793 | """ 2794 | A function with typed parameters and forward references. 2795 | 2796 | Parameters 2797 | ---------- 2798 | bar : int 2799 | This is very bar. 2800 | baz : str 2801 | This is totally baz. 2802 | qux : Thing 2803 | Many values. 2804 | spam : str or Thing 2805 | You'll love it. 2806 | eggs : dict[str, Thing] 2807 | Good with spam. 2808 | bacon : {'xxx', 'yyy', 'zzz'} 2809 | Good with eggs. 2810 | sausage : list of Thing 2811 | Good with bacon. 2812 | lobster : tuple of Thing 2813 | Good with sausage. 2814 | thermidor : sequence of Thing 2815 | A type of lobster dish. 2816 | norwegian_blue : Thing 2817 | This is an ex-parrot. 2818 | lumberjack_song : Thing or None 2819 | I sleep all night and I work all day. 2820 | 2821 | """ 2822 | ) 2823 | actual = getdoc(foo) 2824 | compare(actual, expected) 2825 | validate(foo) 2826 | 2827 | # check annotated types are bypassed 2828 | compare(foo.__annotations__["norwegian_blue"], ForwardRef("Thing")) 2829 | compare(foo.__annotations__["lumberjack_song"], Optional["Thing"]) 2830 | 2831 | 2832 | def test_annotated_doc(): 2833 | # check we can use https://typing-extensions.readthedocs.io/en/latest/#Doc 2834 | 2835 | from typing_extensions import Doc 2836 | 2837 | @doc( 2838 | summary="A function with annotated parameters.", 2839 | ) 2840 | def foo( 2841 | bar: Annotated[int, Doc("This is very bar.")], 2842 | baz: Annotated[str, Doc("This is totally baz.")], 2843 | qux: Annotated[Sequence, Doc("Many values.")], 2844 | spam: Annotated[Union[list, str], Doc("You'll love it.")], 2845 | eggs: Annotated[Dict[str, Sequence], Doc("Good with spam.")], 2846 | bacon: Annotated[Literal["xxx", "yyy", "zzz"], Doc("Good with eggs.")], 2847 | sausage: Annotated[List[int], Doc("Good with bacon.")], 2848 | lobster: Annotated[Tuple[float, ...], Doc("Good with sausage.")], 2849 | thermidor: Annotated[Sequence[str], Doc("A type of lobster dish.")], 2850 | norwegian_blue: Annotated[str, Doc("This is an ex-parrot.")], 2851 | lumberjack_song: Optional[ 2852 | Annotated[int, Doc("I sleep all night and I work all day.")] 2853 | ], 2854 | ): 2855 | pass 2856 | 2857 | expected = cleandoc( 2858 | """ 2859 | A function with annotated parameters. 2860 | 2861 | Parameters 2862 | ---------- 2863 | bar : int 2864 | This is very bar. 2865 | baz : str 2866 | This is totally baz. 2867 | qux : Sequence 2868 | Many values. 2869 | spam : list or str 2870 | You'll love it. 2871 | eggs : dict[str, Sequence] 2872 | Good with spam. 2873 | bacon : {'xxx', 'yyy', 'zzz'} 2874 | Good with eggs. 2875 | sausage : list of int 2876 | Good with bacon. 2877 | lobster : tuple of float 2878 | Good with sausage. 2879 | thermidor : sequence of str 2880 | A type of lobster dish. 2881 | norwegian_blue : str 2882 | This is an ex-parrot. 2883 | lumberjack_song : int or None 2884 | I sleep all night and I work all day. 2885 | 2886 | """ 2887 | ) 2888 | actual = getdoc(foo) 2889 | compare(actual, expected) 2890 | validate(foo) 2891 | 2892 | # check annotated types are stripped 2893 | expected_annotations = { 2894 | "bar": int, 2895 | "baz": str, 2896 | "qux": Sequence, 2897 | "spam": Union[list, str], 2898 | "eggs": Dict[str, Sequence], 2899 | "bacon": Literal["xxx", "yyy", "zzz"], 2900 | "sausage": List[int], 2901 | "lobster": Tuple[float, ...], 2902 | "thermidor": Sequence[str], 2903 | "norwegian_blue": str, 2904 | "lumberjack_song": Optional[int], 2905 | } 2906 | compare(foo.__annotations__, expected_annotations) 2907 | 2908 | 2909 | def test_annotated_field(): 2910 | # check we can use https://docs.pydantic.dev/latest/api/fields/#pydantic.fields.Field 2911 | 2912 | from pydantic import Field 2913 | 2914 | @doc( 2915 | summary="A function with annotated parameters.", 2916 | ) 2917 | def foo( 2918 | bar: Annotated[int, Field(gt=1), "This is very bar."], 2919 | baz: Annotated[str, Field(description="This is totally baz.")], 2920 | qux: Annotated[Sequence, Field(description="Many values.")], 2921 | spam: Annotated[Union[list, str], Field(description="You'll love it.")], 2922 | eggs: Annotated[Dict[str, Sequence], Field(description="Good with spam.")], 2923 | bacon: Annotated[ 2924 | Literal["xxx", "yyy", "zzz"], Field(description="Good with eggs.") 2925 | ], 2926 | sausage: Annotated[List[int], Field(description="Good with bacon.")], 2927 | lobster: Annotated[Tuple[float, ...], Field(description="Good with sausage.")], 2928 | thermidor: Annotated[ 2929 | Sequence[str], Field(description="A type of lobster dish.") 2930 | ], 2931 | norwegian_blue: Annotated[str, Field(description="This is an ex-parrot.")], 2932 | lumberjack_song: Optional[ 2933 | Annotated[int, Field(description="I sleep all night and I work all day.")] 2934 | ], 2935 | ): 2936 | pass 2937 | 2938 | expected = cleandoc( 2939 | """ 2940 | A function with annotated parameters. 2941 | 2942 | Parameters 2943 | ---------- 2944 | bar : int 2945 | This is very bar. 2946 | baz : str 2947 | This is totally baz. 2948 | qux : Sequence 2949 | Many values. 2950 | spam : list or str 2951 | You'll love it. 2952 | eggs : dict[str, Sequence] 2953 | Good with spam. 2954 | bacon : {'xxx', 'yyy', 'zzz'} 2955 | Good with eggs. 2956 | sausage : list of int 2957 | Good with bacon. 2958 | lobster : tuple of float 2959 | Good with sausage. 2960 | thermidor : sequence of str 2961 | A type of lobster dish. 2962 | norwegian_blue : str 2963 | This is an ex-parrot. 2964 | lumberjack_song : int or None 2965 | I sleep all night and I work all day. 2966 | 2967 | """ 2968 | ) 2969 | actual = getdoc(foo) 2970 | compare(actual, expected) 2971 | validate(foo) 2972 | 2973 | # check annotated types are stripped 2974 | expected_annotations = { 2975 | "bar": int, 2976 | "baz": str, 2977 | "qux": Sequence, 2978 | "spam": Union[list, str], 2979 | "eggs": Dict[str, Sequence], 2980 | "bacon": Literal["xxx", "yyy", "zzz"], 2981 | "sausage": List[int], 2982 | "lobster": Tuple[float, ...], 2983 | "thermidor": Sequence[str], 2984 | "norwegian_blue": str, 2985 | "lumberjack_song": Optional[int], 2986 | } 2987 | compare(foo.__annotations__, expected_annotations) 2988 | 2989 | 2990 | def test_dataclass(): 2991 | @doc( 2992 | summary="This tests @doc for a dataclass.", 2993 | parameters=dict( 2994 | x="This is a number.", 2995 | name="This is a name.", 2996 | ), 2997 | ) 2998 | @dataclass 2999 | class MyDC: 3000 | x: int 3001 | name: str 3002 | 3003 | expected = cleandoc( 3004 | """ 3005 | This tests @doc for a dataclass. 3006 | 3007 | Parameters 3008 | ---------- 3009 | x : int 3010 | This is a number. 3011 | name : str 3012 | This is a name. 3013 | """ 3014 | ) 3015 | actual = getdoc(MyDC) 3016 | compare(actual, expected) 3017 | validate(MyDC) 3018 | --------------------------------------------------------------------------------