pax_global_header00006660000000000000000000000064147547515250014531gustar00rootroot0000000000000052 comment=e68c68815cffa46320f3ba42b867fc1175e804e1 griffe-typingdoc-0.2.8/000077500000000000000000000000001475475152500150005ustar00rootroot00000000000000griffe-typingdoc-0.2.8/.copier-answers.yml000066400000000000000000000012601475475152500205410ustar00rootroot00000000000000# Changes here will be overwritten by Copier _commit: 1.5.7 _src_path: gh:pawamoy/copier-uv author_email: pawamoy@pm.me author_fullname: Timothée Mazzucotelli author_username: pawamoy copyright_date: '2023' copyright_holder: Timothée Mazzucotelli copyright_holder_email: pawamoy@pm.me copyright_license: ISC License insiders: false project_description: Griffe extension for PEP 727 – Documentation Metadata in Typing. project_name: Griffe TypingDoc python_package_command_line_name: '' python_package_distribution_name: griffe-typingdoc python_package_import_name: griffe_typingdoc repository_name: griffe-typingdoc repository_namespace: mkdocstrings repository_provider: github.com griffe-typingdoc-0.2.8/.envrc000066400000000000000000000000211475475152500161070ustar00rootroot00000000000000PATH_add scripts griffe-typingdoc-0.2.8/.github/000077500000000000000000000000001475475152500163405ustar00rootroot00000000000000griffe-typingdoc-0.2.8/.github/FUNDING.yml000066400000000000000000000001261475475152500201540ustar00rootroot00000000000000github: pawamoy ko_fi: pawamoy polar: pawamoy custom: - https://www.paypal.me/pawamoy griffe-typingdoc-0.2.8/.github/ISSUE_TEMPLATE/000077500000000000000000000000001475475152500205235ustar00rootroot00000000000000griffe-typingdoc-0.2.8/.github/ISSUE_TEMPLATE/1-bug.md000066400000000000000000000027141475475152500217640ustar00rootroot00000000000000--- name: Bug report about: Create a bug report to help us improve. title: "bug: " labels: unconfirmed assignees: [pawamoy] --- ### Description of the bug ### To Reproduce ``` WRITE MRE / INSTRUCTIONS HERE ``` ### Full traceback
Full traceback ```python PASTE TRACEBACK HERE ```
### Expected behavior ### Environment information ```bash python -m griffe_typingdoc.debug # | xclip -selection clipboard ``` PASTE MARKDOWN OUTPUT HERE ### Additional context griffe-typingdoc-0.2.8/.github/ISSUE_TEMPLATE/2-feature.md000066400000000000000000000012131475475152500226340ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project. title: "feature: " labels: feature assignees: pawamoy --- ### Is your feature request related to a problem? Please describe. ### Describe the solution you'd like ### Describe alternatives you've considered ### Additional context griffe-typingdoc-0.2.8/.github/ISSUE_TEMPLATE/3-docs.md000066400000000000000000000011311475475152500221310ustar00rootroot00000000000000--- name: Documentation update about: Point at unclear, missing or outdated documentation. title: "docs: " labels: docs assignees: pawamoy --- ### Is something unclear, missing or outdated in our documentation? ### Relevant code snippets ### Link to the relevant documentation section griffe-typingdoc-0.2.8/.github/ISSUE_TEMPLATE/4-change.md000066400000000000000000000011261475475152500224330ustar00rootroot00000000000000--- name: Change request about: Suggest any other kind of change for this project. title: "change: " assignees: pawamoy --- ### Is your change request related to a problem? Please describe. ### Describe the solution you'd like ### Describe alternatives you've considered ### Additional context griffe-typingdoc-0.2.8/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000003421475475152500225120ustar00rootroot00000000000000blank_issues_enabled: false contact_links: - name: I have a question / I need help url: https://github.com/mkdocstrings/griffe-typingdoc/discussions/new?category=q-a about: Ask and answer questions in the Discussions tab. griffe-typingdoc-0.2.8/.github/workflows/000077500000000000000000000000001475475152500203755ustar00rootroot00000000000000griffe-typingdoc-0.2.8/.github/workflows/ci.yml000066400000000000000000000041651475475152500215210ustar00rootroot00000000000000name: ci on: push: pull_request: branches: - main defaults: run: shell: bash env: LANG: en_US.utf-8 LC_ALL: en_US.utf-8 PYTHONIOENCODING: UTF-8 PYTHON_VERSIONS: "" jobs: quality: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 fetch-tags: true - name: Setup Python uses: actions/setup-python@v5 with: python-version: "3.12" - name: Setup uv uses: astral-sh/setup-uv@v3 with: enable-cache: true cache-dependency-glob: pyproject.toml - name: Install dependencies run: make setup - name: Check if the documentation builds correctly run: make check-docs - name: Check the code quality run: make check-quality - name: Check if the code is correctly typed run: make check-types - name: Check for breaking changes in the API run: make check-api tests: strategy: matrix: os: - ubuntu-latest - macos-latest - windows-latest python-version: - "3.9" - "3.10" - "3.11" - "3.12" - "3.13" - "3.14" resolution: - highest - lowest-direct exclude: - os: macos-latest resolution: lowest-direct - os: windows-latest resolution: lowest-direct runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.python-version == '3.14' }} steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 fetch-tags: true - name: Setup Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} allow-prereleases: true - name: Setup uv uses: astral-sh/setup-uv@v3 with: enable-cache: true cache-dependency-glob: pyproject.toml cache-suffix: py${{ matrix.python-version }} - name: Install dependencies env: UV_RESOLUTION: ${{ matrix.resolution }} run: make setup - name: Run the test suite run: make test griffe-typingdoc-0.2.8/.github/workflows/release.yml000066400000000000000000000012141475475152500225360ustar00rootroot00000000000000name: release on: push permissions: contents: write jobs: release: runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/') steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 fetch-tags: true - name: Setup Python uses: actions/setup-python@v5 with: python-version: "3.12" - name: Setup uv uses: astral-sh/setup-uv@v3 - name: Prepare release notes run: uv tool run git-changelog --release-notes > release-notes.md - name: Create release uses: softprops/action-gh-release@v2 with: body_path: release-notes.md griffe-typingdoc-0.2.8/.gitignore000066400000000000000000000003311475475152500167650ustar00rootroot00000000000000# editors .idea/ .vscode/ # python *.egg-info/ *.py[cod] .venv/ .venvs/ /build/ /dist/ # tools .coverage* /.pdm-build/ /htmlcov/ /site/ uv.lock # cache .cache/ .pytest_cache/ .mypy_cache/ .ruff_cache/ __pycache__/ griffe-typingdoc-0.2.8/CHANGELOG.md000066400000000000000000000150131475475152500166110ustar00rootroot00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [0.2.8](https://github.com/mkdocstrings/griffe-typingdoc/releases/tag/0.2.8) - 2025-02-18 [Compare with 0.2.7](https://github.com/mkdocstrings/griffe-typingdoc/compare/0.2.7...0.2.8) ### Code Refactoring - Only add docstring sections when elements are annotated with `Doc` ([514467c](https://github.com/mkdocstrings/griffe-typingdoc/commit/514467c06f4381fca9c5f96a860c8abd87c1827b) by Timothée Mazzucotelli). [Issue-13](https://github.com/mkdocstrings/griffe-typingdoc/issues/13) ## [0.2.7](https://github.com/mkdocstrings/griffe-typingdoc/releases/tag/0.2.7) - 2024-09-10 [Compare with 0.2.6](https://github.com/mkdocstrings/griffe-typingdoc/compare/0.2.6...0.2.7) ### Bug Fixes - Resolve names in `Unpack`, instead of naively trying to get them from the parent of the function being handled ([5e06b33](https://github.com/mkdocstrings/griffe-typingdoc/commit/5e06b33651f43b292059d27d3b232e2646a409d5) by Timothée Mazzucotelli). [Issue-11](https://github.com/mkdocstrings/griffe-typingdoc/issues/11) ## [0.2.6](https://github.com/mkdocstrings/griffe-typingdoc/releases/tag/0.2.6) - 2024-08-14 [Compare with 0.2.5](https://github.com/mkdocstrings/griffe-typingdoc/compare/0.2.5...0.2.6) ### Build - Depend on Griffe 0.49 ([b6d7bd9](https://github.com/mkdocstrings/griffe-typingdoc/commit/b6d7bd9ce462a8dbd067464b3d14a9dd25865957) by Timothée Mazzucotelli). ## [0.2.5](https://github.com/mkdocstrings/griffe-typingdoc/releases/tag/0.2.5) - 2024-02-08 [Compare with 0.2.4](https://github.com/mkdocstrings/griffe-typingdoc/compare/0.2.4...0.2.5) ### Bug Fixes - Support simple return annotations ([b4afabe](https://github.com/mkdocstrings/griffe-typingdoc/commit/b4afabed86e8b7c1905cbf672ab261be0d895e40) by Timothée Mazzucotelli). [Issue #9](https://github.com/mkdocstrings/griffe-typingdoc/issues/9) ## [0.2.4](https://github.com/mkdocstrings/griffe-typingdoc/releases/tag/0.2.4) - 2023-11-14 [Compare with 0.2.3](https://github.com/mkdocstrings/griffe-typingdoc/compare/0.2.3...0.2.4) ### Code Refactoring - Run static analysis only after the whole package was loaded ([08be3d0](https://github.com/mkdocstrings/griffe-typingdoc/commit/08be3d0e735b03b9ba28b055895dfa2d01778fda) by Timothée Mazzucotelli). [Issue #7](https://github.com/mkdocstrings/griffe-typingdoc/issues/7), [PR #8](https://github.com/mkdocstrings/griffe-typingdoc/pull/8) ## [0.2.3](https://github.com/mkdocstrings/griffe-typingdoc/releases/tag/0.2.3) - 2023-10-23 [Compare with 0.2.2](https://github.com/mkdocstrings/griffe-typingdoc/compare/0.2.2...0.2.3) ### Bug Fixes - Fix index error when trying to access the first parameter of functions ([92e27a3](https://github.com/mkdocstrings/griffe-typingdoc/commit/92e27a3bc5f81acfdb94c24fcd33e1992e3db503) by Timothée Mazzucotelli). [Issue #7](https://github.com/mkdocstrings/griffe-typingdoc/issues/7) ## [0.2.2](https://github.com/mkdocstrings/griffe-typingdoc/releases/tag/0.2.2) - 2023-10-16 [Compare with 0.2.1](https://github.com/mkdocstrings/griffe-typingdoc/compare/0.2.1...0.2.2) ### Bug Fixes - Do not always add docstrings to attributes ([02d8cb6](https://github.com/mkdocstrings/griffe-typingdoc/commit/02d8cb6d70edc3869767561e42003b6ef97ac1cd) by Timothée Mazzucotelli). ## [0.2.1](https://github.com/mkdocstrings/griffe-typingdoc/releases/tag/0.2.1) - 2023-10-05 [Compare with 0.2.0](https://github.com/mkdocstrings/griffe-typingdoc/compare/0.2.0...0.2.1) ### Bug Fixes - Fix casing of `deprecated`, only set metadata when annotated element is known ([98f3c2c](https://github.com/mkdocstrings/griffe-typingdoc/commit/98f3c2c296e946dee0fd30ae533515c1896022e1) by Sebastián Ramírez). ## [0.2.0](https://github.com/mkdocstrings/griffe-typingdoc/releases/tag/0.2.0) - 2023-09-14 [Compare with 0.1.0](https://github.com/mkdocstrings/griffe-typingdoc/compare/0.1.0...0.2.0) ### Dependencies - Depend on Griffe ([cc15edc](https://github.com/mkdocstrings/griffe-typingdoc/commit/cc15edc3b170e891fa37ff69b58eb9fea7af8fa8) by Timothée Mazzucotelli). ### Features - Support more experimental annotations (names, deprecations, warnings, exceptions) ([afa6dd9](https://github.com/mkdocstrings/griffe-typingdoc/commit/afa6dd96fe7dc90d16934b1b191484f891f56d92) by Timothée Mazzucotelli). [Issue #1](https://github.com/mkdocstrings/griffe-typingdoc/issues/1), [PR #3](https://github.com/mkdocstrings/griffe-typingdoc/pull/3) ### Code Refactoring - Refactor implementation with latest version in `typing_extensions`, `Doc()`, and de-indent ([c7a61c6](https://github.com/mkdocstrings/griffe-typingdoc/commit/c7a61c68a39d6dbb4955037cd18f96be214f2d0d) by Sebastián Ramírez). [PR #2](https://github.com/mkdocstrings/griffe-typingdoc/pull/2) ## [0.1.0](https://github.com/mkdocstrings/griffe-typingdoc/releases/tag/0.1.0) - 2023-08-29 [Compare with first commit](https://github.com/mkdocstrings/griffe-typingdoc/compare/10139be2140f73617681a1f7ca2c4514ea9017e5...0.1.0) ### Dependencies - Always depend on typing-extensions, use @tiangolo's fork to test ([33d242e](https://github.com/mkdocstrings/griffe-typingdoc/commit/33d242e22237fc4652b86d44c7b8655ded661342) by Timothée Mazzucotelli). ### Features - Support Python 3.8 thanks to typing-extensions ([489aaac](https://github.com/mkdocstrings/griffe-typingdoc/commit/489aaacd8e2cea3c57dd6c2ce7f9635e4489e8b4) by Timothée Mazzucotelli). - Implement extension ([5fccd06](https://github.com/mkdocstrings/griffe-typingdoc/commit/5fccd065f6717e195bd7fbc7c4f487ae6bd413b1) by Timothée Mazzucotelli). - Generate project with copier-pdm ([10139be](https://github.com/mkdocstrings/griffe-typingdoc/commit/10139be2140f73617681a1f7ca2c4514ea9017e5) by Timothée Mazzucotelli). ### Code Refactoring - Update to support new proposal (https://peps.python.org/pep-0727/) ([bd3eecd](https://github.com/mkdocstrings/griffe-typingdoc/commit/bd3eecdc96755dc4fa50a1cd5049e8366ab2ba72) by Timothée Mazzucotelli). - Use newer Griffe extensions ([199609f](https://github.com/mkdocstrings/griffe-typingdoc/commit/199609f053c04b8d0c21e7026c5f2eb1ad268ead) by Timothée Mazzucotelli). - Remove CLI setup ([48dea50](https://github.com/mkdocstrings/griffe-typingdoc/commit/48dea500a5543f389816eee5ef6e98f5541d090d) by Timothée Mazzucotelli). griffe-typingdoc-0.2.8/CODE_OF_CONDUCT.md000066400000000000000000000125471475475152500176100ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at pawamoy@pm.me. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html [Mozilla CoC]: https://github.com/mozilla/diversity [FAQ]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations griffe-typingdoc-0.2.8/CONTRIBUTING.md000066400000000000000000000101121475475152500172240ustar00rootroot00000000000000# Contributing Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given. ## Environment setup Nothing easier! Fork and clone the repository, then: ```bash cd griffe-typingdoc make setup ``` > NOTE: > If it fails for some reason, > you'll need to install > [uv](https://github.com/astral-sh/uv) > manually. > > You can install it with: > > ```bash > curl -LsSf https://astral.sh/uv/install.sh | sh > ``` > > Now you can try running `make setup` again, > or simply `uv sync`. You now have the dependencies installed. Run `make help` to see all the available actions! ## Tasks The entry-point to run commands and tasks is the `make` Python script, located in the `scripts` directory. Try running `make` to show the available commands and tasks. The *commands* do not need the Python dependencies to be installed, while the *tasks* do. The cross-platform tasks are written in Python, thanks to [duty](https://github.com/pawamoy/duty). If you work in VSCode, we provide [an action to configure VSCode](https://pawamoy.github.io/copier-uv/work/#vscode-setup) for the project. ## Development As usual: 1. create a new branch: `git switch -c feature-or-bugfix-name` 1. edit the code and/or the documentation **Before committing:** 1. run `make format` to auto-format the code 1. run `make check` to check everything (fix any warning) 1. run `make test` to run the tests (fix any issue) 1. if you updated the documentation or the project dependencies: 1. run `make docs` 1. go to http://localhost:8000 and check that everything looks good 1. follow our [commit message convention](#commit-message-convention) If you are unsure about how to fix or ignore a warning, just let the continuous integration fail, and we will help you during review. Don't bother updating the changelog, we will take care of this. ## Commit message convention Commit messages must follow our convention based on the [Angular style](https://gist.github.com/stephenparish/9941e89d80e2bc58a153#format-of-the-commit-message) or the [Karma convention](https://karma-runner.github.io/4.0/dev/git-commit-msg.html): ``` [(scope)]: Subject [Body] ``` **Subject and body must be valid Markdown.** Subject must have proper casing (uppercase for first letter if it makes sense), but no dot at the end, and no punctuation in general. Scope and body are optional. Type can be: - `build`: About packaging, building wheels, etc. - `chore`: About packaging or repo/files management. - `ci`: About Continuous Integration. - `deps`: Dependencies update. - `docs`: About documentation. - `feat`: New feature. - `fix`: Bug fix. - `perf`: About performance. - `refactor`: Changes that are not features or bug fixes. - `style`: A change in code style/format. - `tests`: About tests. If you write a body, please add trailers at the end (for example issues and PR references, or co-authors), without relying on GitHub's flavored Markdown: ``` Body. Issue #10: https://github.com/namespace/project/issues/10 Related to PR namespace/other-project#15: https://github.com/namespace/other-project/pull/15 ``` These "trailers" must appear at the end of the body, without any blank lines between them. The trailer title can contain any character except colons `:`. We expect a full URI for each trailer, not just GitHub autolinks (for example, full GitHub URLs for commits and issues, not the hash or the #issue-number). We do not enforce a line length on commit messages summary and body, but please avoid very long summaries, and very long lines in the body, unless they are part of code blocks that must not be wrapped. ## Pull requests guidelines Link to any related issue in the Pull Request message. During the review, we recommend using fixups: ```bash # SHA is the SHA of the commit you want to fix git commit --fixup=SHA ``` Once all the changes are approved, you can squash your commits: ```bash git rebase -i --autosquash main ``` And force-push: ```bash git push -f ``` If this seems all too complicated, you can push or force-push each new commit, and we will squash them ourselves if needed, before merging. griffe-typingdoc-0.2.8/LICENSE000066400000000000000000000013621475475152500160070ustar00rootroot00000000000000ISC License Copyright (c) 2023, Timothée Mazzucotelli Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. griffe-typingdoc-0.2.8/Makefile000066400000000000000000000007601475475152500164430ustar00rootroot00000000000000# If you have `direnv` loaded in your shell, and allow it in the repository, # the `make` command will point at the `scripts/make` shell script. # This Makefile is just here to allow auto-completion in the terminal. actions = \ allrun \ changelog \ check \ check-api \ check-docs \ check-quality \ check-types \ clean \ coverage \ docs \ docs-deploy \ format \ help \ multirun \ release \ run \ setup \ test \ vscode .PHONY: $(actions) $(actions): @python scripts/make "$@" griffe-typingdoc-0.2.8/README.md000066400000000000000000000016241475475152500162620ustar00rootroot00000000000000# Griffe TypingDoc [![ci](https://github.com/mkdocstrings/griffe-typingdoc/workflows/ci/badge.svg)](https://github.com/mkdocstrings/griffe-typingdoc/actions?query=workflow%3Aci) [![documentation](https://img.shields.io/badge/docs-mkdocs-708FCC.svg?style=flat)](https://mkdocstrings.github.io/griffe-typingdoc/) [![pypi version](https://img.shields.io/pypi/v/griffe-typingdoc.svg)](https://pypi.org/project/griffe-typingdoc/) [![gitter](https://badges.gitter.im/join%20chat.svg)](https://app.gitter.im/#/room/#griffe-typingdoc:gitter.im) Griffe extension for [PEP 727 – Documentation Metadata in Typing](https://peps.python.org/pep-0727/). ## Installation ```bash pip install griffe-typingdoc ``` To use the extension in a MkDocs project, use this configuration: ```yaml # mkdocs.yml plugins: - mkdocstrings: handlers: python: options: extensions: - griffe_typingdoc ``` griffe-typingdoc-0.2.8/config/000077500000000000000000000000001475475152500162455ustar00rootroot00000000000000griffe-typingdoc-0.2.8/config/coverage.ini000066400000000000000000000005631475475152500205450ustar00rootroot00000000000000[coverage:run] branch = true parallel = true source = src/ tests/ [coverage:paths] equivalent = src/ .venv/lib/*/site-packages/ .venvs/*/lib/*/site-packages/ [coverage:report] precision = 2 omit = src/*/__init__.py src/*/__main__.py tests/__init__.py exclude_lines = pragma: no cover if TYPE_CHECKING [coverage:json] output = htmlcov/coverage.json griffe-typingdoc-0.2.8/config/git-changelog.toml000066400000000000000000000003401475475152500216470ustar00rootroot00000000000000bump = "auto" convention = "angular" in-place = true output = "CHANGELOG.md" parse-refs = false parse-trailers = true sections = ["build", "deps", "feat", "fix", "refactor"] template = "keepachangelog" versioning = "pep440" griffe-typingdoc-0.2.8/config/mypy.ini000066400000000000000000000001621475475152500177430ustar00rootroot00000000000000[mypy] ignore_missing_imports = true exclude = tests/fixtures/ warn_unused_ignores = true show_error_codes = true griffe-typingdoc-0.2.8/config/pytest.ini000066400000000000000000000004341475475152500202770ustar00rootroot00000000000000[pytest] python_files = test_*.py addopts = --cov --cov-config config/coverage.ini testpaths = tests # action:message_regex:warning_class:module_regex:line filterwarnings = error # TODO: remove once pytest-xdist 4 is released ignore:.*rsyncdir:DeprecationWarning:xdist griffe-typingdoc-0.2.8/config/ruff.toml000066400000000000000000000041241475475152500201050ustar00rootroot00000000000000target-version = "py39" line-length = 120 [lint] exclude = [ "tests/fixtures/*.py", ] select = [ "A", "ANN", "ARG", "B", "BLE", "C", "C4", "COM", "D", "DTZ", "E", "ERA", "EXE", "F", "FBT", "G", "I", "ICN", "INP", "ISC", "N", "PGH", "PIE", "PL", "PLC", "PLE", "PLR", "PLW", "PT", "PYI", "Q", "RUF", "RSE", "RET", "S", "SIM", "SLF", "T", "T10", "T20", "TCH", "TID", "TRY", "UP", "W", "YTT", ] ignore = [ "A001", # Variable is shadowing a Python builtin "ANN101", # Missing type annotation for self "ANN102", # Missing type annotation for cls "ANN204", # Missing return type annotation for special method __str__ "ANN401", # Dynamically typed expressions (typing.Any) are disallowed "ARG005", # Unused lambda argument "C901", # Too complex "D105", # Missing docstring in magic method "D417", # Missing argument description in the docstring "E501", # Line too long "ERA001", # Commented out code "G004", # Logging statement uses f-string "PLR0911", # Too many return statements "PLR0912", # Too many branches "PLR0913", # Too many arguments to function call "PLR0915", # Too many statements "SLF001", # Private member accessed "TRY003", # Avoid specifying long messages outside the exception class ] [lint.per-file-ignores] "src/*/cli.py" = [ "T201", # Print statement ] "src/*/debug.py" = [ "T201", # Print statement ] "scripts/*.py" = [ "INP001", # File is part of an implicit namespace package "T201", # Print statement ] "tests/*.py" = [ "ARG005", # Unused lambda argument "FBT001", # Boolean positional arg in function definition "PLR2004", # Magic value used in comparison "S101", # Use of assert detected ] [lint.flake8-quotes] docstring-quotes = "double" [lint.flake8-tidy-imports] ban-relative-imports = "all" [lint.isort] known-first-party = ["griffe_typingdoc"] [lint.pydocstyle] convention = "google" [format] exclude = [ "tests/fixtures/*.py", ] docstring-code-format = true docstring-code-line-length = 80 griffe-typingdoc-0.2.8/config/vscode/000077500000000000000000000000001475475152500175305ustar00rootroot00000000000000griffe-typingdoc-0.2.8/config/vscode/launch.json000066400000000000000000000021771475475152500217040ustar00rootroot00000000000000{ "version": "0.2.0", "configurations": [ { "name": "python (current file)", "type": "debugpy", "request": "launch", "program": "${file}", "console": "integratedTerminal", "justMyCode": false }, { "name": "docs", "type": "debugpy", "request": "launch", "module": "mkdocs", "justMyCode": false, "args": [ "serve", "-v" ] }, { "name": "test", "type": "debugpy", "request": "launch", "module": "pytest", "justMyCode": false, "args": [ "-c=config/pytest.ini", "-vvv", "--no-cov", "--dist=no", "tests", "-k=${input:tests_selection}" ] } ], "inputs": [ { "id": "tests_selection", "type": "promptString", "description": "Tests selection", "default": "" } ] }griffe-typingdoc-0.2.8/config/vscode/settings.json000066400000000000000000000017041475475152500222650ustar00rootroot00000000000000{ "files.watcherExclude": { "**/.venv*/**": true, "**/.venvs*/**": true, "**/venv*/**": true }, "mypy-type-checker.args": [ "--config-file=config/mypy.ini" ], "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, "python.testing.pytestArgs": [ "--config-file=config/pytest.ini" ], "ruff.enable": true, "ruff.format.args": [ "--config=config/ruff.toml" ], "ruff.lint.args": [ "--config=config/ruff.toml" ], "yaml.schemas": { "https://squidfunk.github.io/mkdocs-material/schema.json": "mkdocs.yml" }, "yaml.customTags": [ "!ENV scalar", "!ENV sequence", "!relative scalar", "tag:yaml.org,2002:python/name:materialx.emoji.to_svg", "tag:yaml.org,2002:python/name:materialx.emoji.twemoji", "tag:yaml.org,2002:python/name:pymdownx.superfences.fence_code_format" ] }griffe-typingdoc-0.2.8/config/vscode/tasks.json000066400000000000000000000046051475475152500215550ustar00rootroot00000000000000{ "version": "2.0.0", "tasks": [ { "label": "changelog", "type": "process", "command": "scripts/make", "args": ["changelog"] }, { "label": "check", "type": "process", "command": "scripts/make", "args": ["check"] }, { "label": "check-quality", "type": "process", "command": "scripts/make", "args": ["check-quality"] }, { "label": "check-types", "type": "process", "command": "scripts/make", "args": ["check-types"] }, { "label": "check-docs", "type": "process", "command": "scripts/make", "args": ["check-docs"] }, { "label": "check-api", "type": "process", "command": "scripts/make", "args": ["check-api"] }, { "label": "clean", "type": "process", "command": "scripts/make", "args": ["clean"] }, { "label": "docs", "type": "process", "command": "scripts/make", "args": ["docs"] }, { "label": "docs-deploy", "type": "process", "command": "scripts/make", "args": ["docs-deploy"] }, { "label": "format", "type": "process", "command": "scripts/make", "args": ["format"] }, { "label": "release", "type": "process", "command": "scripts/make", "args": ["release", "${input:version}"] }, { "label": "setup", "type": "process", "command": "scripts/make", "args": ["setup"] }, { "label": "test", "type": "process", "command": "scripts/make", "args": ["test", "coverage"], "group": "test" }, { "label": "vscode", "type": "process", "command": "scripts/make", "args": ["vscode"] } ], "inputs": [ { "id": "version", "type": "promptString", "description": "Version" } ] }griffe-typingdoc-0.2.8/docs/000077500000000000000000000000001475475152500157305ustar00rootroot00000000000000griffe-typingdoc-0.2.8/docs/.overrides/000077500000000000000000000000001475475152500200105ustar00rootroot00000000000000griffe-typingdoc-0.2.8/docs/.overrides/main.html000066400000000000000000000005001475475152500216150ustar00rootroot00000000000000{% extends "base.html" %} {% block announce %} Follow @pawamoy on {% include ".icons/fontawesome/brands/mastodon.svg" %} Fosstodon for updates {% endblock %} griffe-typingdoc-0.2.8/docs/.overrides/partials/000077500000000000000000000000001475475152500216275ustar00rootroot00000000000000griffe-typingdoc-0.2.8/docs/.overrides/partials/comments.html000066400000000000000000000041251475475152500243440ustar00rootroot00000000000000 griffe-typingdoc-0.2.8/docs/changelog.md000066400000000000000000000000261475475152500201770ustar00rootroot00000000000000--8<-- "CHANGELOG.md" griffe-typingdoc-0.2.8/docs/code_of_conduct.md000066400000000000000000000000341475475152500213640ustar00rootroot00000000000000--8<-- "CODE_OF_CONDUCT.md" griffe-typingdoc-0.2.8/docs/contributing.md000066400000000000000000000000311475475152500207530ustar00rootroot00000000000000--8<-- "CONTRIBUTING.md" griffe-typingdoc-0.2.8/docs/credits.md000066400000000000000000000002011475475152500177000ustar00rootroot00000000000000--- hide: - toc --- ```python exec="yes" --8<-- "scripts/gen_credits.py" ``` griffe-typingdoc-0.2.8/docs/css/000077500000000000000000000000001475475152500165205ustar00rootroot00000000000000griffe-typingdoc-0.2.8/docs/css/material.css000066400000000000000000000001311475475152500210230ustar00rootroot00000000000000/* More space at the bottom of the page. */ .md-main__inner { margin-bottom: 1.5rem; } griffe-typingdoc-0.2.8/docs/css/mkdocstrings.css000066400000000000000000000021021475475152500217340ustar00rootroot00000000000000/* Indentation. */ div.doc-contents:not(.first) { padding-left: 25px; border-left: .05rem solid var(--md-typeset-table-color); } /* Mark external links as such. */ a.external::after, a.autorefs-external::after { /* https://primer.style/octicons/arrow-up-right-24 */ mask-image: url('data:image/svg+xml,'); -webkit-mask-image: url('data:image/svg+xml,'); content: ' '; display: inline-block; vertical-align: middle; position: relative; height: 1em; width: 1em; background-color: currentColor; } a.external:hover::after, a.autorefs-external:hover::after { background-color: var(--md-accent-fg-color); }griffe-typingdoc-0.2.8/docs/examples.md000066400000000000000000000011141475475152500200650ustar00rootroot00000000000000--- hide: - navigation --- # Examples ## Simple /// details | `simple` Python module type: example ```python --8<-- "docs/examples/simple.py" ``` /// ::: simple options: heading_level: 3 ## Enhanced > WARNING: **Non-standard features** The "enhanced" features are not part of PEP 727. They just serve as an example to show what would be possible if the PEP was enhanced to account for more use-cases. /// details | `enhanced` Python module type: example ```python --8<-- "docs/examples/enhanced.py" ``` /// ::: enhanced options: heading_level: 3 griffe-typingdoc-0.2.8/docs/examples/000077500000000000000000000000001475475152500175465ustar00rootroot00000000000000griffe-typingdoc-0.2.8/docs/examples/enhanced.py000066400000000000000000000047621475475152500216760ustar00rootroot00000000000000# This gist shows how we could get rid of docstrings micro-syntax # like Google-style and Numpydoc-style pseudo-standards, # using an enhanced version of PEP 727. # The goal is to replace the following sections: # deprecated # Parameters # Other Parameters # Raises # Warns # Yields # Receives # Returns from __future__ import annotations from typing import ( Annotated, Generator, deprecated, Doc, Name, Raises, Warns, ) # Documenting deprecations, replacing Deprecated sections: DEPRECATED: Annotated[ int, deprecated( """Deprecated since v2. Please stop using this deprecated attribute, thanks! """ ), Doc("Showing off deprecated attributes."), ] # For functions, maybe add the information to the return value annotation: def deprecated1() -> Annotated[None, deprecated("Deprecated since v2.")]: """Showing off deprecated functions.""" # For parameters: def deprecated2(param1: Annotated[int, deprecated("Deprecated since v2."), Doc("Description of param1.")] = 0): """Showing off deprecated parameters.""" # Documenting exceptions, replacing Raises sections, # maybe add the information to the return value annotation: def exceptions() -> ( Annotated[ None, Raises(ValueError, "When something goes wrong."), Raises(TypeError, "When something goes even wronger."), ] ): """Showing off raised exceptions.""" # Documenting warnings, replacing Warns sections, # maybe add the information to the return value annotation: def warnings() -> ( Annotated[ None, Warns(FutureWarning, "Hello users."), Warns(DeprecationWarning, "Hello developers."), ] ): """Showing off emitted warnings.""" # Advanced use-case: documenting multiple yielded/received/returned values: def return_tuple() -> ( Generator[ tuple[ Annotated[int, Name("python"), Doc("First element of the yielded value.")], Annotated[float, Name("cobra"), Doc("Second element of the yielded value.")], ], tuple[ Annotated[int, Name("beep"), Doc("First element of the received value.")], Annotated[float, Name("boop"), Doc("Second element of the received value.")], ], tuple[ Annotated[int, Name("super"), Doc("First element of the returned value.")], Annotated[float, Name("hyper"), Doc("Second element of the returned value.")], ], ] ): """Showing off tuples as yield/receive/return values.""" griffe-typingdoc-0.2.8/docs/examples/simple.py000066400000000000000000000044211475475152500214120ustar00rootroot00000000000000from __future__ import annotations from typing import ( Annotated, Generator, Iterator, NotRequired, TypedDict, Unpack, Doc, ) # Documenting module/class attributes, replacing Attributes sections: ATTRIBUTE: Annotated[ str, Doc( """Showing off attributes. Supporting multiple lines. """ ) ] # Documenting parameters, replacing Parameters sections: def parameters(param1: Annotated[str, Doc("Description of param1.")] = "default"): """Showing off parameters.""" # Documenting other parameters (keyword arguments), replacing Other Parameters sections: class OtherParameters(TypedDict, total=False): """Keyword arguments of [`simple.other_parameters`][].""" param1: Annotated[NotRequired[str], Doc("Description of param1.")] param2: Annotated[NotRequired[str], Doc("Description of param2.")] def other_parameters( **kwargs: Annotated[Unpack[OtherParameters], Doc("See other parameters.")], # noqa: ARG001 ) -> None: """Showing off other parameters.""" # Documenting returned values, replacing Returns sections: def return_value() -> Annotated[int, Doc("Returned integer.")]: """Showing off return values.""" return 0 # Documenting yielded and received values, replacing Yields and Receives sections: def generator() -> Generator[ Annotated[int, Doc("Yielded integers.")], Annotated[int, Doc("Received integers.")], Annotated[int, Doc("Final returned value.")], ]: """Showing off generators.""" # Same thing with Iterator instead of Generator: def iterator() -> Iterator[Annotated[int, Doc("Yielded integers.")]]: """Showing off iterators.""" # Advanced use-case: documenting multiple yielded/received/returned values: def return_tuple() -> Generator[ tuple[ Annotated[int, Doc("First element of the yielded value.")], Annotated[float, Doc("Second element of the yielded value.")], ], tuple[ Annotated[int, Doc("First element of the received value.")], Annotated[float, Doc("Second element of the received value.")], ], tuple[ Annotated[int, Doc("First element of the returned value.")], Annotated[float, Doc("Second element of the returned value.")], ], ]: """Showing off tuples as yield/receive/return values.""" griffe-typingdoc-0.2.8/docs/index.md000066400000000000000000000000551475475152500173610ustar00rootroot00000000000000--- hide: - feedback --- --8<-- "README.md" griffe-typingdoc-0.2.8/docs/js/000077500000000000000000000000001475475152500163445ustar00rootroot00000000000000griffe-typingdoc-0.2.8/docs/js/feedback.js000066400000000000000000000007751475475152500204370ustar00rootroot00000000000000const feedback = document.forms.feedback; feedback.hidden = false; feedback.addEventListener("submit", function(ev) { ev.preventDefault(); const commentElement = document.getElementById("feedback"); commentElement.style.display = "block"; feedback.firstElementChild.disabled = true; const data = ev.submitter.getAttribute("data-md-value"); const note = feedback.querySelector(".md-feedback__note [data-md-value='" + data + "']"); if (note) { note.hidden = false; } }) griffe-typingdoc-0.2.8/docs/license.md000066400000000000000000000000761475475152500176770ustar00rootroot00000000000000--- hide: - feedback --- # License ``` --8<-- "LICENSE" ``` griffe-typingdoc-0.2.8/duties.py000066400000000000000000000140031475475152500166450ustar00rootroot00000000000000"""Development tasks.""" from __future__ import annotations import os import sys from contextlib import contextmanager from importlib.metadata import version as pkgversion from pathlib import Path from typing import TYPE_CHECKING from duty import duty, tools if TYPE_CHECKING: from collections.abc import Iterator from duty.context import Context PY_SRC_PATHS = (Path(_) for _ in ("src", "tests", "duties.py", "scripts")) PY_SRC_LIST = tuple(str(_) for _ in PY_SRC_PATHS) PY_SRC = " ".join(PY_SRC_LIST) CI = os.environ.get("CI", "0") in {"1", "true", "yes", ""} WINDOWS = os.name == "nt" PTY = not WINDOWS and not CI MULTIRUN = os.environ.get("MULTIRUN", "0") == "1" def pyprefix(title: str) -> str: # noqa: D103 if MULTIRUN: prefix = f"(python{sys.version_info.major}.{sys.version_info.minor})" return f"{prefix:14}{title}" return title @contextmanager def material_insiders() -> Iterator[bool]: # noqa: D103 if "+insiders" in pkgversion("mkdocs-material"): os.environ["MATERIAL_INSIDERS"] = "true" try: yield True finally: os.environ.pop("MATERIAL_INSIDERS") else: yield False @duty def changelog(ctx: Context, bump: str = "") -> None: """Update the changelog in-place with latest commits. Parameters: bump: Bump option passed to git-changelog. """ ctx.run(tools.git_changelog(bump=bump or None), title="Updating changelog") @duty(pre=["check-quality", "check-types", "check-docs", "check-api"]) def check(ctx: Context) -> None: """Check it all!""" @duty def check_quality(ctx: Context) -> None: """Check the code quality.""" ctx.run( tools.ruff.check(*PY_SRC_LIST, config="config/ruff.toml"), title=pyprefix("Checking code quality"), ) @duty def check_docs(ctx: Context) -> None: """Check if the documentation builds correctly.""" Path("htmlcov").mkdir(parents=True, exist_ok=True) Path("htmlcov/index.html").touch(exist_ok=True) with material_insiders(): ctx.run( tools.mkdocs.build(strict=True, verbose=True), title=pyprefix("Building documentation"), ) @duty def check_types(ctx: Context) -> None: """Check that the code is correctly typed.""" os.environ["FORCE_COLOR"] = "1" ctx.run( tools.mypy(*PY_SRC_LIST, config_file="config/mypy.ini"), title=pyprefix("Type-checking"), ) @duty def check_api(ctx: Context, *cli_args: str) -> None: """Check for API breaking changes.""" ctx.run( tools.griffe.check("griffe_typingdoc", search=["src"], color=True).add_args(*cli_args), title="Checking for API breaking changes", nofail=True, ) @duty def docs(ctx: Context, *cli_args: str, host: str = "127.0.0.1", port: int = 8000) -> None: """Serve the documentation (localhost:8000). Parameters: host: The host to serve the docs from. port: The port to serve the docs on. """ with material_insiders(): ctx.run( tools.mkdocs.serve(dev_addr=f"{host}:{port}").add_args(*cli_args), title="Serving documentation", capture=False, ) @duty def docs_deploy(ctx: Context) -> None: """Deploy the documentation to GitHub pages.""" os.environ["DEPLOY"] = "true" with material_insiders() as insiders: if not insiders: ctx.run(lambda: False, title="Not deploying docs without Material for MkDocs Insiders!") ctx.run(tools.mkdocs.gh_deploy(), title="Deploying documentation") @duty def format(ctx: Context) -> None: """Run formatting tools on the code.""" ctx.run( tools.ruff.check(*PY_SRC_LIST, config="config/ruff.toml", fix_only=True, exit_zero=True), title="Auto-fixing code", ) ctx.run(tools.ruff.format(*PY_SRC_LIST, config="config/ruff.toml"), title="Formatting code") @duty def build(ctx: Context) -> None: """Build source and wheel distributions.""" ctx.run( tools.build(), title="Building source and wheel distributions", pty=PTY, ) @duty def publish(ctx: Context) -> None: """Publish source and wheel distributions to PyPI.""" if not Path("dist").exists(): ctx.run("false", title="No distribution files found") dists = [str(dist) for dist in Path("dist").iterdir()] ctx.run( tools.twine.upload(*dists, skip_existing=True), title="Publishing source and wheel distributions to PyPI", pty=PTY, ) @duty(post=["build", "publish", "docs-deploy"]) def release(ctx: Context, version: str = "") -> None: """Release a new Python package. Parameters: version: The new version number to use. """ if not (version := (version or input("> Version to release: ")).strip()): ctx.run("false", title="A version must be provided") ctx.run("git add pyproject.toml CHANGELOG.md", title="Staging files", pty=PTY) ctx.run(["git", "commit", "-m", f"chore: Prepare release {version}"], title="Committing changes", pty=PTY) ctx.run(f"git tag {version}", title="Tagging commit", pty=PTY) ctx.run("git push", title="Pushing commits", pty=False) ctx.run("git push --tags", title="Pushing tags", pty=False) @duty(silent=True, aliases=["cov"]) def coverage(ctx: Context) -> None: """Report coverage as text and HTML.""" ctx.run(tools.coverage.combine(), nofail=True) ctx.run(tools.coverage.report(rcfile="config/coverage.ini"), capture=False) ctx.run(tools.coverage.html(rcfile="config/coverage.ini")) @duty def test(ctx: Context, *cli_args: str, match: str = "") -> None: """Run the test suite. Parameters: match: A pytest expression to filter selected tests. """ py_version = f"{sys.version_info.major}{sys.version_info.minor}" os.environ["COVERAGE_FILE"] = f".coverage.{py_version}" ctx.run( tools.pytest( "tests", config_file="config/pytest.ini", select=match, color="yes", ).add_args("-n", "auto", *cli_args), title=pyprefix("Running tests"), ) griffe-typingdoc-0.2.8/mkdocs.yml000066400000000000000000000107201475475152500170030ustar00rootroot00000000000000site_name: "Griffe TypingDoc" site_description: "Griffe extension for PEP 727 – Documentation Metadata in Typing." site_url: "https://mkdocstrings.github.io/griffe-typingdoc" repo_url: "https://github.com/mkdocstrings/griffe-typingdoc" repo_name: "mkdocstrings/griffe-typingdoc" site_dir: "site" watch: [mkdocs.yml, README.md, CONTRIBUTING.md, CHANGELOG.md, src/griffe_typingdoc] copyright: Copyright © 2023 Timothée Mazzucotelli edit_uri: edit/main/docs/ validation: omitted_files: warn absolute_links: warn unrecognized_links: warn nav: - Home: - Overview: index.md - Changelog: changelog.md - Credits: credits.md - License: license.md - Examples: examples.md # defer to gen-files + literate-nav - API reference: - Griffe TypingDoc: reference/ - Development: - Contributing: contributing.md - Code of Conduct: code_of_conduct.md - Coverage report: coverage.md - Author's website: https://pawamoy.github.io/ theme: name: material custom_dir: docs/.overrides icon: logo: material/currency-sign features: - announce.dismiss - content.action.edit - content.action.view - content.code.annotate - content.code.copy - content.tooltips - navigation.footer - navigation.indexes - navigation.sections - navigation.tabs - navigation.tabs.sticky - navigation.top - search.highlight - search.suggest - toc.follow palette: - media: "(prefers-color-scheme)" toggle: icon: material/brightness-auto name: Switch to light mode - media: "(prefers-color-scheme: light)" scheme: default primary: teal accent: purple toggle: icon: material/weather-sunny name: Switch to dark mode - media: "(prefers-color-scheme: dark)" scheme: slate primary: black accent: lime toggle: icon: material/weather-night name: Switch to system preference extra_css: - css/material.css - css/mkdocstrings.css extra_javascript: - js/feedback.js markdown_extensions: - attr_list - admonition - callouts - footnotes - pymdownx.blocks.details - pymdownx.emoji: emoji_index: !!python/name:material.extensions.emoji.twemoji emoji_generator: !!python/name:material.extensions.emoji.to_svg - pymdownx.magiclink - pymdownx.snippets: base_path: [!relative $config_dir] check_paths: true - pymdownx.superfences - pymdownx.tabbed: alternate_style: true slugify: !!python/object/apply:pymdownx.slugs.slugify kwds: case: lower - pymdownx.tasklist: custom_checkbox: true - toc: permalink: "¤" plugins: - search - markdown-exec - gen-files: scripts: - scripts/gen_ref_nav.py - literate-nav: nav_file: SUMMARY.md - coverage - mkdocstrings: handlers: python: import: - https://docs.python.org/3/objects.inv - https://mkdocstrings.github.io/griffe/objects.inv paths: [src, docs/examples] options: docstring_options: ignore_init_summary: true docstring_section_style: list extensions: - griffe_typingdoc filters: ["!^_"] heading_level: 1 inherited_members: true merge_init_into_class: true separate_signature: true show_root_heading: true show_root_full_path: false show_signature_annotations: true show_source: true show_symbol_type_heading: true show_symbol_type_toc: true signature_crossrefs: true summary: true - git-revision-date-localized: enabled: !ENV [DEPLOY, false] enable_creation_date: true type: timeago - minify: minify_html: !ENV [DEPLOY, false] - group: enabled: !ENV [MATERIAL_INSIDERS, false] plugins: - typeset extra: social: - icon: fontawesome/brands/github link: https://github.com/pawamoy - icon: fontawesome/brands/mastodon link: https://fosstodon.org/@pawamoy - icon: fontawesome/brands/twitter link: https://twitter.com/pawamoy - icon: fontawesome/brands/gitter link: https://gitter.im/griffe-typingdoc/community - icon: fontawesome/brands/python link: https://pypi.org/project/griffe-typingdoc/ analytics: feedback: title: Was this page helpful? ratings: - icon: material/emoticon-happy-outline name: This page was helpful data: 1 note: Thanks for your feedback! - icon: material/emoticon-sad-outline name: This page could be improved data: 0 note: Let us know how we can improve this page. griffe-typingdoc-0.2.8/pyproject.toml000066400000000000000000000057331475475152500177240ustar00rootroot00000000000000[build-system] requires = ["pdm-backend"] build-backend = "pdm.backend" [project] name = "griffe-typingdoc" description = "Griffe extension for PEP 727 – Documentation Metadata in Typing." authors = [{name = "Timothée Mazzucotelli", email = "pawamoy@pm.me"}] license = {text = "ISC"} readme = "README.md" requires-python = ">=3.9" keywords = [] dynamic = ["version"] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Topic :: Documentation", "Topic :: Software Development", "Topic :: Software Development :: Documentation", "Topic :: Utilities", "Typing :: Typed", ] dependencies = [ "griffe>=0.49", "typing-extensions>=4.7", ] [project.urls] Homepage = "https://mkdocstrings.github.io/griffe-typingdoc" Documentation = "https://mkdocstrings.github.io/griffe-typingdoc" Changelog = "https://mkdocstrings.github.io/griffe-typingdoc/changelog" Repository = "https://github.com/mkdocstrings/griffe-typingdoc" Issues = "https://github.com/mkdocstrings/griffe-typingdoc/issues" Discussions = "https://github.com/mkdocstrings/griffe-typingdoc/discussions" Gitter = "https://gitter.im/mkdocstrings/griffe-typingdoc" Funding = "https://github.com/sponsors/pawamoy" [tool.pdm.version] source = "call" getter = "scripts.get_version:get_version" [tool.pdm.build] # Include as much as possible in the source distribution, to help redistributors. excludes = ["**/.pytest_cache"] source-includes = [ "config", "docs", "scripts", "share", "tests", "duties.py", "mkdocs.yml", "*.md", "LICENSE", ] [tool.pdm.build.wheel-data] # Manual pages can be included in the wheel. # Depending on the installation tool, they will be accessible to users. # pipx supports it, uv does not yet, see https://github.com/astral-sh/uv/issues/4731. data = [ {path = "share/**/*", relative-to = "."}, ] [dependency-groups] dev = [ # maintenance "build>=1.2", "git-changelog>=2.5", "twine>=5.1", # ci "duty>=1.4", "ruff>=0.4", "pytest>=8.2", "pytest-cov>=5.0", "pytest-randomly>=3.15", "pytest-xdist>=3.6", "mypy>=1.10", "types-markdown>=3.6", "types-pyyaml>=6.0", # docs "black>=24.4", "markdown-callouts>=0.4", "markdown-exec>=1.8", "mkdocs>=1.6", "mkdocs-coverage>=1.0", "mkdocs-gen-files>=0.5", "mkdocs-git-revision-date-localized-plugin>=1.2", "mkdocs-literate-nav>=0.6", "mkdocs-material>=9.5", "mkdocs-minify-plugin>=0.8", "mkdocstrings[python]>=0.25", # YORE: EOL 3.10: Remove line. "tomli>=2.0; python_version < '3.11'", ]griffe-typingdoc-0.2.8/scripts/000077500000000000000000000000001475475152500164675ustar00rootroot00000000000000griffe-typingdoc-0.2.8/scripts/gen_credits.py000066400000000000000000000147541475475152500213420ustar00rootroot00000000000000"""Script to generate the project's credits.""" from __future__ import annotations import os import sys from collections import defaultdict from collections.abc import Iterable from importlib.metadata import distributions from itertools import chain from pathlib import Path from textwrap import dedent from typing import Union from jinja2 import StrictUndefined from jinja2.sandbox import SandboxedEnvironment from packaging.requirements import Requirement # YORE: EOL 3.10: Replace block with line 2. if sys.version_info >= (3, 11): import tomllib else: import tomli as tomllib project_dir = Path(os.getenv("MKDOCS_CONFIG_DIR", ".")) with project_dir.joinpath("pyproject.toml").open("rb") as pyproject_file: pyproject = tomllib.load(pyproject_file) project = pyproject["project"] project_name = project["name"] devdeps = [dep for dep in pyproject["dependency-groups"]["dev"] if not dep.startswith("-e")] PackageMetadata = dict[str, Union[str, Iterable[str]]] Metadata = dict[str, PackageMetadata] def _merge_fields(metadata: dict) -> PackageMetadata: fields = defaultdict(list) for header, value in metadata.items(): fields[header.lower()].append(value.strip()) return { field: value if len(value) > 1 or field in ("classifier", "requires-dist") else value[0] for field, value in fields.items() } def _norm_name(name: str) -> str: return name.replace("_", "-").replace(".", "-").lower() def _requirements(deps: list[str]) -> dict[str, Requirement]: return {_norm_name((req := Requirement(dep)).name): req for dep in deps} def _extra_marker(req: Requirement) -> str | None: if not req.marker: return None try: return next(marker[2].value for marker in req.marker._markers if getattr(marker[0], "value", None) == "extra") except StopIteration: return None def _get_metadata() -> Metadata: metadata = {} for pkg in distributions(): name = _norm_name(pkg.name) # type: ignore[attr-defined,unused-ignore] metadata[name] = _merge_fields(pkg.metadata) # type: ignore[arg-type] metadata[name]["spec"] = set() metadata[name]["extras"] = set() metadata[name].setdefault("summary", "") _set_license(metadata[name]) return metadata def _set_license(metadata: PackageMetadata) -> None: license_field = metadata.get("license-expression", metadata.get("license", "")) license_name = license_field if isinstance(license_field, str) else " + ".join(license_field) check_classifiers = license_name in ("UNKNOWN", "Dual License", "") or license_name.count("\n") if check_classifiers: license_names = [] for classifier in metadata["classifier"]: if classifier.startswith("License ::"): license_names.append(classifier.rsplit("::", 1)[1].strip()) license_name = " + ".join(license_names) metadata["license"] = license_name or "?" def _get_deps(base_deps: dict[str, Requirement], metadata: Metadata) -> Metadata: deps = {} for dep_name, dep_req in base_deps.items(): if dep_name not in metadata or dep_name == "griffe-typingdoc": continue metadata[dep_name]["spec"] |= {str(spec) for spec in dep_req.specifier} # type: ignore[operator] metadata[dep_name]["extras"] |= dep_req.extras # type: ignore[operator] deps[dep_name] = metadata[dep_name] again = True while again: again = False for pkg_name in metadata: if pkg_name in deps: for pkg_dependency in metadata[pkg_name].get("requires-dist", []): requirement = Requirement(pkg_dependency) dep_name = _norm_name(requirement.name) extra_marker = _extra_marker(requirement) if ( dep_name in metadata and dep_name not in deps and dep_name != project["name"] and (not extra_marker or extra_marker in deps[pkg_name]["extras"]) ): metadata[dep_name]["spec"] |= {str(spec) for spec in requirement.specifier} # type: ignore[operator] deps[dep_name] = metadata[dep_name] again = True return deps def _render_credits() -> str: metadata = _get_metadata() dev_dependencies = _get_deps(_requirements(devdeps), metadata) prod_dependencies = _get_deps( _requirements( chain( # type: ignore[arg-type] project.get("dependencies", []), chain(*project.get("optional-dependencies", {}).values()), ), ), metadata, ) template_data = { "project_name": project_name, "prod_dependencies": sorted(prod_dependencies.values(), key=lambda dep: str(dep["name"]).lower()), "dev_dependencies": sorted(dev_dependencies.values(), key=lambda dep: str(dep["name"]).lower()), "more_credits": "http://pawamoy.github.io/credits/", } template_text = dedent( """ # Credits These projects were used to build *{{ project_name }}*. **Thank you!** [Python](https://www.python.org/) | [uv](https://github.com/astral-sh/uv) | [copier-uv](https://github.com/pawamoy/copier-uv) {% macro dep_line(dep) -%} [{{ dep.name }}](https://pypi.org/project/{{ dep.name }}/) | {{ dep.summary }} | {{ ("`" ~ dep.spec|sort(reverse=True)|join(", ") ~ "`") if dep.spec else "" }} | `{{ dep.version }}` | {{ dep.license }} {%- endmacro %} {% if prod_dependencies -%} ### Runtime dependencies Project | Summary | Version (accepted) | Version (last resolved) | License ------- | ------- | ------------------ | ----------------------- | ------- {% for dep in prod_dependencies -%} {{ dep_line(dep) }} {% endfor %} {% endif -%} {% if dev_dependencies -%} ### Development dependencies Project | Summary | Version (accepted) | Version (last resolved) | License ------- | ------- | ------------------ | ----------------------- | ------- {% for dep in dev_dependencies -%} {{ dep_line(dep) }} {% endfor %} {% endif -%} {% if more_credits %}**[More credits from the author]({{ more_credits }})**{% endif %} """, ) jinja_env = SandboxedEnvironment(undefined=StrictUndefined) return jinja_env.from_string(template_text).render(**template_data) print(_render_credits()) griffe-typingdoc-0.2.8/scripts/gen_ref_nav.py000066400000000000000000000022551475475152500213160ustar00rootroot00000000000000"""Generate the code reference pages and navigation.""" from pathlib import Path import mkdocs_gen_files nav = mkdocs_gen_files.Nav() mod_symbol = '' root = Path(__file__).parent.parent src = root / "src" for path in sorted(src.rglob("*.py")): module_path = path.relative_to(src).with_suffix("") doc_path = path.relative_to(src).with_suffix(".md") full_doc_path = Path("reference", doc_path) parts = tuple(module_path.parts) if parts[-1] == "__init__": parts = parts[:-1] doc_path = doc_path.with_name("index.md") full_doc_path = full_doc_path.with_name("index.md") elif parts[-1].startswith("_"): continue nav_parts = [f"{mod_symbol} {part}" for part in parts] nav[tuple(nav_parts)] = doc_path.as_posix() with mkdocs_gen_files.open(full_doc_path, "w") as fd: ident = ".".join(parts) fd.write(f"---\ntitle: {ident}\n---\n\n::: {ident}") mkdocs_gen_files.set_edit_path(full_doc_path, ".." / path.relative_to(root)) with mkdocs_gen_files.open("reference/SUMMARY.md", "w") as nav_file: nav_file.writelines(nav.build_literate_nav()) griffe-typingdoc-0.2.8/scripts/get_version.py000066400000000000000000000021141475475152500213630ustar00rootroot00000000000000"""Get current project version from Git tags or changelog.""" import re from contextlib import suppress from pathlib import Path from pdm.backend.hooks.version import SCMVersion, Version, default_version_formatter, get_version_from_scm _root = Path(__file__).parent.parent _changelog = _root / "CHANGELOG.md" _changelog_version_re = re.compile(r"^## \[(\d+\.\d+\.\d+)\].*$") _default_scm_version = SCMVersion(Version("0.0.0"), None, False, None, None) # noqa: FBT003 def get_version() -> str: """Get current project version from Git tags or changelog.""" scm_version = get_version_from_scm(_root) or _default_scm_version if scm_version.version <= Version("0.1"): # Missing Git tags? with suppress(OSError, StopIteration): # noqa: SIM117 with _changelog.open("r", encoding="utf8") as file: match = next(filter(None, map(_changelog_version_re.match, file))) scm_version = scm_version._replace(version=Version(match.group(1))) return default_version_formatter(scm_version) if __name__ == "__main__": print(get_version()) griffe-typingdoc-0.2.8/scripts/make000077700000000000000000000000001475475152500206112make.pyustar00rootroot00000000000000griffe-typingdoc-0.2.8/scripts/make.py000077500000000000000000000142261475475152500177660ustar00rootroot00000000000000#!/usr/bin/env python3 """Management commands.""" from __future__ import annotations import os import shutil import subprocess import sys from contextlib import contextmanager from pathlib import Path from textwrap import dedent from typing import TYPE_CHECKING, Any if TYPE_CHECKING: from collections.abc import Iterator PYTHON_VERSIONS = os.getenv("PYTHON_VERSIONS", "3.9 3.10 3.11 3.12 3.13 3.14").split() def shell(cmd: str, *, capture_output: bool = False, **kwargs: Any) -> str | None: """Run a shell command.""" if capture_output: return subprocess.check_output(cmd, shell=True, text=True, **kwargs) # noqa: S602 subprocess.run(cmd, shell=True, check=True, stderr=subprocess.STDOUT, **kwargs) # noqa: S602 return None @contextmanager def environ(**kwargs: str) -> Iterator[None]: """Temporarily set environment variables.""" original = dict(os.environ) os.environ.update(kwargs) try: yield finally: os.environ.clear() os.environ.update(original) def uv_install(venv: Path) -> None: """Install dependencies using uv.""" with environ(UV_PROJECT_ENVIRONMENT=str(venv), PYO3_USE_ABI3_FORWARD_COMPATIBILITY="1"): if "CI" in os.environ: shell("uv sync --no-editable") else: shell("uv sync") def setup() -> None: """Setup the project.""" if not shutil.which("uv"): raise ValueError("make: setup: uv must be installed, see https://github.com/astral-sh/uv") print("Installing dependencies (default environment)") default_venv = Path(".venv") if not default_venv.exists(): shell("uv venv") uv_install(default_venv) if PYTHON_VERSIONS: for version in PYTHON_VERSIONS: print(f"\nInstalling dependencies (python{version})") venv_path = Path(f".venvs/{version}") if not venv_path.exists(): shell(f"uv venv --python {version} {venv_path}") with environ(UV_PROJECT_ENVIRONMENT=str(venv_path.resolve())): uv_install(venv_path) def run(version: str, cmd: str, *args: str, **kwargs: Any) -> None: """Run a command in a virtual environment.""" kwargs = {"check": True, **kwargs} uv_run = ["uv", "run", "--no-sync"] if version == "default": with environ(UV_PROJECT_ENVIRONMENT=".venv"): subprocess.run([*uv_run, cmd, *args], **kwargs) # noqa: S603, PLW1510 else: with environ(UV_PROJECT_ENVIRONMENT=f".venvs/{version}", MULTIRUN="1"): subprocess.run([*uv_run, cmd, *args], **kwargs) # noqa: S603, PLW1510 def multirun(cmd: str, *args: str, **kwargs: Any) -> None: """Run a command for all configured Python versions.""" if PYTHON_VERSIONS: for version in PYTHON_VERSIONS: run(version, cmd, *args, **kwargs) else: run("default", cmd, *args, **kwargs) def allrun(cmd: str, *args: str, **kwargs: Any) -> None: """Run a command in all virtual environments.""" run("default", cmd, *args, **kwargs) if PYTHON_VERSIONS: multirun(cmd, *args, **kwargs) def clean() -> None: """Delete build artifacts and cache files.""" paths_to_clean = ["build", "dist", "htmlcov", "site", ".coverage*", ".pdm-build"] for path in paths_to_clean: shutil.rmtree(path, ignore_errors=True) cache_dirs = {".cache", ".pytest_cache", ".mypy_cache", ".ruff_cache", "__pycache__"} for dirpath in Path(".").rglob("*/"): if dirpath.parts[0] not in (".venv", ".venvs") and dirpath.name in cache_dirs: shutil.rmtree(dirpath, ignore_errors=True) def vscode() -> None: """Configure VSCode to work on this project.""" shutil.copytree("config/vscode", ".vscode", dirs_exist_ok=True) def main() -> int: """Main entry point.""" args = list(sys.argv[1:]) if not args or args[0] == "help": if len(args) > 1: run("default", "duty", "--help", args[1]) else: print( dedent( """ Available commands help Print this help. Add task name to print help. setup Setup all virtual environments (install dependencies). run Run a command in the default virtual environment. multirun Run a command for all configured Python versions. allrun Run a command in all virtual environments. 3.x Run a command in the virtual environment for Python 3.x. clean Delete build artifacts and cache files. vscode Configure VSCode to work on this project. """, ), flush=True, ) if os.path.exists(".venv"): print("\nAvailable tasks", flush=True) run("default", "duty", "--list") return 0 while args: cmd = args.pop(0) if cmd == "run": run("default", *args) return 0 if cmd == "multirun": multirun(*args) return 0 if cmd == "allrun": allrun(*args) return 0 if cmd.startswith("3."): run(cmd, *args) return 0 opts = [] while args and (args[0].startswith("-") or "=" in args[0]): opts.append(args.pop(0)) if cmd == "clean": clean() elif cmd == "setup": setup() elif cmd == "vscode": vscode() elif cmd == "check": multirun("duty", "check-quality", "check-types", "check-docs") run("default", "duty", "check-api") elif cmd in {"check-quality", "check-docs", "check-types", "test"}: multirun("duty", cmd, *opts) else: run("default", "duty", cmd, *opts) return 0 if __name__ == "__main__": try: sys.exit(main()) except subprocess.CalledProcessError as process: if process.output: print(process.output, file=sys.stderr) sys.exit(process.returncode) griffe-typingdoc-0.2.8/src/000077500000000000000000000000001475475152500155675ustar00rootroot00000000000000griffe-typingdoc-0.2.8/src/griffe_typingdoc/000077500000000000000000000000001475475152500211115ustar00rootroot00000000000000griffe-typingdoc-0.2.8/src/griffe_typingdoc/__init__.py000066400000000000000000000003601475475152500232210ustar00rootroot00000000000000"""Griffe TypingDoc package. Griffe extension for PEP 727 - Documentation Metadata in Typing. """ from __future__ import annotations from griffe_typingdoc._extension import TypingDocExtension __all__: list[str] = ["TypingDocExtension"] griffe-typingdoc-0.2.8/src/griffe_typingdoc/_docstrings.py000066400000000000000000000101511475475152500237770ustar00rootroot00000000000000"""Helpers to build docstring sections.""" from __future__ import annotations from typing import TYPE_CHECKING, Any from griffe import ( DocstringParameter, DocstringRaise, DocstringReceive, DocstringReturn, DocstringSectionAdmonition, DocstringSectionOtherParameters, DocstringSectionParameters, DocstringSectionRaises, DocstringSectionReceives, DocstringSectionReturns, DocstringSectionWarns, DocstringSectionYields, DocstringWarn, DocstringYield, ) if TYPE_CHECKING: from collections.abc import Iterable from griffe import Function, Parameter def _no_self_params(func: Function) -> list[Parameter]: if func.parent and func.parent.is_class and func.parameters and func.parameters[0].name in {"self", "cls"}: return list(func.parameters)[1:] return list(func.parameters) def _to_parameters_section(params_dict: dict[str, dict[str, Any]], func: Function) -> DocstringSectionParameters: return DocstringSectionParameters( [ DocstringParameter( name=param_name, description=param_doc["description"], annotation=param_doc["annotation"], value=func.parameters[param_name].default, # type: ignore[arg-type] ) for param_name, param_doc in params_dict.items() ], ) def _to_other_parameters_section(params_dict: dict[str, dict[str, Any]]) -> DocstringSectionOtherParameters: return DocstringSectionOtherParameters( [ DocstringParameter( name=param_name, description=param_doc["description"], annotation=param_doc["annotation"], ) for param_name, param_doc in params_dict.items() ], ) def _to_yields_section(yield_data: Iterable[dict[str, Any]]) -> DocstringSectionYields: return DocstringSectionYields( [ DocstringYield( name=yield_dict.get("name", ""), description=yield_dict.get("doc", ""), annotation=yield_dict["annotation"], ) for yield_dict in yield_data ], ) def _to_receives_section(receive_data: Iterable[dict[str, Any]]) -> DocstringSectionReceives: return DocstringSectionReceives( [ DocstringReceive( name=receive_dict.get("name", ""), description=receive_dict.get("doc", ""), annotation=receive_dict["annotation"], ) for receive_dict in receive_data ], ) def _to_returns_section(return_data: Iterable[dict[str, Any]]) -> DocstringSectionReturns: return DocstringSectionReturns( [ DocstringReturn( name=return_dict.get("name", ""), description=return_dict.get("doc", ""), annotation=return_dict["annotation"], ) for return_dict in return_data ], ) def _to_warns_section(warn_data: Iterable[dict[str, Any]]) -> DocstringSectionWarns: return DocstringSectionWarns( [ DocstringWarn( annotation=warn_dict["annotation"], description=warn_dict.get("description", ""), ) for warn_dict in warn_data ], ) def _to_raises_section(raise_data: Iterable[dict[str, Any]]) -> DocstringSectionRaises: return DocstringSectionRaises( [ DocstringRaise( annotation=raise_dict["annotation"], description=raise_dict.get("description", ""), ) for raise_dict in raise_data ], ) def _to_deprecated_section(deprecation_data: dict[str, Any]) -> DocstringSectionAdmonition: description = deprecation_data["description"] description_lines = deprecation_data["description"].split("\n") if len(description_lines) > 1: title = description_lines[0].strip() description = "\n".join(description_lines[1:]).strip() else: title = description description = "" return DocstringSectionAdmonition(kind="danger", title=title, text=description) griffe-typingdoc-0.2.8/src/griffe_typingdoc/_dynamic.py000066400000000000000000000062161475475152500232530ustar00rootroot00000000000000"""Helpers to get documentation metadata dynamically.""" from __future__ import annotations from typing import TYPE_CHECKING, Any, get_type_hints from griffe_typingdoc._docstrings import _to_parameters_section if TYPE_CHECKING: from griffe import Attribute, Function, ObjectNode from griffe.docstrings.dataclasses import ( DocstringSectionAdmonition, DocstringSectionOtherParameters, DocstringSectionParameters, DocstringSectionRaises, DocstringSectionReceives, DocstringSectionReturns, DocstringSectionWarns, DocstringSectionYields, ) def _hints(node: ObjectNode) -> dict[str, str]: try: return get_type_hints(node.obj, include_extras=True) except TypeError: if node.parent: return _hints(node.parent) return {} def _doc(name: str, hints: dict[str, Any]) -> str | None: try: return hints[name].__metadata__[0].documentation except (AttributeError, KeyError): return None def _attribute_docs(attr: Attribute, *, node: ObjectNode, **kwargs: Any) -> str: # noqa: ARG001 return _doc(attr.name, _hints(node)) or "" def _parameters_docs( func: Function, *, node: ObjectNode, **kwargs: Any, # noqa: ARG001 ) -> DocstringSectionParameters | None: hints = _hints(node) params_doc: dict[str, dict[str, Any]] = { name: {"description": _doc(name, hints)} for name in hints if name != "return" } if params_doc: return _to_parameters_section(params_doc, func) return None # FIXME: Implement this function. def _other_parameters_docs( func: Function, # noqa: ARG001 *, node: ObjectNode, # noqa: ARG001 **kwargs: Any, # noqa: ARG001 ) -> DocstringSectionOtherParameters | None: return None # FIXME: Implement this function. def _deprecated_docs( attr_or_func: Attribute | Function, # noqa: ARG001 *, node: ObjectNode, # noqa: ARG001 **kwargs: Any, # noqa: ARG001 ) -> DocstringSectionAdmonition | None: return None # FIXME: Implement this function. def _raises_docs( attr_or_func: Attribute | Function, # noqa: ARG001 *, node: ObjectNode, # noqa: ARG001 **kwargs: Any, # noqa: ARG001 ) -> DocstringSectionRaises | None: return None # FIXME: Implement this function. def _warns_docs( attr_or_func: Attribute | Function, # noqa: ARG001 *, node: ObjectNode, # noqa: ARG001 **kwargs: Any, # noqa: ARG001 ) -> DocstringSectionWarns | None: return None # FIXME: Implement this function. def _yields_docs( func: Function, # noqa: ARG001 *, node: ObjectNode, # noqa: ARG001 **kwargs: Any, # noqa: ARG001 ) -> DocstringSectionYields | None: return None # FIXME: Implement this function. def _receives_docs( func: Function, # noqa: ARG001 *, node: ObjectNode, # noqa: ARG001 **kwargs: Any, # noqa: ARG001 ) -> DocstringSectionReceives | None: return None # FIXME: Implement this function. def _returns_docs( func: Function, # noqa: ARG001 *, node: ObjectNode, # noqa: ARG001 **kwargs: Any, # noqa: ARG001 ) -> DocstringSectionReturns | None: return None griffe-typingdoc-0.2.8/src/griffe_typingdoc/_extension.py000066400000000000000000000120141475475152500236340ustar00rootroot00000000000000"""This module defines the Griffe TypingDoc extension.""" from __future__ import annotations from typing import TYPE_CHECKING, Any from griffe import Docstring, Extension, Function, ObjectNode from griffe_typingdoc import _dynamic, _static if TYPE_CHECKING: import ast from typing import Annotated from griffe.dataclasses import Attribute, Module, Object from typing_extensions import Doc class TypingDocExtension(Extension): """Griffe extension that reads documentation from `typing.Doc`.""" def __init__(self) -> None: self._handled: set[str] = set() def _handle_attribute(self, attr: Attribute, /, *, node: ObjectNode | None = None) -> None: if attr.path in self._handled: return self._handled.add(attr.path) module = _dynamic if node else _static new_sections = ( docstring := module._attribute_docs(attr, node=node), deprecated_section := module._deprecated_docs(attr, node=node), raises_section := module._raises_docs(attr, node=node), warns_section := module._warns_docs(attr, node=node), ) if not any(new_sections): return if not attr.docstring: attr.docstring = Docstring(docstring, parent=attr) sections = attr.docstring.parsed if deprecated_section: sections.insert(0, deprecated_section) if raises_section: sections.append(raises_section) if warns_section: sections.append(warns_section) def _handle_function(self, func: Function, /, *, node: ObjectNode | None = None) -> None: if func.path in self._handled: return self._handled.add(func.path) module = _dynamic if node else _static new_sections = ( deprecated_section := module._deprecated_docs(func, node=node), params_section := module._parameters_docs(func, node=node), other_params_section := module._other_parameters_docs(func, node=node), warns_section := module._warns_docs(func, node=node), raises_section := module._raises_docs(func, node=node), yields_section := module._yields_docs(func, node=node), receives_section := module._receives_docs(func, node=node), returns_section := module._returns_docs(func, node=node), ) if not any(new_sections): return if not func.docstring: func.docstring = Docstring("", parent=func) sections = func.docstring.parsed if other_params_section: sections.insert(1, other_params_section) if params_section: sections.insert(1, params_section) if deprecated_section: sections.insert(0, deprecated_section) if raises_section: sections.append(raises_section) if warns_section: sections.append(warns_section) if yields_section: sections.append(yields_section) if receives_section: sections.append(receives_section) if returns_section: sections.append(returns_section) def _handle_object(self, obj: Object) -> None: if obj.is_alias: return if obj.is_module or obj.is_class: for member in obj.members.values(): self._handle_object(member) elif obj.is_function: self._handle_function(obj) elif obj.is_attribute: self._handle_attribute(obj) def on_package_loaded( self, *, pkg: Annotated[ Module, Doc("The top-level module representing a package."), ], **kwargs: Any, # noqa: ARG002 ) -> None: """Post-process Griffe packages recursively (non-yet handled objects only).""" self._handle_object(pkg) def on_function_instance( self, *, node: Annotated[ ast.AST | ObjectNode, Doc("The object/AST node describing the function or its definition."), ], func: Annotated[ Function, Doc("""The Griffe function just instantiated."""), ], **kwargs: Any, # noqa: ARG002 ) -> None: """Post-process Griffe functions to add a parameters section. It applies only for dynamic analysis. """ if isinstance(node, ObjectNode): self._handle_function(func, node=node) def on_attribute_instance( self, *, node: Annotated[ ast.AST | ObjectNode, Doc("The object/AST node describing the attribute or its definition."), ], attr: Annotated[ Attribute, Doc("The Griffe attribute just instantiated."), ], **kwargs: Any, # noqa: ARG002 ) -> None: """Post-process Griffe attributes to create their docstring. It applies only for dynamic analysis. """ if isinstance(node, ObjectNode): self._handle_attribute(attr, node=node) griffe-typingdoc-0.2.8/src/griffe_typingdoc/_static.py000066400000000000000000000234141475475152500231150ustar00rootroot00000000000000"""Helpers to get documentation metadata statically.""" from __future__ import annotations import inspect from ast import literal_eval from collections import defaultdict from typing import TYPE_CHECKING, Any from griffe import Expr, ExprCall, ExprSubscript, ExprTuple, ParameterKind from griffe_typingdoc._docstrings import ( _no_self_params, _to_deprecated_section, _to_other_parameters_section, _to_parameters_section, _to_raises_section, _to_receives_section, _to_returns_section, _to_warns_section, _to_yields_section, ) if TYPE_CHECKING: from collections.abc import Sequence from griffe import Function from griffe.dataclasses import Attribute from griffe.docstrings.dataclasses import ( DocstringSectionAdmonition, DocstringSectionParameters, DocstringSectionRaises, DocstringSectionReceives, DocstringSectionReturns, DocstringSectionWarns, DocstringSectionYields, ) def _literal(value: str | Expr) -> str: return inspect.cleandoc(literal_eval(str(value))) def _set_metadata_doc(metadata: dict[str, Any], data: ExprCall) -> None: metadata["doc"] = _literal(data.arguments[0]) def _set_metadata_deprecated(metadata: dict[str, Any], data: ExprCall) -> None: metadata["deprecated"] = _literal(data.arguments[0]) def _set_metadata_name(metadata: dict[str, Any], data: ExprCall) -> None: metadata["name"] = _literal(data.arguments[0]) def _set_metadata_raises(metadata: dict[str, Any], data: ExprCall) -> None: metadata["raises"].append((data.arguments[0], _literal(data.arguments[1]))) def _set_metadata_warns(metadata: dict[str, Any], data: ExprCall) -> None: metadata["warns"].append((data.arguments[0], _literal(data.arguments[1]))) _set_metadata_map = { "typing.Doc": _set_metadata_doc, "typing_extensions.Doc": _set_metadata_doc, "typing.deprecated": _set_metadata_deprecated, "typing_extensions.deprecated": _set_metadata_deprecated, "typing.Name": _set_metadata_name, "typing_extensions.Name": _set_metadata_name, "typing.Raises": _set_metadata_raises, "typing_extensions.Raises": _set_metadata_raises, "typing.Warns": _set_metadata_warns, "typing_extensions.Warns": _set_metadata_warns, } def _set_metadata(metadata: dict[str, Any], data: ExprCall) -> None: if data.function.canonical_path in _set_metadata_map: _set_metadata_map[data.function.canonical_path](metadata, data) def _metadata(annotation: str | Expr | None) -> dict[str, Any]: metadata: dict[str, Any] = {"raises": [], "warns": []} if isinstance(annotation, ExprSubscript) and annotation.canonical_path in { "typing.Annotated", "typing_extensions.Annotated", }: annotated_data: Sequence[str | Expr] if isinstance(annotation.slice, ExprTuple): annotation, *annotated_data = annotation.slice.elements else: annotation = annotation.slice annotated_data = () for data in annotated_data: if isinstance(data, ExprCall): _set_metadata(metadata, data) return metadata def _attribute_docs(attr: Attribute, **kwargs: Any) -> str: # noqa: ARG001 return _metadata(attr.annotation).get("doc", "") def _parameters_docs(func: Function, **kwargs: Any) -> DocstringSectionParameters | None: # noqa: ARG001 params_data: dict[str, dict[str, Any]] = defaultdict(dict) for parameter in _no_self_params(func): stars = {ParameterKind.var_positional: "*", ParameterKind.var_keyword: "**"}.get(parameter.kind, "") # type: ignore[arg-type] param_name = f"{stars}{parameter.name}" metadata = _metadata(parameter.annotation) if "deprecated" in metadata or "doc" in metadata: description = f"{metadata.get('deprecated', '')} {metadata.get('doc', '')}".lstrip() params_data[param_name]["description"] = description params_data[param_name]["annotation"] = parameter.annotation if params_data: return _to_parameters_section(params_data, func) return None def _other_parameters_docs(func: Function, **kwargs: Any) -> DocstringSectionParameters | None: # noqa: ARG001 for parameter in func.parameters: if parameter.kind is ParameterKind.var_keyword: annotation = parameter.annotation if isinstance(annotation, ExprSubscript) and annotation.canonical_path in { "typing.Annotated", "typing_extensions.Annotated", }: annotation = annotation.slice.elements[0] # type: ignore[union-attr] if isinstance(annotation, ExprSubscript) and annotation.canonical_path in { "typing.Unpack", "typing_extensions.Unpack", }: slice_path = annotation.slice.canonical_path # type: ignore[union-attr] typed_dict = func.modules_collection[slice_path] params_data = { attr.name: {"annotation": attr.annotation, "description": description} for attr in typed_dict.members.values() if (description := _metadata(attr.annotation).get("doc")) is not None } if params_data: return _to_other_parameters_section(params_data) break return None def _yields_docs(func: Function, **kwargs: Any) -> DocstringSectionYields | None: # noqa: ARG001 yield_annotation = None annotation = func.returns if isinstance(annotation, ExprSubscript): if annotation.canonical_path in {"typing.Generator", "typing_extensions.Generator"}: yield_annotation = annotation.slice.elements[0] # type: ignore[union-attr] elif annotation.canonical_path in {"typing.Iterator", "typing_extensions.Iterator"}: yield_annotation = annotation.slice if yield_annotation: if isinstance(yield_annotation, ExprSubscript) and yield_annotation.is_tuple: yield_elements = yield_annotation.slice.elements # type: ignore[union-attr] else: yield_elements = [yield_annotation] yield_data = [ {"annotation": element, **metadata} for element in yield_elements if "doc" in (metadata := _metadata(element)) ] if yield_data: return _to_yields_section(yield_data) return None def _receives_docs(func: Function, **kwargs: Any) -> DocstringSectionReceives | None: # noqa: ARG001 receive_annotation = None annotation = func.returns if isinstance(annotation, ExprSubscript) and annotation.canonical_path in { "typing.Generator", "typing_extensions.Generator", }: receive_annotation = annotation.slice.elements[1] # type: ignore[union-attr] if receive_annotation: if isinstance(receive_annotation, ExprSubscript) and receive_annotation.is_tuple: receive_elements = receive_annotation.slice.elements # type: ignore[union-attr] else: receive_elements = [receive_annotation] receive_data = [ {"annotation": element, **metadata} for element in receive_elements if "doc" in (metadata := _metadata(element)) ] if receive_data: return _to_receives_section(receive_data) return None def _returns_docs(func: Function, **kwargs: Any) -> DocstringSectionReturns | None: # noqa: ARG001 return_annotation = None annotation = func.returns if isinstance(annotation, ExprSubscript) and annotation.canonical_path in { "typing.Generator", "typing_extensions.Generator", }: return_annotation = annotation.slice.elements[2] # type: ignore[union-attr] elif isinstance(annotation, ExprSubscript) and annotation.canonical_path in { "typing.Annotated", "typing_extensions.Annotated", }: return_annotation = annotation if return_annotation: if isinstance(return_annotation, ExprSubscript) and return_annotation.is_tuple: return_elements = return_annotation.slice.elements # type: ignore[union-attr] else: return_elements = [return_annotation] return_data = [ {"annotation": element, **metadata} for element in return_elements if "doc" in (metadata := _metadata(element)) ] if return_data: return _to_returns_section(return_data) return None def _warns_docs(attr_or_func: Attribute | Function, **kwargs: Any) -> DocstringSectionWarns | None: # noqa: ARG001 if attr_or_func.is_attribute: annotation = attr_or_func.annotation elif attr_or_func.is_function: annotation = attr_or_func.returns metadata = _metadata(annotation) if metadata["warns"]: return _to_warns_section({"annotation": warned[0], "description": warned[1]} for warned in metadata["warns"]) return None def _raises_docs(attr_or_func: Attribute | Function, **kwargs: Any) -> DocstringSectionRaises | None: # noqa: ARG001 if attr_or_func.is_attribute: annotation = attr_or_func.annotation elif attr_or_func.is_function: annotation = attr_or_func.returns metadata = _metadata(annotation) if metadata["raises"]: return _to_raises_section({"annotation": raised[0], "description": raised[1]} for raised in metadata["raises"]) return None def _deprecated_docs( attr_or_func: Attribute | Function, **kwargs: Any, # noqa: ARG001 ) -> DocstringSectionAdmonition | None: if attr_or_func.is_attribute: annotation = attr_or_func.annotation elif attr_or_func.is_function: annotation = attr_or_func.returns metadata = _metadata(annotation) if "deprecated" in metadata: return _to_deprecated_section({"description": metadata["deprecated"]}) return None griffe-typingdoc-0.2.8/src/griffe_typingdoc/debug.py000066400000000000000000000054411475475152500225550ustar00rootroot00000000000000"""Debugging utilities.""" from __future__ import annotations import os import platform import sys from dataclasses import dataclass from importlib import metadata @dataclass class Variable: """Dataclass describing an environment variable.""" name: str """Variable name.""" value: str """Variable value.""" @dataclass class Package: """Dataclass describing a Python package.""" name: str """Package name.""" version: str """Package version.""" @dataclass class Environment: """Dataclass to store environment information.""" interpreter_name: str """Python interpreter name.""" interpreter_version: str """Python interpreter version.""" interpreter_path: str """Path to Python executable.""" platform: str """Operating System.""" packages: list[Package] """Installed packages.""" variables: list[Variable] """Environment variables.""" def _interpreter_name_version() -> tuple[str, str]: if hasattr(sys, "implementation"): impl = sys.implementation.version version = f"{impl.major}.{impl.minor}.{impl.micro}" kind = impl.releaselevel if kind != "final": version += kind[0] + str(impl.serial) return sys.implementation.name, version return "", "0.0.0" def get_version(dist: str = "griffe-typingdoc") -> str: """Get version of the given distribution. Parameters: dist: A distribution name. Returns: A version number. """ try: return metadata.version(dist) except metadata.PackageNotFoundError: return "0.0.0" def get_debug_info() -> Environment: """Get debug/environment information. Returns: Environment information. """ py_name, py_version = _interpreter_name_version() packages = ["griffe-typingdoc"] variables = ["PYTHONPATH", *[var for var in os.environ if var.startswith("GRIFFE_TYPINGDOC")]] return Environment( interpreter_name=py_name, interpreter_version=py_version, interpreter_path=sys.executable, platform=platform.platform(), variables=[Variable(var, val) for var in variables if (val := os.getenv(var))], packages=[Package(pkg, get_version(pkg)) for pkg in packages], ) def print_debug_info() -> None: """Print debug/environment information.""" info = get_debug_info() print(f"- __System__: {info.platform}") print(f"- __Python__: {info.interpreter_name} {info.interpreter_version} ({info.interpreter_path})") print("- __Environment variables__:") for var in info.variables: print(f" - `{var.name}`: `{var.value}`") print("- __Installed packages__:") for pkg in info.packages: print(f" - `{pkg.name}` v{pkg.version}") if __name__ == "__main__": print_debug_info() griffe-typingdoc-0.2.8/src/griffe_typingdoc/py.typed000066400000000000000000000000001475475152500225760ustar00rootroot00000000000000griffe-typingdoc-0.2.8/tests/000077500000000000000000000000001475475152500161425ustar00rootroot00000000000000griffe-typingdoc-0.2.8/tests/__init__.py000066400000000000000000000002511475475152500202510ustar00rootroot00000000000000"""Tests suite for `griffe_typingdoc`.""" from pathlib import Path TESTS_DIR = Path(__file__).parent TMP_DIR = TESTS_DIR / "tmp" FIXTURES_DIR = TESTS_DIR / "fixtures" griffe-typingdoc-0.2.8/tests/conftest.py000066400000000000000000000000571475475152500203430ustar00rootroot00000000000000"""Configuration for the pytest test suite.""" griffe-typingdoc-0.2.8/tests/test_extension.py000066400000000000000000000256111475475152500215740ustar00rootroot00000000000000"""Tests for the Griffe extension.""" import pytest from griffe import DocstringSectionKind, Extensions, GriffeLoader, temporary_visited_package from griffe_typingdoc import TypingDocExtension typing_imports = ( "from typing import Annotated, Doc, Generator, Iterator, Name, NotRequired, Raises, TypedDict, Unpack, Warns" ) warning_imports = "from warnings import deprecated" # NOTE: Important! The value in calls to `Doc` will be parsed as a Name expression # if it is valid Python syntax for names. To make sure it is correctly parsed as a string, # it must contain invalid syntax for names, such as a dot at the end. # The alternative solution would be to add `from __future__ import annotations` # at the beginning of each temporary visited module. def test_extension_on_itself() -> None: """Load our own package using the extension, assert a parameters section is added to the parsed docstring.""" loader = GriffeLoader(extensions=Extensions(TypingDocExtension())) typingdoc = loader.load("griffe_typingdoc") sections = typingdoc["TypingDocExtension.on_function_instance"].docstring.parsed assert len(sections) == 2 assert sections[1].kind is DocstringSectionKind.parameters assert sections[1].value[1].description == "The Griffe function just instantiated." def test_attribute_doc() -> None: """Read documentation for attributes.""" with temporary_visited_package( "package", modules={"__init__.py": f"{typing_imports}\na: Annotated[str, Doc('Hello.')]"}, extensions=Extensions(TypingDocExtension()), ) as package: assert package["a"].docstring.value == "Hello." def test_parameter_doc() -> None: """Read documentation for parameters.""" with temporary_visited_package( "package", modules={"__init__.py": f"{typing_imports}\ndef f(a: Annotated[str, Doc('Hello.')]): ..."}, extensions=Extensions(TypingDocExtension()), ) as package: assert package["f"].docstring.parsed[1].value[0].description == "Hello." def test_other_parameter_doc() -> None: """Read documentation for other parameters, in unpack/typeddict annotations.""" with temporary_visited_package( "package", modules={ "__init__.py": f""" {typing_imports} class OtherParameters(TypedDict, total=False): param1: Annotated[NotRequired[str], Doc("Hello.")] def f(**kwargs: Annotated[Unpack[OtherParameters], Doc("See other parameters.")]): ... """, }, extensions=Extensions(TypingDocExtension()), ) as package: assert package["f"].docstring.parsed[2].value[0].description == "Hello." def test_iterator_doc() -> None: """Read documentation in iterator annotations.""" with temporary_visited_package( "package", modules={ "__init__.py": f""" {typing_imports} def f() -> Iterator[Annotated[int, Doc("Yielded hello.")]]: ... """, }, extensions=Extensions(TypingDocExtension()), ) as package: assert package["f"].docstring.parsed[1].value[0].description == "Yielded hello." def test_generator_doc() -> None: """Read documentation in generator annotations.""" with temporary_visited_package( "package", modules={ "__init__.py": f""" {typing_imports} def f() -> Generator[ Annotated[int, Doc("Yielded hello.")], Annotated[int, Doc("Received hello.")], Annotated[int, Doc("Returned hello.")], ]: ... """, }, extensions=Extensions(TypingDocExtension()), ) as package: sections = package["f"].docstring.parsed assert sections[1].value[0].description == "Yielded hello." assert sections[2].value[0].description == "Received hello." assert sections[3].value[0].description == "Returned hello." def test_generator_tuples() -> None: """Read documentation in generator annotations (in tuples).""" with temporary_visited_package( "package", modules={ "__init__.py": f""" {typing_imports} def f() -> Generator[ tuple[ Annotated[int, Doc("First yielded.")], Annotated[float, Doc("Second yielded.")], ], tuple[ Annotated[int, Doc("First received.")], Annotated[float, Doc("Second received.")], ], tuple[ Annotated[int, Doc("First returned.")], Annotated[float, Doc("Second returned.")], ], ]: ... """, }, extensions=Extensions(TypingDocExtension()), ) as package: sections = package["f"].docstring.parsed assert sections[1].value[0].description == "First yielded." assert sections[1].value[1].description == "Second yielded." assert sections[2].value[0].description == "First received." assert sections[2].value[1].description == "Second received." assert sections[3].value[0].description == "First returned." assert sections[3].value[1].description == "Second returned." def test_return_doc() -> None: """Read documentation for return value.""" with temporary_visited_package( "package", modules={"__init__.py": f"{typing_imports}\ndef f() -> Annotated[int, Doc('Hello.')]: ..."}, extensions=Extensions(TypingDocExtension()), ) as package: assert package["f"].docstring.parsed[1].value[0].description == "Hello." def test_unpacking_typed_dict() -> None: """Unpack typed dicts, resolving them to their right location.""" with temporary_visited_package( "package", { "__init__.py": """ from typing import TypedDict from typing_extensions import Annotated, Doc, Unpack from package import module class Options(TypedDict): foo: Annotated[int, Doc("Foo's description.")] class A: def __init__(self, **kwargs: Unpack[Options]) -> None: '''Init.''' self.options = kwargs class B: def __init__(self, **kwargs: Unpack[module.Options]) -> None: '''Init.''' self.options = kwargs """, "module.py": """ from typing import TypedDict from typing_extensions import Annotated, Doc class Options(TypedDict): bar: Annotated[str, Doc("Bar's description.")] """, }, extensions=Extensions(TypingDocExtension()), ) as package: sections = package["A.__init__"].docstring.parsed assert len(sections) == 2 assert sections[0].kind is DocstringSectionKind.text assert sections[1].kind is DocstringSectionKind.other_parameters foo = sections[1].value[0] assert foo.name == "foo" assert foo.description == "Foo's description." assert str(foo.annotation).startswith("Annotated[int") sections = package["B.__init__"].docstring.parsed assert len(sections) == 2 assert sections[0].kind is DocstringSectionKind.text assert sections[1].kind is DocstringSectionKind.other_parameters bar = sections[1].value[0] assert bar.name == "bar" assert bar.description == "Bar's description." assert str(bar.annotation).startswith("Annotated[str") @pytest.mark.parametrize( "annotation", ["int", "Annotated[int, '']"], ) def test_ignore_unannotated_params(annotation: str) -> None: """Ignore parameters that are not annotated with `Doc`.""" with temporary_visited_package( "package", { "__init__.py": f"{typing_imports}\ndef f(a: {annotation}):\n '''Docstring.'''", }, extensions=Extensions(TypingDocExtension()), ) as package: sections = package["f"].docstring.parsed assert len(sections) == 1 assert sections[0].kind is DocstringSectionKind.text @pytest.mark.parametrize( "annotation", ["int", "Annotated[int, '']"], ) def test_ignore_unannotated_other_params(annotation: str) -> None: """Ignore other parameters that are not annotated with `Doc`.""" with temporary_visited_package( "package", { "__init__.py": f""" {typing_imports} from typing import TypedDict class Kwargs(TypedDict): a: {annotation} def f(**kwargs: Unpack[Kwargs]): '''Docstring.''' """, }, extensions=Extensions(TypingDocExtension()), ) as package: sections = package["f"].docstring.parsed assert len(sections) == 1 assert sections[0].kind is DocstringSectionKind.text @pytest.mark.parametrize( "annotation", ["int", "Annotated[int, '']"], ) def test_ignore_unannotated_returns(annotation: str) -> None: """Ignore return values that are not annotated with `Doc`.""" with temporary_visited_package( "package", { "__init__.py": f"{typing_imports}\ndef f() -> {annotation}:\n '''Docstring.'''", }, extensions=Extensions(TypingDocExtension()), ) as package: sections = package["f"].docstring.parsed assert len(sections) == 1 assert sections[0].kind is DocstringSectionKind.text @pytest.mark.parametrize( "annotation", ["int", "Annotated[int, '']"], ) def test_ignore_unannotated_yields(annotation: str) -> None: """Ignore yields that are not annotated with `Doc`.""" with temporary_visited_package( "package", { "__init__.py": f"{typing_imports}\ndef f() -> Iterator[{annotation}]:\n '''Docstring.'''", }, extensions=Extensions(TypingDocExtension()), ) as package: sections = package["f"].docstring.parsed assert len(sections) == 1 assert sections[0].kind is DocstringSectionKind.text @pytest.mark.parametrize( "annotation", ["int", "Annotated[int, '']"], ) def test_ignore_unannotated_receives(annotation: str) -> None: """Ignore receives that are not annotated with `Doc`.""" with temporary_visited_package( "package", { "__init__.py": f"{typing_imports}\ndef f() -> Generator[int, {annotation}, None]:\n '''Docstring.'''", }, extensions=Extensions(TypingDocExtension()), ) as package: sections = package["f"].docstring.parsed assert len(sections) == 1 assert sections[0].kind is DocstringSectionKind.text