diff --git a/.copier-answers.yml b/.copier-answers.yml index f7a640c0..70fc1c32 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: 0.11.2 +_commit: 0.15.2 _src_path: gh:pawamoy/copier-pdm author_email: pawamoy@pm.me author_fullname: Timothée Mazzucotelli @@ -8,13 +8,13 @@ copyright_date: '2021' copyright_holder: Timothée Mazzucotelli copyright_holder_email: pawamoy@pm.me copyright_license: ISC License +insiders: true project_description: A Python handler for mkdocstrings. project_name: mkdocstrings-python -python_package_command_line_name: mkdocstrings-python +python_package_command_line_name: '' python_package_distribution_name: mkdocstrings-python python_package_import_name: mkdocstrings_handlers repository_name: python repository_namespace: mkdocstrings repository_provider: github.com -use_precommit: false diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index cf5764f4..01e293ac 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,4 +1,4 @@ -github: -- pawamoy +github: pawamoy +ko_fi: pawamoy custom: - https://www.paypal.me/pawamoy diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f7fd9bb8..00d61acb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,6 +48,9 @@ jobs: - name: Check for vulnerabilities in dependencies run: pdm run duty check-dependencies + - name: Check for breaking changes in the API + run: pdm run duty check-api + tests: strategy: diff --git a/.github/workflows/dists.yml b/.github/workflows/dists.yml new file mode 100644 index 00000000..13733a78 --- /dev/null +++ b/.github/workflows/dists.yml @@ -0,0 +1,30 @@ +name: dists + +on: push +permissions: + contents: write + +jobs: + build: + name: Build dists + runs-on: ubuntu-latest + if: github.repository_owner == 'pawamoy-insiders' + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Python + uses: actions/setup-python@v3 + - name: Install build + run: python -m pip install build + - name: Build dists + run: python -m build + - name: Upload dists artifact + uses: actions/upload-artifact@v3 + with: + name: python-insiders + path: ./dist/* + - name: Create release and upload assets + uses: softprops/action-gh-release@v1 + if: startsWith(github.ref, 'refs/tags/') + with: + files: ./dist/* diff --git a/.gitignore b/.gitignore index a93f1c73..428b2409 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ pip-wheel-metadata/ .mypy_cache/ site/ pdm.lock -.pdm.toml +pdm.toml +.pdm-python __pypackages__/ .venv/ +.cache/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f6dd2bb..f6aade5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,24 @@ 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.10.0](https://github.com/mkdocstrings/python/releases/tag/0.10.0) - 2023-05-07 + +[Compare with 0.9.0](https://github.com/mkdocstrings/python/compare/0.9.0...0.10.0) + +### Features + +- Add option to disallow inspection ([40f2f26](https://github.com/mkdocstrings/python/commit/40f2f268876358941cf8221d01d219a0deb9de38) by Nyuan Zhang). [Issue #68](https://github.com/mkdocstrings/python/issues/68), [PR #69](https://github.com/mkdocstrings/python/pull/69) + +### Bug Fixes + +- Make admonitions open by default ([79cd153](https://github.com/mkdocstrings/python/commit/79cd153cfceec860f6ce08d30817c21031983238) by Timothée Mazzucotelli). [Issue #22](https://github.com/mkdocstrings/python/issues/22) + +### Code Refactoring + +- Match documented behavior for filtering (all members, list, none) ([c7f70c3](https://github.com/mkdocstrings/python/commit/c7f70c353c3dd2b82e1f34c70cd433e0bab4f6e6) by Timothée Mazzucotelli). +- Switch to an info level log for when black's not installed ([f593bb0](https://github.com/mkdocstrings/python/commit/f593bb06c63860be14d2025c4bd795e0c8976ce0) by Faster Speeding). +- Return anchors as a set ([e2b820c](https://github.com/mkdocstrings/python/commit/e2b820c5af3787518656d5f7f799ecb6b55aa033) by Timothée Mazzucotelli). + ## [0.9.0](https://github.com/mkdocstrings/python/releases/tag/0.9.0) - 2023-04-03 [Compare with 0.8.3](https://github.com/mkdocstrings/python/compare/0.8.3...0.9.0) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 488292a7..ec1ca91f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,8 +32,6 @@ make setup You now have the dependencies installed. -You can run the application with `pdm run mkdocstrings-python [ARGS...]`. - Run `make help` to see all the available actions! ## Tasks @@ -63,7 +61,7 @@ As usual: 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-serve` + 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) diff --git a/Makefile b/Makefile index b034ffff..68fea02f 100644 --- a/Makefile +++ b/Makefile @@ -5,19 +5,18 @@ DUTY = $(shell [ -n "${VIRTUAL_ENV}" ] || echo pdm run) duty args = $(foreach a,$($(subst -,_,$1)_args),$(if $(value $a),$a="$($a)")) check_quality_args = files -docs_serve_args = host port +docs_args = host port release_args = version test_args = match BASIC_DUTIES = \ changelog \ + check-api \ check-dependencies \ clean \ coverage \ docs \ docs-deploy \ - docs-regen \ - docs-serve \ format \ release @@ -42,7 +41,7 @@ setup: .PHONY: check check: @pdm multirun duty check-quality check-types check-docs - @$(DUTY) check-dependencies + @$(DUTY) check-dependencies check-api .PHONY: $(BASIC_DUTIES) $(BASIC_DUTIES): diff --git a/README.md b/README.md index b59516ef..7535de03 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,13 @@

+The Python handler uses [Griffe](https://mkdocstrings.github.io/griffe) +to collect documentation from Python source code. +The word "griffe" can sometimes be used instead of "signature" in French. +Griffe is able to visit the Abstract Syntax Tree (AST) of the source code to extract useful information. +It is also able to execute the code (by importing it) and introspect objects in memory +when source code is not available. Finally, it can parse docstrings following different styles. + ## Installation You can install this handler as a *mkdocstrings* extra: diff --git a/config/ruff.toml b/config/ruff.toml index 55bab1a8..56ad1c02 100644 --- a/config/ruff.toml +++ b/config/ruff.toml @@ -63,6 +63,7 @@ ignore = [ "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 diff --git a/docs/.glossary.md b/docs/.glossary.md new file mode 100644 index 00000000..2a273d7a --- /dev/null +++ b/docs/.glossary.md @@ -0,0 +1,11 @@ +[__all__]: https://docs.python.org/3/tutorial/modules.html#importing-from-a-package +[class template]: https://github.com/mkdocstrings/python/blob/master/src/mkdocstrings_handlers/python/templates/material/_base/class.html +[function template]: https://github.com/mkdocstrings/python/blob/master/src/mkdocstrings_handlers/python/templates/material/_base/function.html +[autodoc syntax]: https://mkdocstrings.github.io/usage/#autodoc-syntax +[autopages recipe]: https://mkdocstrings.github.io/recipes/#automatic-code-reference-pages +[Griffe]: https://github.com/mkdocstrings/griffe +[ReadTheDocs Sphinx theme]: https://sphinx-rtd-theme.readthedocs.io/en/stable/index.html +[Spacy's documentation]: https://spacy.io/api/doc/ +[Black]: https://pypi.org/project/black/ + +*[ToC]: Table of Contents diff --git a/docs/.overrides/main.html b/docs/.overrides/main.html new file mode 100644 index 00000000..cb5234e5 --- /dev/null +++ b/docs/.overrides/main.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} + +{% block announce %} + + Sponsorship + is now available! + + {% include ".icons/octicons/heart-fill-16.svg" %} + + + — For updates follow @pawamoy on + + + {% include ".icons/fontawesome/brands/mastodon.svg" %} + + Fosstodon + +{% endblock %} diff --git a/docs/css/insiders.css b/docs/css/insiders.css new file mode 100644 index 00000000..81dbd756 --- /dev/null +++ b/docs/css/insiders.css @@ -0,0 +1,98 @@ +@keyframes heart { + + 0%, + 40%, + 80%, + 100% { + transform: scale(1); + } + + 20%, + 60% { + transform: scale(1.15); + } +} + +@keyframes vibrate { + 0%, 2%, 4%, 6%, 8%, 10%, 12%, 14%, 16%, 18% { + -webkit-transform: translate3d(-2px, 0, 0); + transform: translate3d(-2px, 0, 0); + } + 1%, 3%, 5%, 7%, 9%, 11%, 13%, 15%, 17%, 19% { + -webkit-transform: translate3d(2px, 0, 0); + transform: translate3d(2px, 0, 0); + } + 20%, 100% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.heart { + color: #e91e63; +} + +.pulse { + animation: heart 1000ms infinite; +} + +.vibrate { + animation: vibrate 2000ms infinite; +} + +.new-feature svg { + fill: var(--md-accent-fg-color) !important; +} + +a.insiders { + color: #e91e63; +} + +.sponsorship-list { + width: 100%; +} + +.sponsorship-item { + float: left; + border-radius: 100%; + display: block; + height: 1.6rem; + margin: .2rem; + overflow: hidden; + width: 1.6rem; +} + +.sponsorship-item:focus, .sponsorship-item:hover { + transform: scale(1.1); +} + +.sponsorship-item img { + filter: grayscale(100%) opacity(75%); + height: auto; + width: 100%; +} + +.sponsorship-item:focus img, .sponsorship-item:hover img { + filter: grayscale(0); +} + +.sponsorship-item.private { + background: var(--md-default-fg-color--lightest); + color: var(--md-default-fg-color); + font-size: .6rem; + font-weight: 700; + line-height: 1.6rem; + text-align: center; +} + +.mastodon { + color: #897ff8; + border-radius: 100%; + box-shadow: inset 0 0 0 .05rem currentcolor; + display: inline-block; + height: 1.2rem !important; + padding: .25rem; + transition: all .25s; + vertical-align: bottom !important; + width: 1.2rem; +} \ No newline at end of file diff --git a/docs/css/material.css b/docs/css/material.css index 9e8c14a6..98a7bed6 100644 --- a/docs/css/material.css +++ b/docs/css/material.css @@ -2,3 +2,30 @@ .md-main__inner { margin-bottom: 1.5rem; } + +/* Custom admonition: preview */ +:root { + --md-admonition-icon--preview: url('data:image/svg+xml;charset=utf-8,') +} + +.md-typeset .admonition.preview, +.md-typeset details.preview { + border-color: rgb(220, 139, 240); +} + +.md-typeset .preview>.admonition-title, +.md-typeset .preview>summary { + background-color: rgba(142, 43, 155, 0.1); +} + +.md-typeset .preview>.admonition-title::before, +.md-typeset .preview>summary::before { + background-color: rgb(220, 139, 240); + -webkit-mask-image: var(--md-admonition-icon--preview); + mask-image: var(--md-admonition-icon--preview); +} + +/* Avoid breaking parameters name, etc. in documentation table cells. */ +td code { + word-break: normal !important; +} \ No newline at end of file diff --git a/docs/css/mkdocstrings.css b/docs/css/mkdocstrings.css index e9e796dd..87842c0c 100644 --- a/docs/css/mkdocstrings.css +++ b/docs/css/mkdocstrings.css @@ -5,6 +5,7 @@ div.doc-contents:not(.first) { } /* Mark external links as such */ +a.external::after, a.autorefs-external::after { /* https://primer.style/octicons/arrow-up-right-24 */ background-image: url('data:image/svg+xml,'); @@ -21,6 +22,8 @@ a.autorefs-external::after { border-radius: 100%; background-color: var(--md-typeset-a-color); } + +a.external:hover::after, a.autorefs-external:hover::after { background-color: var(--md-accent-fg-color); -} +} \ No newline at end of file diff --git a/docs/insiders/changelog.md b/docs/insiders/changelog.md new file mode 100644 index 00000000..eead3a6a --- /dev/null +++ b/docs/insiders/changelog.md @@ -0,0 +1,3 @@ +# Changelog + +## mkdocstrings-python Insiders diff --git a/docs/insiders/goals.yml b/docs/insiders/goals.yml new file mode 100644 index 00000000..896b9240 --- /dev/null +++ b/docs/insiders/goals.yml @@ -0,0 +1 @@ +goals: {} diff --git a/docs/insiders/index.md b/docs/insiders/index.md new file mode 100644 index 00000000..a8773879 --- /dev/null +++ b/docs/insiders/index.md @@ -0,0 +1,230 @@ +# Insiders + +*mkdocstrings-python* follows the **sponsorware** release strategy, which means +that new features are first exclusively released to sponsors as part of +[Insiders][insiders]. Read on to learn [what sponsorships achieve][sponsorship], +[how to become a sponsor][sponsors] to get access to Insiders, +and [what's in it for you][features]! + +## What is Insiders? + +*mkdocstrings-python Insiders* is a private fork of *mkdocstrings-python*, hosted as +a private GitHub repository. Almost[^1] [all new features][features] +are developed as part of this fork, which means that they are immediately +available to all eligible sponsors, as they are made collaborators of this +repository. + + [^1]: + In general, every new feature is first exclusively released to sponsors, but + sometimes upstream dependencies enhance + existing features that must be supported by *mkdocstrings-python*. + +Every feature is tied to a [funding goal][funding] in monthly subscriptions. When a +funding goal is hit, the features that are tied to it are merged back into +*mkdocstrings-python* and released for general availability, making them available +to all users. Bugfixes are always released in tandem. + +Sponsorships start as low as [**$10 a month**][sponsors].[^2] + + [^2]: + Note that $10 a month is the minimum amount to become eligible for + Insiders. While GitHub Sponsors also allows to sponsor lower amounts or + one-time amounts, those can't be granted access to Insiders due to + technical reasons. Such contributions are still very much welcome as + they help ensuring the project's sustainability. + + +## What sponsorships achieve + +Sponsorships make this project sustainable, as they buy the maintainers of this +project time – a very scarce resource – which is spent on the development of new +features, bug fixing, stability improvement, issue triage and general support. +The biggest bottleneck in Open Source is time.[^3] + + [^3]: + Making an Open Source project sustainable is exceptionally hard: maintainers + burn out, projects are abandoned. That's not great and very unpredictable. + The sponsorware model ensures that if you decide to use *mkdocstrings-python*, + you can be sure that bugs are fixed quickly and new features are added + regularly. + + + +## What's in it for me? + +```python exec="1" session="insiders" +data_source = "docs/insiders/goals.yml" +``` + +```python exec="1" session="insiders" +--8<-- "scripts/insiders.py" +``` + + + +We currently don't have any features available to sponsors only. +Right now we are putting our efforts into the documentation, +then we will start again implementing features. +You can get updates on *mkdocstrings-python Insiders* work +by following **@pawamoy** on :material-mastodon:{ .mastodon } [Fosstodon](https://fosstodon.org/@pawamoy). + +## How to become a sponsor + +Thanks for your interest in sponsoring! In order to become an eligible sponsor +with your GitHub account, visit [pawamoy's sponsor profile][github sponsor profile], +and complete a sponsorship of **$10 a month or more**. +You can use your individual or organization GitHub account for sponsoring. + +**Important**: If you're sponsoring **[@pawamoy][github sponsor profile]** +through a GitHub organization, please send a short email +to pawamoy@pm.me with the name of your +organization and the GitHub account of the individual +that should be added as a collaborator.[^4] + +You can cancel your sponsorship anytime.[^5] + + [^4]: + It's currently not possible to grant access to each member of an + organization, as GitHub only allows for adding users. Thus, after + sponsoring, please send an email to pawamoy@pm.me, stating which + account should become a collaborator of the Insiders repository. We're + working on a solution which will make access to organizations much simpler. + To ensure that access is not tied to a particular individual GitHub account, + create a bot account (i.e. a GitHub account that is not tied to a specific + individual), and use this account for the sponsoring. After being added to + the list of collaborators, the bot account can create a private fork of the + private Insiders GitHub repository, and grant access to all members of the + organizations. + + [^5]: + If you cancel your sponsorship, GitHub schedules a cancellation request + which will become effective at the end of the billing cycle. This means + that even though you cancel your sponsorship, you will keep your access to + Insiders as long as your cancellation isn't effective. All charges are + processed by GitHub through Stripe. As we don't receive any information + regarding your payment, and GitHub doesn't offer refunds, sponsorships are + non-refundable. + +```python exec="1" session="insiders" +print_join_sponsors_button() +``` + +
+ +```python exec="1" session="insiders" +print_sponsors() +``` + +

+ + + If you sponsor publicly, you're automatically added here with a link to + your profile and avatar to show your support for *mkdocstrings-python*. + Alternatively, if you wish to keep your sponsorship private, you'll be a + silent +1. You can select visibility during checkout and change it + afterwards. + + +## Funding + +```python exec="1" session="insiders" +print(f""" +Current funding is at **$ {human_readable_amount(current_funding)} a month**. +We do not have any funding goals yet. +Stay updated by following **@pawamoy** +on :material-mastodon:{{ .mastodon }} [Fosstodon](https://fosstodon.org/@pawamoy). +""") +``` + + + +## Frequently asked questions + +### Compatibility + +> We're building an open source project and want to allow outside collaborators +to use *mkdocstrings-python* locally without having access to Insiders. +Is this still possible? + +Yes. Insiders is compatible with *mkdocstrings-python*. Almost all new features +and configuration options are either backward-compatible or implemented behind +feature flags. Most Insiders features enhance the overall experience, +though while these features add value for the users of your project, they +shouldn't be necessary for previewing when making changes to content. + +### Payment + +> We don't want to pay for sponsorship every month. Are there any other options? + +Yes. You can sponsor on a yearly basis by [switching your GitHub account to a +yearly billing cycle][billing cycle]. If for some reason you cannot do that, you +could also create a dedicated GitHub account with a yearly billing cycle, which +you only use for sponsoring (some sponsors already do that). + +If you have any problems or further questions, please reach out to pawamoy@pm.me. + +### Terms + +> Are we allowed to use Insiders under the same terms and conditions as +*mkdocstrings-python*? + +Yes. Whether you're an individual or a company, you may use *mkdocstrings-python +Insiders* precisely under the same terms as *mkdocstrings-python*, which are given +by the [ISC License][license]. However, we kindly ask you to respect our +**fair use policy**: + +- Please **don't distribute the source code** of Insiders. You may freely use + it for public, private or commercial projects, privately fork or mirror it, + but please don't make the source code public, as it would counteract the + sponsorware strategy. + +- If you cancel your subscription, you're automatically removed as a + collaborator and will miss out on all future updates of Insiders. However, you + may **use the latest version** that's available to you **as long as you like**. + Just remember that [GitHub deletes private forks][private forks]. + +[insiders]: #what-is-insiders +[sponsorship]: #what-sponsorships-achieve +[sponsors]: #how-to-become-a-sponsor +[features]: #whats-in-it-for-me +[funding]: #funding +[goals completed]: #goals-completed +[github sponsor profile]: https://github.com/sponsors/pawamoy +[billing cycle]: https://docs.github.com/en/github/setting-up-and-managing-billing-and-payments-on-github/changing-the-duration-of-your-billing-cycle +[license]: ../license/ +[private forks]: https://docs.github.com/en/github/setting-up-and-managing-your-github-user-account/removing-a-collaborator-from-a-personal-repository diff --git a/docs/insiders/installation.md b/docs/insiders/installation.md new file mode 100644 index 00000000..88ebd021 --- /dev/null +++ b/docs/insiders/installation.md @@ -0,0 +1,190 @@ +--- +title: Getting started with Insiders +--- + +# Getting started with Insiders + +*mkdocstrings-python Insiders* is a compatible drop-in replacement for *mkdocstrings-python*, +and can be installed similarly using `pip` or `git`. +Note that in order to access the Insiders repository, +you need to [become an eligible sponsor] of @pawamoy on GitHub. + + [become an eligible sponsor]: index.md#how-to-become-a-sponsor + +## Installation + +### with pip (ssh/https) + +*mkdocstrings-python Insiders* can be installed with `pip` [using SSH][using ssh]: + +```bash +pip install git+ssh://git@github.com/pawamoy-insiders/python.git +``` + + [using ssh]: https://docs.github.com/en/authentication/connecting-to-github-with-ssh + +Or using HTTPS: + +```bash +pip install git+https://${GH_TOKEN}@github.com/pawamoy-insiders/python.git +``` + +>? NOTE: **How to get a GitHub personal access token** +> The `GH_TOKEN` environment variable is a GitHub token. +> It can be obtained by creating a [personal access token] for +> your GitHub account. It will give you access to the Insiders repository, +> programmatically, from the command line or GitHub Actions workflows: +> +> 1. Go to https://github.com/settings/tokens +> 2. Click on [Generate a new token] +> 3. Enter a name and select the [`repo`][scopes] scope +> 4. Generate the token and store it in a safe place +> +> [personal access token]: https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token +> [Generate a new token]: https://github.com/settings/tokens/new +> [scopes]: https://docs.github.com/en/developers/apps/scopes-for-oauth-apps#available-scopes +> +> Note that the personal access +> token must be kept secret at all times, as it allows the owner to access your +> private repositories. + +### with pip (self-hosted) + +Self-hosting the Insiders package makes it possible to depend on *mkdocstrings-python* normally, +while transparently downloading and installing the Insiders version locally. +It means that you can specify your dependencies normally, and your contributors without access +to Insiders will get the public version, while you get the Insiders version on your machine. + +WARNING: **Limitation** +With this method, there is no way to force the installation of an Insiders version +rather than a public version. If there is a public version that is more recent +than your self-hosted Insiders version, the public version will take precedence. +Remember to regularly update your self-hosted versions by uploading latest distributions. + +You can build the distributions for Insiders yourself, by cloning the repository +and using [build] to build the distributions, +or you can download them from our [GitHub Releases]. +You can upload these distributions to a private PyPI-like registry +([Artifactory], [Google Cloud], [pypiserver], etc.) +with [Twine]: + + [build]: https://pypi.org/project/build/ + [Artifactory]: https://jfrog.com/help/r/jfrog-artifactory-documentation/pypi-repositories + [Google Cloud]: https://cloud.google.com/artifact-registry/docs/python + [pypiserver]: https://pypi.org/project/pypiserver/ + [Github Releases]: https://github.com/pawamoy-insiders/python/releases + [Twine]: https://pypi.org/project/twine/ + +```bash +# download distributions in ~/dists, then upload with: +twine upload --repository-url https://your-private-index.com ~/dists/* +``` + +You might also need to provide a username and password/token to authenticate against the registry. +Please check [Twine's documentation][twine docs]. + + [twine docs]: https://twine.readthedocs.io/en/stable/ + +You can then configure pip (or other tools) to look for packages into your package index. +For example, with pip: + +```bash +pip config set global.extra-index-url https://your-private-index.com/simple +``` + +Note that the URL might differ depending on whether your are uploading a package (with Twine) +or installing a package (with pip), and depending on the registry you are using (Artifactory, Google Cloud, etc.). +Please check the documentation of your registry to learn how to configure your environment. + +**We kindly ask that you do not upload the distributions to public registries, +as it is against our [Terms of use](../#terms).** + +>? TIP: **Full example with `pypiserver`** +> In this example we use [pypiserver] to serve a local PyPI index. +> +> ```bash +> pip install --user pypiserver +> # or pipx install pypiserver +> +> # create a packages directory +> mkdir -p ~/.local/pypiserver/packages +> +> # run the pypi server without authentication +> pypi-server run -p 8080 -a . -P . ~/.local/pypiserver/packages & +> ``` +> +> We can configure the credentials to access the server in [`~/.pypirc`][pypirc]: +> +> [pypirc]: https://packaging.python.org/en/latest/specifications/pypirc/ +> +> ```ini title=".pypirc" +> [distutils] +> index-servers = +> local +> +> [local] +> repository: http://localhost:8080 +> username: +> password: +> ``` +> +> We then clone the Insiders repository, build distributions and upload them to our local server: +> +> ```bash +> # clone the repository +> git clone git@github.com:pawamoy-insiders/python +> cd python +> +> # install build +> pip install --user build +> # or pipx install build +> +> # checkout latest tag +> git checkout $(git describe --tags --abbrev=0) +> +> # build the distributions +> pyproject-build +> +> # upload them to our local server +> twine upload -r local dist/* --skip-existing +> ``` +> +> Finally, we configure pip, and for example [PDM][pdm], to use our local index to find packages: +> +> ```bash +> pip config set global.extra-index-url http://localhost:8080/simple +> pdm config pypi.extra.url http://localhost:8080/simple +> ``` +> +> [pdm]: https://pdm.fming.dev/latest/ +> +> Now when running `pip install mkdocstrings-python`, +> or resolving dependencies with PDM, +> both tools will look into our local index and find the Insiders version. +> **Remember to update your local index regularly!** + +### with git + +Of course, you can use *mkdocstrings-python Insiders* directly from `git`: + +``` +git clone git@github.com:pawamoy-insiders/python +``` + +When cloning from `git`, the package must be installed: + +``` +pip install -e python +``` + +## Upgrading + +When upgrading Insiders, you should always check the version of *mkdocstrings-python* +which makes up the first part of the version qualifier. For example, a version like +`8.x.x.4.x.x` means that Insiders `4.x.x` is currently based on `8.x.x`. + +If the major version increased, it's a good idea to consult the [changelog] +and go through the steps to ensure your configuration is up to date and +all necessary changes have been made. + + [changelog]: ./changelog.md diff --git a/docs/usage/configuration/docstrings.md b/docs/usage/configuration/docstrings.md new file mode 100644 index 00000000..97446d97 --- /dev/null +++ b/docs/usage/configuration/docstrings.md @@ -0,0 +1,937 @@ +# Docstrings options + +## `docstring_style` + +- **:octicons-package-24: Type [`str`][] :material-equal: `"google"`{ title="default value" }** + + +The docstring style to expect when parsing docstrings. + +Possible values: + +- `"google"`: see [Google style](../docstrings/google.md). +- `"numpy"`: see [Numpy style](../docstrings/numpy.md). +- `"sphinx"`: see [Sphinx style](../docstrings/sphinx.md). +- `None` (`null` or `~` in YAML): no style at all, parse as regular text. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + docstring_style: google +``` + +```md title="or in docs/some_page.md (local configuration)" +::: path.to.module + options: + docstring_style: numpy +``` + +/// admonition | Preview + type: preview + +Every style gets rendered the same way. +Here are some docstring examples. + +//// tab | Google +```python +def greet(name: str) -> str: + """Greet someone. + + Parameters: + name: The name of the person to greet. + + Returns: + A greeting message. + """ + return f"Hello {name}!" +``` +//// + +//// tab | Numpy +```python +def greet(name: str) -> str: + """Greet someone. + + Parameters + ---------- + name + The name of the person to greet. + + Returns + ------- + A greeting message. + """ + return f"Hello {name}!" +``` +//// + +//// tab | Sphinx +```python +def greet(name: str) -> str: + """Greet someone. + + :param name: The name of the person to greet. + :return: A greeting message. + """ + return f"Hello {name}!" +``` +//// +/// + +## `docstring_options` + +- **:octicons-package-24: Type [`dict`][] :material-equal: `{}`{ title="default value" }** + + +The options for the docstring parser. + +Both Google and Numpy styles offer the following options: + +- `ignore_init_summary` ([`bool`][], default `False`): whether to discard + the one-line summary of `__init__` methods. + It is useful when combined with the [`merge_init_into_class`][] option. +- `trim_doctest_flags` ([`bool`][], default `True`): remove the + [doctest flags](https://docs.python.org/3/library/doctest.html#option-flags){ .external } + written as comments in `pycon` snippets within a docstring. These flags are used + to alter the behavior of [`doctest`][] when testing docstrings, + and should not be visible in your docs. + +The Sphinx style does not offer any option. + +See the API documentation of the available parsers in [`griffe.docstrings`][]. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + docstring_options: + ignore_init_summary: false + trim_doctest_flags: true + +``` + +```md title="or in docs/some_page.md (local configuration)" +::: path.to.module + options: + docstring_options: + ignore_init_summary: true + trim_doctest_flags: false +``` + +```python +class PrintOK: + """Class docstring.""" + + def __init__(self): + """Initialize the instance. + + Examples: + >>> Class() # doctest: +NORMALIZE_WHITESPACE + ok + """ + print("ok") +``` + +/// admonition | Preview + type: preview + +//// tab | Ignore init summary, trim doctest flags +

PrintOK

+

Class docstring.

+

__init__

+

Examples:

+ +```pycon +>>> Class() +ok +``` +//// + +//// tab | Keep init summary and doctest flags +

PrintOK

+

Class docstring.

+

__init__

+

Initialize the instance.

+

Examples:

+ +```pycon +>>> Class() # doctest: +NORMALIZE_WHITESPACE +ok +``` +//// +/// + +## `docstring_section_style` + +- **:octicons-package-24: Type [`str`][] :material-equal: `"table"`{ title="default value" }** + + +The style used to render docstring sections. + +A section is a block of text that has a special meaning in a docstring. +There are sections for documenting attributes of an object, +parameters of a function, exceptions raised by a function, +the return value of a function, etc. + +Sections are parsed as structured data and can therefore be rendered +in different ways. Possible values: + +- `"table"`: a simple table, usually with type, name and description columns +- `"list"`: a simple list, akin to what you get with the [ReadTheDocs Sphinx theme]{ .external } +- `"spacy"`: a poor implementation of the amazing tables in [Spacy's documentation]{ .external } + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + docstring_section_style: table +``` + +```md title="or in docs/some_page.md (local configuration)" +::: path.to.module + options: + docstring_section_style: list +``` + +/// admonition | Preview + type: preview + +//// tab | Table +Tables work well when you have lots of items with short names, type annotations, descriptions, etc.. +With longer strings, the columns risk getting squished horizontally. +In that case, the Spacy tables can help. + +**Parameters:** + +**Type** | **Name** | **Description** | **Default** +---------- | ----------- | ------------------------ | ----------- +[`int`][] | `threshold` | Threshold for something. | *required* +[`bool`][] | `flag` | Enable something. | `False` + +**Other Parameters:** + +**Type** | **Name** | **Description** | **Default** +---------- | ----------- | ------------------------ | ----------- +list[int \| float] | `gravity_forces` | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. | *required* +VacuumType \| Literal["regular"] | `vacuum_type` | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. | `VacuumType.PLASMA` +//// + +//// tab | List +Lists work well whatever the length of names, type annotations, descriptions, etc. + +**Parameters:** + +- `threshold` ([`int`][]) — Threshold for something. +- `flag` ([`bool`][]) — Enable something. + +**Other Parameters:** + +- `gravity_forces` (list[int \| float]) — Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +- `vacuum_type` (VacuumType \| Literal["regular"]) — Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +//// + +//// tab | Spacy +Spacy tables work better than regular tables with longer names, type annotations, descriptions, etc., +by reserving more horizontal space on the second column. + +**Parameters:** + +**Name** | **Description** +----------- | --------------- +`threshold` | Threshold for something.
**TYPE:** [`int`][] DEFAULT: required +`flag` | Enable something.
**TYPE:** [`bool`][] DEFAULT: False + +**Other Parameters:** + +**Name** | **Description** +----------- | --------------- +`gravity_forces` | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
**TYPE:** list[int \| float] DEFAULT: required +`vacuum_type` | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
**TYPE:**VacuumType \| Literal["regular"] DEFAULT: VacuumType.PLASMA +//// +/// + +## `merge_init_into_class` + +- **:octicons-package-24: Type [`bool`][] :material-equal: `False`{ title="default value" }** + + +Whether to merge the `__init__` method into the class' signature and docstring. + +By default, only the class name is rendered in headings. +When merging, the `__init__` method parameters are added after the class name, +like a signature, and the `__init__` method docstring is appended to the class' docstring. +This option is well used in combination with the `ignore_init_summary` [docstring option][docstring_options], +to discard the first line of the `__init__` docstring which is not often useful. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + docstring_options: + ignore_init_summary: false + merge_init_into_class: false +``` + +```md title="or in docs/some_page.md (local configuration)" +::: path.to.module + options: + docstring_options: + ignore_init_summary: true + merge_init_into_class: true +``` + +```python +class Thing: + """A class for things.""" + + def __init__(self, value: int = 0): + """Initialize a thing. + + Parameters: + value: The thing's value. + """ + self.value = value +``` + +/// admonition | Preview + type: preview + +//// tab | Merged, summary discarded +

Thing(value=0)

+

Class docstring.

+

Parameters:

+ +**Type** | **Name** | **Description** | **Default** +--------- | -------- | ------------------ | ----------- +[`int`][] | `value` | The thing's value. | `0` +//// + +//// tab | Unmerged, summary kept +

Thing

+

Class docstring.

+

__init__(value=0)

+

Initialize a thing.

+

Parameters:

+ +**Type** | **Name** | **Description** | **Default** +--------- | -------- | ------------------ | ----------- +[`int`][] | `value` | The thing's value. | `0` +//// +/// + +## `show_if_no_docstring` + +- **:octicons-package-24: Type [`bool`][] :material-equal: `False`{ title="default value" }** + + +Show the object heading even if it has no docstring or children with docstrings. + +Without an explicit list of [`members`][], members are selected based on [`filters`][], +and then filtered again to keep only those with docstrings. Checking if a member has a docstring +is done recursively: if at least one of its direct or indirect members (lower in the tree) +has a docstring, the member is rendered. If the member does not have a docstring, +and none of its members have a docstring, it is excluded. + +With this option you can tell the Python handler to skip the docstring check. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + show_if_no_docstring: false +``` + +```md title="or in docs/some_page.md (local configuration)" +::: path.to.module + options: + show_if_no_docstring: true +``` + +```python +def function_without_docstring(): + ... + + +def function_with_docstring(): + """Hello.""" + + +class ClassWithoutDocstring: + def method_without_docstring(self): + ... + + def method_with_docstring(self): + """Hello.""" +``` + +/// admonition | Preview + type: preview + +//// tab | Show +

function_without_docstring

+

function_with_docstring

+

Hello.

+

ClassWithoutDocstring

+

method_without_docstring

+

method_with_docstring

+

Hello.

+//// + +//// tab | Don't show +

function_with_docstring

+

Hello.

+

ClassWithoutDocstring

+

method_with_docstring

+

Hello.

+//// +/// + +## `show_docstring_attributes` + +- **:octicons-package-24: Type [`bool`][] :material-equal: `True`{ title="default value" }** + + +Whether to render the "Attributes" sections of docstrings. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + show_docstring_attributes: true +``` + +```md title="or in docs/some_page.md (local configuration)" +::: path.to.module + options: + show_if_no_docstring: false +``` + +```python +class Class: + """Summary. + + Attributes: + attr: Some attribute. + """ + + attr: int = 1 +``` + +/// admonition | Preview + type: preview + +//// tab | With attributes +

Class

+

Summary.

+

Attributes:

+ +**Type** | **Name** | **Description** +--------- | -------- | --------------- +[`int`][] | `attr` | Some attribute. +//// + +//// tab | Without attributes +

Class

+

Summary.

+//// +/// + +## `show_docstring_description` + +- **:octicons-package-24: Type [`bool`][] :material-equal: `True`{ title="default value" }** + + +Whether to render the textual blocks (including admonitions) of docstrings. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + show_docstring_description: true +``` + +```md title="or in docs/some_page.md (local configuration)" +::: path.to.module + options: + show_docstring_description: false +``` + +```python +class Class: + """Summary. + + Long description. + + Warning: Deprecated + Stop using this class. + + Attributes: + attr: Some attribute. + """ + + attr: int = 1 +``` + +/// admonition | Preview + type: preview + +//// tab | With description blocks +

Class

+

Summary.

+

Long description.

+
Deprecated

Stop using this class.

+

Attributes:

+ +**Type** | **Name** | **Description** +--------- | -------- | --------------- +[`int`][] | `attr` | Some attribute. +//// + +//// tab | Without description blocks +

Class

+

Attributes:

+ +**Type** | **Name** | **Description** +--------- | -------- | --------------- +[`int`][] | `attr` | Some attribute. +//// +/// + +## `show_docstring_examples` + +- **:octicons-package-24: Type [`bool`][] :material-equal: `True`{ title="default value" }** + + +Whether to render the "Examples" section of docstrings. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + show_docstring_examples: true +``` + +```md title="or in docs/some_page.md (local configuration)" +::: path.to.module + options: + show_docstring_examples: false +``` + +```python +def print_hello(): + """Print hello. + + Examples: + >>> print("hello") + hello + """ + print("hello") +``` + +/// admonition | Preview + type: preview + +//// tab | With examples +

print_hello

+

Print hello.

+

Examples:

+ +```pycon +>>> print("hello") +hello +``` +//// + +//// tab | Without examples +

print_hello

+

Print hello.

+//// +/// + +## `show_docstring_other_parameters` + +- **:octicons-package-24: Type [`bool`][] :material-equal: `True`{ title="default value" }** + + +Whether to render the "Other Parameters" section of docstrings. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + show_docstring_other_parameters: true +``` + +```md title="or in docs/some_page.md (local configuration)" +::: path.to.module + options: + show_docstring_other_parameters: false +``` + +```python +def do_something(**kwargs): + """Do something. + + Other parameters: + whatever (int): Some integer. + """ +``` + +/// admonition | Preview + type: preview + +//// tab | With other parameters +

do_something

+

Do something.

+

Other parameters:

+ +**Type** | **Name** | **Description** +--------- | ---------- | --------------- +[`int`][] | `whatever` | Some integer. +//// + +//// tab | Without other parameters +

do_something

+

Do something.

+//// +/// + +## `show_docstring_parameters` + +- **:octicons-package-24: Type [`bool`][] :material-equal: `True`{ title="default value" }** + + +Whether to render the "Parameters" section of docstrings. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + show_docstring_parameters: true +``` + +```md title="or in docs/some_page.md (local configuration)" +::: path.to.module + options: + show_docstring_parameters: false +``` + +```python +def do_something(whatever: int = 0): + """Do something. + + Parameters: + whatever: Some integer. + """ +``` + +/// admonition | Preview + type: preview + +//// tab | With parameters +

do_something

+

Do something.

+

Parameters:

+ +**Type** | **Name** | **Description** | **Default** +--------- | ---------- | --------------- | ----------- +[`int`][] | `whatever` | Some integer. | `0` +//// + +//// tab | Without parameters +

do_something

+

Do something.

+//// +/// + +## `show_docstring_raises` + +- **:octicons-package-24: Type [`bool`][] :material-equal: `True`{ title="default value" }** + + +Whether to render the "Raises" section of docstrings. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + show_docstring_raises: true +``` + +```md title="or in docs/some_page.md (local configuration)" +::: path.to.module + options: + show_docstring_raises: false +``` + +```python +def raise_runtime_error(): + """Raise a runtime error. + + Raises: + RuntimeError: Not good. + """ + raise RuntimeError +``` + +/// admonition | Preview + type: preview + +//// tab | With exceptions +

raise_runtime_error

+

Raise a runtime error.

+

Raises:

+ +**Type** | **Description** +------------------ | --------------- +[`RuntimeError`][] | Not good. +//// + +//// tab | Without exceptions +

raise_runtime_error

+

Raise a runtime error.

+//// +/// + +## `show_docstring_receives` + +- **:octicons-package-24: Type [`bool`][] :material-equal: `True`{ title="default value" }** + + +Whether to render the "Receives" section of docstrings. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + show_docstring_receives: true +``` + +```md title="or in docs/some_page.md (local configuration)" +::: path.to.module + options: + show_docstring_receives: false +``` + +```python +def iter_skip( + iterable: Iterable[T], + initial_skip: int = 0, +) -> Generator[T, int, None]: + """Iterate and skip elements. + + Receives: + skip: Number of elements to skip. + """ + skip = initial_skip + for element in iterable: + if skip or 0 > 0: + skip -= 1 + else: + skip = yield element +``` + +/// admonition | Preview + type: preview + +//// tab | With received values +

iter_skip

+

Iterate and skip elements.

+

Receives:

+ +**Type** | **Description** +--------- | --------------- +[`int`][] | Number of elements to skip. +//// + +//// tab | Without received values +

iter_skip

+

Iterate and skip elements.

+//// +/// + +## `show_docstring_returns` + +- **:octicons-package-24: Type [`bool`][] :material-equal: `True`{ title="default value" }** + + +Whether to render the "Returns" section of docstrings. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + show_docstring_returns: true +``` + +```md title="or in docs/some_page.md (local configuration)" +::: path.to.module + options: + show_docstring_returns: false +``` + +```python +def rand() -> int: + """Return a random number. + + Returns: + A random number. + """ + return random.randint(0, 1000) +``` + +/// admonition | Preview + type: preview + +//// tab | With return value +

rand

+

Return a random number.

+

Returns:

+ +**Type** | **Description** +--------- | --------------- +[`int`][] | A random number. +//// + +//// tab | Without return value +

rand

+

Return a random number.

+//// +/// + +## `show_docstring_warns` + +- **:octicons-package-24: Type [`bool`][] :material-equal: `True`{ title="default value" }** + + +Whether to render the "Warns" section of docstrings. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + show_docstring_warns: true +``` + +```md title="or in docs/some_page.md (local configuration)" +::: path.to.module + options: + show_docstring_warns: false +``` + +```python +def warn(): + """Warn user. + + Warns: + UserWarning: When this is inappropriate. + """ + warnings.warn(UserWarning("This is inappropriate")) +``` + +/// admonition | Preview + type: preview + +//// tab | With warnings +

warn

+

Warn user.

+

Warns:

+ +**Type** | **Description** +----------------- | --------------- +[`UserWarning`][] | When this is inappropriate. +//// + +//// tab | Without warnings +

warn

+

Warn user.

+//// +/// + +## `show_docstring_yields` + +- **:octicons-package-24: Type [`bool`][] :material-equal: `True`{ title="default value" }** + + +Whether to render the "Yields" section of docstrings. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + show_docstring_yields: true +``` + +```md title="or in docs/some_page.md (local configuration)" +::: path.to.module + options: + show_docstring_yields: false +``` + +```python +def iter_skip( + iterable: Iterable[T], + initial_skip: int = 0, +) -> Generator[T, int, None]: + """Iterate and skip elements. + + Yields: + Elements of the iterable. + """ + skip = initial_skip + for element in iterable: + if skip or 0 > 0: + skip -= 1 + else: + skip = yield element +``` + +/// admonition | Preview + type: preview + +//// tab | With yielded values +

iter_skip

+

Iterate and skip elements.

+

Yields:

+ +**Type** | **Description** +--------- | --------------- +`T` | Elements of the iterable. +//// + +//// tab | Without yielded values +

iter_skip

+

Iterate and skip elements.

+//// +/// diff --git a/docs/usage/configuration/general.md b/docs/usage/configuration/general.md new file mode 100644 index 00000000..921f9187 --- /dev/null +++ b/docs/usage/configuration/general.md @@ -0,0 +1,192 @@ +# General options + +## `allow_inspection` + +- **:octicons-package-24: Type [`bool`][] :material-equal: `True`{ title="default value" }** + + +Whether to allow inspecting modules (importing them) +when it is not possible to visit them (parse their source code). + +When loading data for a given package, [Griffe] discovers every Python module, +compiled or not, and inspects or visits them accordingly. + +If you have compiled modules but also provide stubs for them, +you might want to disable the inspection of these modules, +because inspection picks up many more members, +and sometimes the collected data is inaccurate +(depending on the tool that was used to compile the module) +or too low-level/technical for API documentation. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + allow_inspection: true +``` + +```md title="or in docs/some_page.md (local configuration)" +::: path.to.object + options: + allow_inspection: false +``` + +/// admonition | Preview + type: preview + +//// tab | With inspection +

SomeClass

+

Docstring of the class.

+

__eq__

+

Method docstring.

+

__weakref__

+

Method docstring.

+

documented_method

+

Method docstring.

+//// + +//// tab | Without inspection +

SomeClass

+

Docstring of the class.

+

documented_method

+

Method docstring.

+//// +/// + +## `show_bases` + +- **:octicons-package-24: Type [`bool`][] :material-equal: `True`{ title="default value" }** + + +Show the base classes of a class. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + show_bases: true +``` + +```md title="or in docs/some_page.md (local configuration)" +::: path.to.object + options: + show_bases: false +``` + +/// admonition | Preview + type: preview + +//// tab | With bases +

SomeClass()

+

Bases: SomeBaseClass

+

Docstring of the class.

+//// + +//// tab | Without bases +

SomeClass()

+

Docstring of the class.

+//// +/// + +## `show_source` + +- **:octicons-package-24: Type [`bool`][] :material-equal: `True`{ title="default value" }** + + +Show the source code of this object. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + show_source: true +``` + +```md title="or in docs/some_page.md (local configuration)" +::: path.to.object + options: + show_source: false +``` + +/// admonition | Preview + type: preview + +//// tab | With source +

some_function()

+

Docstring of the function.

+ +///// details | Source code in `package/module.py` + type: quote + +```python linenums="1" +def some_function(): ... +``` +///// +//// + +//// tab | Without source +

some_function()

+

Docstring of the function.

+//// +/// + +## `preload_modules` + +- **:octicons-package-24: Type list[str] | None :material-equal: `None`{ title="default value" }** + + +Pre-load modules that are not specified directly in [autodoc instructions][autodoc syntax] (`::: identifier`). +It is useful when you want to render documentation for a particular member of an object, +and this member is imported from another package than its parent. + +For an imported member to be rendered, +you need to add it to the [`__all__`][__all__] attribute of the importing module. +The package from which the imported object originates must be accessible to the handler +(see [Finding modules](../index.md#finding-modules)). + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + preload_modules: + - their_package +``` + +```md title="or in docs/some_page.md (local configuration)" +::: your_package.your_module + options: + preload_modules: + - their_package +``` + +```python title="your_package/your_module.py" +from their_package.their_module import their_object + +__all__ = ["their_object"] + +# rest of your code +``` + +/// admonition | Preview + type: preview + +//// tab | With preloaded modules +

your_module

+

Docstring of your module.

+

their_object

+

Docstring of their object.

+//// + +//// tab | Without preloaded modules +

your_module

+

Docstring of your module.

+//// +/// diff --git a/docs/usage/configuration/headings.md b/docs/usage/configuration/headings.md new file mode 100644 index 00000000..e1c2e63a --- /dev/null +++ b/docs/usage/configuration/headings.md @@ -0,0 +1,389 @@ +# Headings options + +## `heading_level` + +- **:octicons-package-24: Type [`int`][] :material-equal: `2`{ title="default value" }** + + +The initial heading level to use. + +When injecting documentation for an object, +the object itself and its members are rendered. +For each layer of objects, we increase the heading level by 1. + +The initial heading level will be used for the first layer. +If you set it to 3, then headings will start with `

`. + +If the [heading for the root object][show_root_heading] is not shown, +then the initial heading level is used for its members. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + heading_level: 2 +``` + +```md title="or in docs/some_page.md (local configuration)" +::: path.to.module + options: + heading_level: 3 +``` + +/// admonition | Preview + type: preview + +//// tab | With level 3 and root heading +

module (3)

+

Docstring of the module.

+

ClassA (4)

+

Docstring of class A.

+

ClassB (4)

+

Docstring of class B.

+
method_1 (5)
+

Docstring of the method.

+//// + +//// tab | With level 3, without root heading +

Docstring of the module.

+

ClassA (3)

+

Docstring of class A.

+

ClassB (3)

+

Docstring of class B.

+

method_1 (4)

+

Docstring of the method.

+//// +/// + +## `show_root_heading` + +- **:octicons-package-24: Type [`bool`][] :material-equal: `False`{ title="default value" }** + + +Show the heading of the object at the root of the documentation tree +(i.e. the object referenced by the identifier after `:::`). + +It is pretty common to inject documentation for one module per page, +especially when following our [automatic reference pages recipe][autopages recipe]. +Since each page already has a title, usually being the module's name, +we can spare one heading level by not showing the heading for the module itself +(heading levels are limited to 6 by the HTML specification). + +Sparing that extra level can be helpful when your objects tree is deeply nested +(e.g. method in a class in a class in a module). +If your objects tree is not deeply nested, and you are injecting documentation +for many different objects on a single page, it might be preferable to render +the heading of each object. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + show_root_heading: false +``` + +```md title="or in docs/some_page.md (local configuration)" +::: path.to.ClassA + options: + show_root_heading: true + +::: path.to.ClassB + options: + show_root_heading: true +``` + +/// admonition | Preview + type: preview + +//// tab | With root heading +

ClassA (2)

+

Docstring of class A.

+

method_a1 (3)

+

Docstring of the method.

+

ClassB (2)

+

Docstring of class B.

+

method_b1 (3)

+

Docstring of the method.

+//// + +//// tab | Without root heading +

Docstring of class A.

+

method_a1 (2)

+

Docstring of the method.

+

Docstring of class B.

+

method_b1 (2)

+

Docstring of the method.

+//// +/// + +## `show_root_toc_entry` + +- **:octicons-package-24: Type [`bool`][] :material-equal: `True`{ title="default value" }** + + +If the root heading is not shown, at least add a ToC entry for it. + +If you inject documentation for an object in the middle of a page, +after long paragraphs, and without showing the [root heading][show_root_heading], +then you will not be able to link to this particular object +as it won't have a permalink and will be "lost" in the middle of text. +In that case, it is useful to add a hidden anchor to the document, +which will also appear in the table of contents. + +In other cases, you might want to disable the entry to avoid polluting the ToC. +It is not possible to show the root heading *and* hide the ToC entry. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + show_root_toc_entry: true +``` + +```md title="or in docs/some_page.md (local configuration)" +## Some heading + +Lots of text. + +::: path.to.object + options: + show_root_toc_entry: false + +## Other heading. + +More text. +``` + +/// admonition | Preview + type: preview + +//// tab | With ToC entry +**Table of contents** +[Some heading](#permalink-to-some-heading){ title="#permalink-to-some-heading" } +[`object`](#permalink-to-object){ title="#permalink-to-object" } +[Other heading](#permalink-to-other-heading){ title="#permalink-to-other-heading" } +//// + +//// tab | Without ToC entry +**Table of contents** +[Some heading](#permalink-to-some-heading){ title="#permalink-to-some-heading" } +[Other heading](#permalink-to-other-heading){ title="#permalink-to-other-heading" } +//// +/// + +## `show_root_full_path` + +- **:octicons-package-24: Type [`bool`][] :material-equal: `True`{ title="default value" }** + + +Show the full Python path for the root object heading. + +The path of a Python object is the dot-separated list of names +under which it is accessible, for example `package.module.Class.method`. + +With this option you can choose to show the full path of the object +you inject documentation for, or just its name. This option impacts +only the object you specify, not its members. For members, see the two +other options [`show_root_members_full_path`][] +and [`show_object_full_path`][]. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + show_root_full_path: true +``` + +```md title="or in docs/some_page.md (local configuration)" +::: package.module.Class.method + options: + show_root_full_path: false +``` + +/// admonition | Preview + type: preview + +//// tab | With root full path +

package.module.Class.method

+

Docstring of the method.

+//// + +//// tab | Without root full path +

method

+

Docstring of the method.

+//// +/// + +## `show_root_members_full_path` + +- **:octicons-package-24: Type [`bool`][] :material-equal: `False`{ title="default value" }** + + +Show the full Python path of the root members. + +This option does the same thing as [`show_root_full_path`][], +but for direct members of the root object instead of the root object itself. + +To show the full path for every member recursively, +see [`show_object_full_path`][]. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + show_root_members_full_path: true +``` + +```md title="or in docs/some_page.md (local configuration)" +::: package.module + options: + show_root_members_full_path: false +``` + +/// admonition | Preview + type: preview + +//// tab | With members full path +

Docstring of the module.

+

package.module.Class

+

Docstring of the class.

+

method

+

Docstring of the method.

+//// + +//// tab | Without members full path +

Docstring of the module.

+

Class

+

Docstring of the class.

+

method

+

Docstring of the method.

+//// +/// + +## `show_object_full_path` + +- **:octicons-package-24: Type [`bool`][] :material-equal: `False`{ title="default value" }** + + +Show the full Python path of every object. + +Same as for [`show_root_members_full_path`][], +but for every member, recursively. This option takes precedence over +[`show_root_members_full_path`][]: + +`show_root_members_full_path` | `show_object_full_path` | Direct root members path +----------------------------- | ----------------------- | ------------------------ +False | False | Name only +False | True | Full +True | False | Full +True | True | Full + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + show_object_full_path: true +``` + +```md title="or in docs/some_page.md (local configuration)" +::: package.module + options: + show_object_full_path: false +``` + +/// admonition | Preview + type: preview + +//// tab | With objects full path +

Docstring of the module.

+

package.module.Class

+

Docstring of the class.

+

package.module.Class.method

+

Docstring of the method.

+//// + +//// tab | Without objects full path +

Docstring of the module.

+

Class

+

Docstring of the class.

+

method

+

Docstring of the method.

+//// +/// + +## `show_category_heading` + +- **:octicons-package-24: Type [`bool`][] :material-equal: `False`{ title="default value" }** + + +When [grouped by categories][group_by_category], show a heading for each category. +These category headings will appear in the table of contents, +allowing you to link to them using their permalinks. + +WARNING: **Not recommended with deeply nested object** +When injecting documentation for deeply nested objects, +you'll quickly run out of heading levels, and the objects +at the bottom of the tree risk all getting documented +using H6 headings, which might decrease the readability +of your API docs. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + group_by_category: true + show_category_heading: true +``` + +```md title="or in docs/some_page.md (local configuration)" +::: package.module + options: + group_by_category: true + show_category_heading: false +``` + +/// admonition | Preview + type: preview + +//// tab | With category headings +

Docstring of the module.

+

Attributes (2)

+

module_attribute (3)

+

Docstring of the module attribute.

+

Classes (2)

+

Class (3)

+

Docstring of the class.

+

Attributes (4)

+
class_attribute (5)
+

Docstring of the class attribute.

+

Methods (4)

+
method (5)
+

Docstring of the method.

+//// + +//// tab | Without category headings +

Docstring of the module.

+

module_attribute (2)

+

Docstring of the module attribute.

+

Class (2)

+

Docstring of the class.

+

class_attribute (3)

+

Docstring of the class attribute.

+

method (3)

+

Docstring of the method.

+//// +/// diff --git a/docs/usage/configuration/members.md b/docs/usage/configuration/members.md new file mode 100644 index 00000000..412fdf45 --- /dev/null +++ b/docs/usage/configuration/members.md @@ -0,0 +1,363 @@ +# Members options + +## `members` + +- **:octicons-package-24: Type list[str] | + bool | None :material-equal: `None`{ title="default value" }** + + +An explicit list of members to render. + +Only members declared in this list will be rendered. +A member without a docstring will still be rendered, +even if [`show_if_no_docstring`][] is set to false. + +The members will be rendered in the specified order, +regardless of the value of [`members_order`][]. + +Passing a falsy value (`no`, `false` in YAML) or an empty list (`[]`) +will tell the Python handler not to render any member. +Passing a truthy value (`yes`, `true` in YAML) +will tell the Python handler to render every member. + +Any given value, except for an explicit `None` (`null` in YAML) +will tell the handler to ignore [`filters`][] for the object's members. +Filters will still be applied to the next layers of members (grand-children). + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + members: + - hello # (1) +``` + +1. :warning: Most of the time it won't make sense to use this option at the global level. + +```md title="or in docs/some_page.md (local configuration)" +::: package.module + options: + members: + - ThisClass + - this_function +``` + +```python title="package/module.py" +"""Module docstring.""" + +def this_function(): + """Function docstring.""" + +class ThisClass: + """Class docstring.""" + def method(self): + """Method docstring.""" + +this_attribute = 0 +"""Attribute docstring.""" +``` + +/// admonition | Preview + type: preview + +//// tab | With `members: true` +

Module docstring.

+

this_function

+

Function docstring.

+

ThisClass

+

Class docstring.

+

method

+

Method docstring.

+

this_attribute

+

Attribute docstring.

+//// + +//// tab | With `members: false` or `members: []` +

Module docstring.

+//// + +//// tab | With `members: [ThisClass]` +

Module docstring.

+

ThisClass

+

Class docstring.

+

method

+

Method docstring.

+//// +/// + +INFO: **The default behavior (with unspecified `members` or `members: null`) is to use [`filters`][].** + +## `members_order` + +- **:octicons-package-24: Type [`str`][] :material-equal: `"alphabetical"`{ title="default value" }** + + +The members ordering to use. Possible values: + +- `alphabetical`: order by the members names. +- `source`: order members as they appear in the source file. + +The order applies for all members, recursively. +The order will be ignored for members that are explicitely sorted using the [`members`][] option. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + members_order: alphabetical +``` + +```md title="or in docs/some_page.md (local configuration)" +::: package.module + options: + members_order: source +``` + +```python title="package/module.py" +"""Module docstring.""" + +def function_b(): + """Function a.""" + +def function_a(): + """Function b.""" + +def function_c(): + """Function c.""" +``` + +/// admonition | Preview + type: preview + +//// tab | With alphabetical order +

Module docstring.

+

function_a

+

Function a.

+

function_b

+

Function b.

+

function_c

+

Function c.

+//// + +//// tab | With source order +

Module docstring.

+

function_b

+

Function b.

+

function_a

+

Function a.

+

function_c

+

Function c.

+//// +/// + +## `filters` + +- **:octicons-package-24: Type list[str] | None :material-equal: `["!^_[^_]"]`{ title="default value" }** + + +A list of filters applied to filter objects based on their name. + +Filters are regular expressions. These regular expressions are evaluated by Python +and so must match the syntax supported by the [`re`][] module. +A filter starting with `!` (negative filter) will exclude matching objects instead of including them. + +The default value (`["!^_[^_]"]`) means: *render every object, except those +starting with one underscore, unless they start with two underscores*. +It means that an object whose name is `hello`, `__hello`, or `__hello__` +will be rendered, but not one whose name is `_hello`. + +Each filter takes precedence over the previous one. This allows for fine-grain +selection of objects by adding more specific filters. For example, you can +start by unselecting objects that start with `_`, and add a second filter +that re-select objects that start with `__`. The default filters can +therefore be rewritten like this: + +```yaml +filters: +- "!^_" +- "^__" +``` + +If there are no negative filters, the handler considers that everything +is **unselected** first, and then selects things based on your positive filters. +If there is at least one negative filter, the handler considers that everything +is **selected** first, and then re-selects/unselects things based on your other filters. +In short, `filters: ["a"]` means *"keep ***nothing*** except names containing `a`"*, while +`filters: ["!a"]` means *"keep ***everything*** except names containing `a`"*. + +An empty list of filters tells the Python handler to render every object. +The [`members`][] option takes precedence over filters +(filters will still be applied recursively to lower members in the hierarchy). + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + filters: + - "!^_" +``` + +```md title="or in docs/some_page.md (local configuration)" +::: package.module + options: + filters: [] +``` + +```python title="package/module.py" +def hello(): ... +def _world(): ... +``` + +/// admonition | Preview + type: preview + +//// tab | With `filters: []` +

Module docstring.

+

hello

+

Function docstring.

+

_world

+

Function docstring.

+//// + +//// tab | With `filters: ["hello"]` +

Module docstring.

+

hello

+

Function docstring.

+//// + +//// tab | With `filters: ["!hello"]` +

Module docstring.

+

_world

+

Function docstring.

+//// +/// + +/// admonition | Common filters + type: tip + +Here are some common filters that you might to want to use. + +- `["!^_"]`: exclude all private/protected/special objects +- `["!^_", "^__init__$"]`: same as above, but keep `__init__` methods +- `["!^_[^_]"]`: exclude all private/protected objects, keep special ones (default filters) +/// + +## `group_by_category` + +- **:octicons-package-24: Type [`bool`][] :material-equal: `True`{ title="default value" }** + + +Group the object members by categories: attributes, classes, functions, and modules. + +Members within a same category will be ordered according to the [`members_order`][] option. +You can use the [`show_category_heading`][] option to also render a heading for each category. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + group_by_category: true +``` + +```md title="or in docs/some_page.md (local configuration)" +::: package.module + options: + group_by_category: false +``` + +```python title="package/module.py" +def function_a(): ... +class ClassB: ... +attribute_C = 0 +def function_d(): ... +``` + +/// admonition | Preview + type: preview + +//// tab | With category grouping +

Module docstring.

+

attribute_c

+

Attribute docstring.

+

ClassB

+

Class docstring.

+

function_a

+

Function docstring.

+

function_d

+

Function docstring.

+//// + +//// tab | Without category grouping +

Module docstring.

+

function_a

+

Function docstring.

+

ClassB

+

Class docstring.

+

attribute_c

+

Attribute docstring.

+

function_d

+

Function docstring.

+//// +/// + +## `show_submodules` + +- **:octicons-package-24: Type [`bool`][] :material-equal: `False`{ title="default value" }** + + +When rendering a module, show its submodules recursively. + +This is false by default, because most of the time we render only one module per page, +and when rendering a package (a tree of modules and their members) on a single page, +we quickly run out of [heading levels][heading_level]. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + show_submodules: true +``` + +```md title="or in docs/some_page.md (local configuration)" +::: package.subpackage + options: + show_submodules: false +``` + +```tree title="package" +package + __init__.py + subpackage + __init__.py + submodule.py +``` + +/// admonition | Preview + type: preview + +//// tab | With submodules +

Subpackage docstring.

+

subpackage_member

+

Member docstring.

+

submodule

+

Submodule docstring.

+

submodule_member

+

Member docstring.

+//// + +//// tab | Without submodules +

Subpackage docstring.

+

subpackage_member

+

Member docstring.

+//// +/// \ No newline at end of file diff --git a/docs/usage/configuration/signatures.md b/docs/usage/configuration/signatures.md new file mode 100644 index 00000000..822c8f6d --- /dev/null +++ b/docs/usage/configuration/signatures.md @@ -0,0 +1,274 @@ +# Signatures options + +## `annotations_path` + +- **:octicons-package-24: Type [`str`][] :material-equal: `"brief"`{ title="default value" }** + + +The verbosity for annotations path. + +Possible values: + +- `brief` (recommended): render only the last component of each type path, not their full paths. + For example, it will render `Sequence[Path]` and not `typing.Sequence[pathlib.Path]`. + Brief annotations will cross-reference the right object anyway, + and show the full path in a tooltip when hovering them. +- `source`: render annotations as written in the source. For example if you imported `typing` as `t`, + it will render `typing.Sequence` as `t.Sequence`. Each part will cross-reference the relevant object: + `t` will link to the `typing` module and `Sequence` will link to the `Sequence` type. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + annotations_path: brief +``` + +```md title="or in docs/some_page.md (local configuration)" +::: path.to.module + options: + annotations_path: source +``` + +```python +import markdown +import markupsafe + +def convert(text: str, md: markdown.Markdown) -> markupsafe.Markup: + """Convert text to Markdown. + + Parameters: + text: The text to convert. + md: A Markdown instance. + + Returns: + Converted markup. + """ + return Markup(md.convert(text)) +``` + +/// admonition | Preview + type: preview + +//// tab | Brief annotations +

convert(text, md)

+

Convert text to Markdown.

+

Parameters:

+ +**Type** | **Description** | **Default** +---------- | ------------------------ | ----------- +[`str`][] | The text to convert. | *required* +[`Markdown`](#ref-to-markdown){ .external title="markdown.Markdown" } | A Markdown instance. | *required* + +

Returns:

+ +**Type** | **Name** | **Description** +---------- | ----------- | --------------- +[`Markup`](#ref-to-markup){ .external title="markupsafe.Markup" } | `text` | Converted markup. +//// + +//// tab | Source annotations +

convert(text, md)

+

Convert text to Markdown.

+

Parameters:

+ +**Type** | **Description** | **Default** +---------- | ------------------------ | ----------- +[`str`][] | The text to convert. | *required* +markdown.Markdown | A Markdown instance. | *required* + +

Returns:

+ +**Type** | **Name** | **Description** +---------- | ----------- | --------------- +markupsafe.Markup | `text` | Converted markup. +//// +/// + +## `line_length` + +- **:octicons-package-24: Type [`int`][] :material-equal: `60`{ title="default value" }** + + +Maximum line length when formatting code/signatures. + +When separating signatures from headings with the [`separate_signature`][] option, +the Python handler will try to format the signatures using [Black] and +the specified line length. + +If Black is not installed, the handler issues an INFO log once. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + separate_signature: true + line_length: 60 +``` + +```md title="or in docs/some_page.md (local configuration)" +::: path.to.module + options: + separate_signature: true + line_length: 80 +``` + +/// admonition | Preview + type: preview + +//// tab | Line length 60 +

long_function_name

+
long_function_name(
+    long_parameter_1="hello",
+    long_parameter_2="world",
+)
+//// + +//// tab | Line length 80 +

long_function_name

+
long_function_name(long_parameter_1="hello", long_parameter_2="world")
+//// +/// + +## `show_signature` + +- **:octicons-package-24: Type [`bool`][] :material-equal: `True`{ title="default value" }** + + +Show methods and functions signatures. + +Without it, just the function/method name is rendered. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + show_signature: true +``` + +```md title="or in docs/some_page.md (local configuration)" +::: path.to.module + options: + show_signature: false +``` + +/// admonition | Preview + type: preview + +//// tab | With signature +

function(param1, param2=None)

+

Function docstring.

+//// + +//// tab | Without signature +

function

+

Function docstring.

+//// +/// + +## `show_signature_annotations` + +- **:octicons-package-24: Type [`bool`][] :material-equal: `False`{ title="default value" }** + + +Show the type annotations in methods and functions signatures. + +Since the heading can become quite long when annotations are rendered, +it is usually best to [separate the signature][separate_signature] from the heading. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + separate_signature: true + show_signature_annotations: true +``` + +```md title="or in docs/some_page.md (local configuration)" +::: path.to.module + options: + separate_signature: true + show_signature_annotations: false +``` + +/// admonition | Preview + type: preview + +//// tab | With signature annotations +

function

+ +```python +function( + param1: list[int | float], + param2: bool | None = None, +) -> float +``` + +

Function docstring.

+//// + +//// tab | Without signature annotations +

function

+ +```python +function(param1, param2=None) +``` + +

Function docstring.

+//// +/// + +## `separate_signature` + +- **:octicons-package-24: Type [`bool`][] :material-equal: `False`{ title="default value" }** + + +Whether to put the whole signature in a code block below the heading. + +When separating signatures from headings, +the Python handler will try to format the signatures using [Black] and +the specified [line length][line_length]. + +If Black is not installed, the handler issues an INFO log once. + +```yaml title="in mkdocs.yml (global configuration)" +plugins: +- mkdocstrings: + handlers: + python: + options: + separate_signature: false +``` + +```md title="or in docs/some_page.md (local configuration)" +::: path.to.module + options: + separate_signature: true +``` + +/// admonition | Preview + type: preview + +//// tab | With separate signature +

function

+ +```python +function(param1, param2=None) +``` + +

Function docstring.

+//// + +//// tab | Without separate signature +

function(param1, param2=None)

+

Function docstring.

+//// +/// diff --git a/docs/customization.md b/docs/usage/customization.md similarity index 80% rename from docs/customization.md rename to docs/usage/customization.md index 5e729675..dd2bd56c 100644 --- a/docs/customization.md +++ b/docs/usage/customization.md @@ -23,26 +23,32 @@ The following CSS classes are used in the generated HTML: - `doc-label`: on `small` elements containing a label - `doc-label-LABEL`: same, where `LABEL` is replaced by the actual label -!!! example "Example with colorful labels" - === "CSS" - ```css - .doc-label { border-radius: 15px; padding: 0 5px; } - .doc-label-special { background-color: blue; color: white; } - .doc-label-private { background-color: red; color: white; } - .doc-label-property { background-color: green; color: white; } - .doc-label-read-only { background-color: yellow; color: black; } - ``` - - === "Result" - -

- special - private - property - read-only -

+/// admonition | Example with colorful labels + type: example + +//// tab | CSS +```css +.doc-label { border-radius: 15px; padding: 0 5px; } +.doc-label-special { background-color: blue; color: white; } +.doc-label-private { background-color: red; color: white; } +.doc-label-property { background-color: green; color: white; } +.doc-label-read-only { background-color: yellow; color: black; } +``` +//// + +//// tab | Result + +

+ special + private + property + read-only +

+//// + +/// ### Recommended style (Material) diff --git a/docs/usage/docstrings/google.md b/docs/usage/docstrings/google.md new file mode 100644 index 00000000..de35d46e --- /dev/null +++ b/docs/usage/docstrings/google.md @@ -0,0 +1,28 @@ +# Google style + +## :warning: Work in Progress! + +### Google-style admonitions + +With Google-style docstrings, any section that is not recognized will be transformed into its admonition equivalent. +For example: + +=== "Docstring" + ```python + """ + Note: + It looks like a section, but it will be rendered as an admonition. + + Tip: You can even choose a title. + This admonition has a custom title! + """ + ``` + +=== "Result" + NOTE: It looks like a section, but it will be rendered as an admonition. + + TIP: **You can even choose a title.** + This admonition has a custom title! + +See [Napoleon's documentation](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html). +See the supported docstring sections on [Griffe's documentation](https://mkdocstrings.github.io/griffe/docstrings/). diff --git a/docs/usage/docstrings/numpy.md b/docs/usage/docstrings/numpy.md new file mode 100644 index 00000000..524bfbfe --- /dev/null +++ b/docs/usage/docstrings/numpy.md @@ -0,0 +1,11 @@ +# Numpydoc style + +## :warning: Work in Progress! + +NOTE: As Numpy-style is partially supported by the underlying parser, +you may experience problems in the building process if your docstring +has a `Methods` section in the class docstring +(see [#366](https://github.com/mkdocstrings/mkdocstrings/issues/366)). + +See [Numpydoc's documentation](https://numpydoc.readthedocs.io/en/latest/format.html). +See the supported docstring sections on [Griffe's documentation](https://mkdocstrings.github.io/griffe/docstrings/). diff --git a/docs/usage/docstrings/sphinx.md b/docs/usage/docstrings/sphinx.md new file mode 100644 index 00000000..bf88c19b --- /dev/null +++ b/docs/usage/docstrings/sphinx.md @@ -0,0 +1,6 @@ +# Sphinx style + +## :warning: Work in Progress! + +See [Sphinx's documentation](https://sphinx-rtd-tutorial.readthedocs.io/en/latest/docstrings.html). +See the supported docstring sections on [Griffe's documentation](https://mkdocstrings.github.io/griffe/docstrings/). diff --git a/docs/usage.md b/docs/usage/index.md similarity index 61% rename from docs/usage.md rename to docs/usage/index.md index 332a72ad..041e8d72 100644 --- a/docs/usage.md +++ b/docs/usage/index.md @@ -4,16 +4,74 @@ TIP: **This is the documentation for the NEW Python handler.** To read the documentation for the LEGACY handler, go to the [legacy handler documentation](https://mkdocstrings.github.io/python-legacy). -The tool used by the Python handler to collect documentation from Python source code -is [Griffe](https://mkdocstrings.github.io/griffe). The word "griffe" can sometimes be used instead of "signature" in french. -Griffe is able to visit the Abstract Syntax Tree (AST) of the source code to extract useful information. -It is also able to execute the code (by importing it) and introspect objects in memory -when source code is not available. Finally, it can parse docstrings following different styles, -see [Supported docstrings styles](#supported-docstrings-styles). +## Installation -Like every handler, the Python handler accepts both **global** and **local** options. +You can install this handler as a *mkdocstrings* extra: -## Global-only options +```toml title="pyproject.toml" +# PEP 621 dependencies declaration +# adapt to your dependencies manager +[project] +dependencies = [ + "mkdocstrings[python]>=0.18", +] +``` + +You can also explicitly depend on the handler: + +```toml title="pyproject.toml" +# PEP 621 dependencies declaration +# adapt to your dependencies manager +[project] +dependencies = [ + "mkdocstrings-python", +] +``` + +The Python handler is the default *mkdocstrings* handler. +You can change the default handler, +or explicitely set the Python handler as default by defining the `default_handler` +configuration option of `mkdocstrings` in `mkdocs.yml`: + +```yaml title="mkdocs.yml" +plugins: +- mkdocstrings: + default_handler: python +``` + +## Injecting documentation + +With the Python handler installed and configured as default handler, +you can inject documentation for a module, class, function, or any other Python object +with *mkdocstrings*' [autodoc syntax], in your Markdown pages: + +```md +::: path.to.object +``` + +If another handler was defined as default handler, +you can explicitely ask for the Python handler to be used when injecting documentation +with the `handler` option: + +```md +::: path.to.object + handler: python +``` + +## Configuration + +When installed, the Python handler becomes the default *mkdocstrings* handler. +You can configure it in `mkdocs.yml`: + +```yaml title="mkdocs.yml" +plugins: +- mkdocstrings: + handlers: + python: + ... # the Python handler configuration +``` + +### Global-only options Some options are **global only**, and go directly under the handler's name. @@ -36,6 +94,11 @@ Some options are **global only**, and go directly under the handler's name. the inventories of your project's dependencies, at least those that are used in the public API. + See [*mkdocstrings*' documentation on inventories][inventories] + for more details. + + [inventories]: https://mkdocstrings.github.io/usage/#cross-references-to-other-projects-inventories + NOTE: This global option is common to *all* handlers, however they might implement it differently (or not even implement it). @@ -52,16 +115,31 @@ Some options are **global only**, and go directly under the handler's name. More details at [Finding modules](#finding-modules). -- `load_external_modules`: - this option allows resolving aliases to any external module. - Enabling this option will tell handler that when it encounters an import that is made public - through the `__all__` variable, it will resolve it recursively to *any* module. - **Use with caution:** this can load a *lot* of modules, slowing down your build - or triggering errors that we do not yet handle. - **We recommend using the `preload_modules` option instead**, - which acts as an include-list rather than as include-all. +- `load_external_modules`: this option allows resolving aliases (imports) to any external module. + Modules are considered external when they are not part + of the package your are injecting documentation for. + Enabling this option will tell the handler to resolve aliases recursively + when they are made public through the [`__all__`][__all__] variable. + + WARNING: **Use with caution** + This can load a *lot* of modules through [Griffe], + slowing down your build or triggering errors that Griffe does not yet handle. + **We recommend using the [`preload_modules`][] option instead**, + which acts as an include-list rather than as include-all. + + Example: + + ```yaml title="mkdocs.yml" + plugins: + - mkdocstrings: + handlers: + python: + load_external_modules: true + ``` + + [__all__]: https://docs.python.org/3/tutorial/modules.html#importing-from-a-package -## Global/local options +### Global/local options The other options can be used both globally *and* locally, under the `options` key. For example, globally: @@ -83,54 +161,24 @@ plugins: do_something: false ``` -These options affect how the documentation is collected from sources and rendered: -headings, members, docstrings, etc. +These options affect how the documentation is collected from sources and rendered. +See the following tables summarizing the options, and get more details for each option +in the following pages: + +- [General options](configuration/general.md): various options that do not fit in the other categories +- [Headings options](configuration/headings.md): options related to headings and the table of contents + (or sidebar, depending on the theme used) +- [Members options](configuration/members.md): options related to filtering or ordering members + in the generated documentation +- [Docstrings options](configuration/docstrings.md): options related to docstrings (parsing and rendering) +- [Signature options](configuration/signatures.md): options related to signatures and type annotations + +#### Options summary ::: mkdocstrings_handlers.python.handler.PythonHandler.default_config options: show_root_toc_entry: false -## Supported docstrings styles - -Griffe supports the Google-style, Numpy-style and Sphinx-style docstring formats. -The style used by default is the Google-style. -You can configure what style you want to use with -the `docstring_style` and `docstring_options` options, -both globally or locally, i.e. per autodoc instruction. - -- Google: see [Napoleon's documentation](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html). -- Numpy: see [Numpydoc's documentation](https://numpydoc.readthedocs.io/en/latest/format.html). -- Sphinx: see [Sphinx's documentation](https://sphinx-rtd-tutorial.readthedocs.io/en/latest/docstrings.html). - -See the supported docstring sections on [Griffe's documentation](https://mkdocstrings.github.io/griffe/docstrings/). - -NOTE: As Numpy-style is partially supported by the underlying parser, -you may experience problems in the building process if your docstring -has a `Methods` section in the class docstring -(see [#366](https://github.com/mkdocstrings/mkdocstrings/issues/366)). - -### Google-style admonitions - -With Google-style docstrings, any section that is not recognized will be transformed into its admonition equivalent. -For example: - -=== "Docstring" - ```python - """ - Note: - It looks like a section, but it will be rendered as an admonition. - - Tip: You can even choose a title. - This admonition has a custom title! - """ - ``` - -=== "Result" - NOTE: It looks like a section, but it will be rendered as an admonition. - - TIP: **You can even choose a title.** - This admonition has a custom title! - ## Finding modules There are multiple ways to tell the handler where to find your packages/modules. @@ -283,21 +331,24 @@ We recommend using the [`paths` method](#using-the-paths-option) instead. Install your package in the current environment, and run MkDocs: -=== "pip" - ```bash - . venv/bin/activate - pip install -e . - mkdocs build - ``` +/// tab | pip +```bash +. venv/bin/activate +pip install -e . +mkdocs build +``` +/// -=== "PDM" - ```bash - pdm install - pdm run mkdocs build - ``` +/// tab | PDM +```bash +pdm install +pdm run mkdocs build +``` +/// -=== "Poetry" - ```bash - poetry install - poetry run mkdocs build - ``` +/// tab | Poetry +```bash +poetry install +poetry run mkdocs build +``` +/// diff --git a/duties.py b/duties.py index 51cef860..3d7592d3 100644 --- a/duties.py +++ b/duties.py @@ -10,13 +10,18 @@ from duty import duty from duty.callables import black, blacken_docs, coverage, lazy, mkdocs, mypy, pytest, ruff, safety +if sys.version_info < (3, 8): + from importlib_metadata import version as pkgversion +else: + from importlib.metadata import version as pkgversion + + if TYPE_CHECKING: 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) -TESTING = os.environ.get("TESTING", "0") in {"1", "true"} CI = os.environ.get("CI", "0") in {"1", "true", "yes", ""} WINDOWS = os.name == "nt" PTY = not WINDOWS and not CI @@ -30,6 +35,12 @@ def pyprefix(title: str) -> str: # noqa: D103 return title +def mkdocs_config() -> str: # noqa: D103 + if "+insiders" in pkgversion("mkdocs-material"): + return "mkdocs.insiders.yml" + return "mkdocs.yml" + + @duty def changelog(ctx: Context) -> None: """Update the changelog in-place with latest commits. @@ -39,7 +50,7 @@ def changelog(ctx: Context) -> None: """ from git_changelog.cli import build_and_render - git_changelog = lazy("git_changelog")(build_and_render) + git_changelog = lazy(build_and_render, name="git_changelog") ctx.run( git_changelog( repository=".", @@ -48,7 +59,7 @@ def changelog(ctx: Context) -> None: template="keepachangelog", parse_trailers=True, parse_refs=False, - sections=("build", "deps", "feat", "fix", "refactor"), + sections=["build", "deps", "feat", "fix", "refactor"], bump_latest=True, in_place=True, ), @@ -56,7 +67,7 @@ def changelog(ctx: Context) -> None: ) -@duty(pre=["check_quality", "check_types", "check_docs", "check_dependencies"]) +@duty(pre=["check_quality", "check_types", "check_docs", "check_dependencies", "check-api"]) def check(ctx: Context) -> None: # noqa: ARG001 """Check it all! @@ -104,7 +115,7 @@ def check_docs(ctx: Context) -> None: """ Path("htmlcov").mkdir(parents=True, exist_ok=True) Path("htmlcov/index.html").touch(exist_ok=True) - ctx.run(mkdocs.build(strict=True), title=pyprefix("Building documentation")) + ctx.run(mkdocs.build(strict=True, config_file=mkdocs_config()), title=pyprefix("Building documentation")) @duty @@ -121,6 +132,23 @@ def check_types(ctx: Context) -> None: ) +@duty +def check_api(ctx: Context) -> None: + """Check for API breaking changes. + + Parameters: + ctx: The context instance (passed automatically). + """ + from griffe.cli import check as g_check + + griffe_check = lazy(g_check, name="griffe.check") + ctx.run( + griffe_check("mkdocstrings_handlers", search_paths=["src"]), + title="Checking for API breaking changes", + nofail=True, + ) + + @duty(silent=True) def clean(ctx: Context) -> None: """Delete temporary files. @@ -142,17 +170,7 @@ def clean(ctx: Context) -> None: @duty -def docs(ctx: Context) -> None: - """Build the documentation locally. - - Parameters: - ctx: The context instance (passed automatically). - """ - ctx.run(mkdocs.build, title="Building documentation") - - -@duty -def docs_serve(ctx: Context, host: str = "127.0.0.1", port: int = 8000) -> None: +def docs(ctx: Context, host: str = "127.0.0.1", port: int = 8000) -> None: """Serve the documentation (localhost:8000). Parameters: @@ -161,7 +179,7 @@ def docs_serve(ctx: Context, host: str = "127.0.0.1", port: int = 8000) -> None: port: The port to serve the docs on. """ ctx.run( - mkdocs.serve(dev_addr=f"{host}:{port}"), + mkdocs.serve(dev_addr=f"{host}:{port}", config_file=mkdocs_config()), title="Serving documentation", capture=False, ) @@ -174,7 +192,23 @@ def docs_deploy(ctx: Context) -> None: Parameters: ctx: The context instance (passed automatically). """ - ctx.run(mkdocs.gh_deploy, title="Deploying documentation") + os.environ["DEPLOY"] = "true" + config_file = mkdocs_config() + if config_file == "mkdocs.yml": + ctx.run(lambda: False, title="Not deploying docs without Material for MkDocs Insiders!") + origin = ctx.run("git config --get remote.origin.url", silent=True) + if "pawamoy-insiders/python" in origin: + ctx.run("git remote add upstream git@github.com:mkdocstrings/python", silent=True, nofail=True) + ctx.run( + mkdocs.gh_deploy(config_file=config_file, remote_name="upstream", force=True), + title="Deploying documentation", + ) + else: + ctx.run( + lambda: False, + title="Not deploying docs from public repository (do that from insiders instead!)", + nofail=True, + ) @duty @@ -196,7 +230,7 @@ def format(ctx: Context) -> None: ) -@duty +@duty(post=["docs-deploy"]) def release(ctx: Context, version: str) -> None: """Release a new Python package. @@ -204,15 +238,19 @@ def release(ctx: Context, version: str) -> None: ctx: The context instance (passed automatically). version: The new version number to use. """ + origin = ctx.run("git config --get remote.origin.url", silent=True) + if "pawamoy-insiders/python" in origin: + ctx.run( + lambda: False, + title="Not releasing from insiders repository (do that from public repo instead!)", + ) 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) - if not TESTING: - ctx.run("git push", title="Pushing commits", pty=False) - ctx.run("git push --tags", title="Pushing tags", pty=False) - ctx.run("pdm build", title="Building dist/wheel", pty=PTY) - ctx.run("twine upload --skip-existing dist/*", title="Publishing version", pty=PTY) - docs_deploy.run() + ctx.run("git push", title="Pushing commits", pty=False) + ctx.run("git push --tags", title="Pushing tags", pty=False) + ctx.run("pdm build", title="Building dist/wheel", pty=PTY) + ctx.run("twine upload --skip-existing dist/*", title="Publishing version", pty=PTY) @duty(silent=True, aliases=["coverage"]) diff --git a/mkdocs.insiders.yml b/mkdocs.insiders.yml new file mode 100644 index 00000000..93e3a93b --- /dev/null +++ b/mkdocs.insiders.yml @@ -0,0 +1,4 @@ +INHERIT: mkdocs.yml + +plugins: + typeset: {} diff --git a/mkdocs.yml b/mkdocs.yml index f07a73d2..bdb863e9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -4,34 +4,67 @@ site_url: "https://mkdocstrings.github.io/python" repo_url: "https://github.com/mkdocstrings/python" repo_name: "mkdocstrings/python" site_dir: "site" -watch: [README.md, CONTRIBUTING.md, CHANGELOG.md, src/mkdocstrings_handlers] +watch: [mkdocs.yml, README.md, CONTRIBUTING.md, CHANGELOG.md, src/mkdocstrings_handlers] +copyright: Copyright © 2021 Timothée Mazzucotelli nav: - Home: - Overview: index.md - - Usage: usage.md - - Customization: customization.md - Changelog: changelog.md - Credits: credits.md - License: license.md +- Usage: + - usage/index.md + - Configuration options: + - General: usage/configuration/general.md + - Headings: usage/configuration/headings.md + - Members: usage/configuration/members.md + - Docstrings: usage/configuration/docstrings.md + - Signatures: usage/configuration/signatures.md + - Docstring styles: + - Google: usage/docstrings/google.md + - Numpy: usage/docstrings/numpy.md + - Sphinx: usage/docstrings/sphinx.md + - Advanced: + - Customization: usage/customization.md # defer to gen-files + literate-nav - Code Reference: reference/ - Development: - Contributing: contributing.md - Code of Conduct: code_of_conduct.md - Coverage report: coverage.md +- Insiders: + - insiders/index.md + - Getting started: + - Installation: insiders/installation.md + - Changelog: insiders/changelog.md - mkdocstrings: https://mkdocstrings.github.io/ theme: name: material + custom_dir: docs/.overrides logo: logo.png 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 @@ -45,38 +78,57 @@ theme: accent: lime toggle: icon: material/weather-night - name: Switch to light mode + name: Switch to system preference extra_css: - css/material.css - css/mkdocstrings.css +- css/insiders.css markdown_extensions: +- abbr +- attr_list - admonition - callouts: strip_period: no -- pymdownx.emoji +- footnotes +- md_in_html +- pymdownx.blocks.admonition +- pymdownx.blocks.details +- pymdownx.blocks.tab: + alternate_style: true + slugify: !!python/object/apply:pymdownx.slugs.slugify + kwds: + case: lower +- pymdownx.emoji: + emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg - pymdownx.magiclink - pymdownx.snippets: + auto_append: [docs/.glossary.md] check_paths: true - pymdownx.superfences - pymdownx.tabbed: alternate_style: true -- pymdownx.tasklist + slugify: !!python/object/apply:pymdownx.slugs.slugify + kwds: + case: lower +- pymdownx.tasklist: + custom_checkbox: true - toc: permalink: "¤" plugins: -- search -- markdown-exec -- gen-files: + autorefs: {} + search: {} + markdown-exec: {} + gen-files: scripts: - scripts/gen_ref_nav.py -- literate-nav: + literate-nav: nav_file: SUMMARY.txt -- coverage -- section-index -- mkdocstrings: + coverage: {} + mkdocstrings: handlers: python: paths: [src] @@ -85,13 +137,15 @@ plugins: - https://mkdocstrings.github.io/objects.inv - https://mkdocstrings.github.io/griffe/objects.inv options: - docstring_style: google + separate_signature: true + merge_init_into_class: true docstring_options: - ignore_init_summary: yes - merge_init_into_class: yes - separate_signature: yes - show_source: no - show_root_full_path: no + ignore_init_summary: true + git-committers: + enabled: !ENV [DEPLOY, false] + repository: mkdocstrings/python + minify: + minify_html: !ENV [DEPLOY, false] extra: social: @@ -101,3 +155,7 @@ extra: link: https://fosstodon.org/@pawamoy - icon: fontawesome/brands/twitter link: https://twitter.com/pawamoy + - icon: fontawesome/brands/gitter + link: https://gitter.im/mkdocstrings/python + - icon: fontawesome/brands/python + link: https://pypi.org/project/mkdocstrings-python/ diff --git a/pyproject.toml b/pyproject.toml index 673d3c8b..22d865ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,12 @@ [build-system] -requires = ["pdm-pep517"] -build-backend = "pdm.pep517.api" +requires = ["pdm-backend"] +build-backend = "pdm.backend" [project] name = "mkdocstrings-python" description = "A Python handler for mkdocstrings." authors = [{name = "Timothée Mazzucotelli", email = "pawamoy@pm.me"}] -license = "ISC" +license = {text = "ISC"} readme = "README.md" requires-python = ">=3.7" keywords = [] @@ -52,16 +52,18 @@ includes = ["src/mkdocstrings_handlers"] editable-backend = "editables" [tool.pdm.dev-dependencies] -duty = ["duty>=0.8"] +duty = ["duty>=0.10"] docs = [ + "black>=23.1", + "markdown-callouts>=0.2", + "markdown-exec>=0.5", "mkdocs>=1.3", "mkdocs-coverage>=0.2", "mkdocs-gen-files>=0.3", + "mkdocs-git-committers-plugin-2>=1.1", "mkdocs-literate-nav>=0.4", "mkdocs-material>=7.3", - "mkdocs-section-index>=0.3", - "markdown-callouts>=0.2", - "markdown-exec>=0.5", + "mkdocs-minify-plugin>=0.6.4", "toml>=0.10", ] maintain = [ @@ -81,6 +83,7 @@ tests = [ typing = [ "mypy>=0.911", "types-markdown>=3.3", + "types-pyyaml>=6.0", "types-toml>=0.10", ] security = ["safety>=2"] diff --git a/scripts/gen_credits.py b/scripts/gen_credits.py index 7f59f8f9..85ac9041 100644 --- a/scripts/gen_credits.py +++ b/scripts/gen_credits.py @@ -53,6 +53,8 @@ def _get_deps(base_deps: Mapping[str, Mapping[str, str]]) -> dict[str, dict[str, for dep in base_deps: parsed = regex.match(dep).groupdict() # type: ignore[union-attr] dep_name = parsed["dist"].lower() + if dep_name not in lock_pkgs: + continue deps[dep_name] = {"license": _get_license(dep_name), **parsed, **lock_pkgs[dep_name]} again = True @@ -63,7 +65,7 @@ def _get_deps(base_deps: Mapping[str, Mapping[str, str]]) -> dict[str, dict[str, for pkg_dependency in lock_pkgs[pkg_name].get("dependencies", []): parsed = regex.match(pkg_dependency).groupdict() # type: ignore[union-attr] dep_name = parsed["dist"].lower() - if dep_name not in deps and dep_name != project["name"]: + if dep_name in lock_pkgs and dep_name not in deps and dep_name != project["name"]: deps[dep_name] = {"license": _get_license(dep_name), **parsed, **lock_pkgs[dep_name]} again = True @@ -87,7 +89,7 @@ def _render_credits() -> str: } template_text = dedent( """ - These projects were used to build `{{ project_name }}`. **Thank you!** + These projects were used to build *{{ project_name }}*. **Thank you!** [`python`](https://www.python.org/) | [`pdm`](https://pdm.fming.dev/) | diff --git a/scripts/insiders.py b/scripts/insiders.py new file mode 100644 index 00000000..add870cb --- /dev/null +++ b/scripts/insiders.py @@ -0,0 +1,201 @@ +"""Functions related to Insiders funding goals.""" + +from __future__ import annotations + +import json +import logging +import posixpath +from dataclasses import dataclass +from datetime import date, datetime, timedelta +from itertools import chain +from pathlib import Path +from textwrap import dedent +from typing import Iterable, cast +from urllib.error import HTTPError +from urllib.parse import urljoin +from urllib.request import urlopen + +import yaml + +logger = logging.getLogger(f"mkdocs.logs.{__name__}") + + +def human_readable_amount(amount: int) -> str: # noqa: D103 + str_amount = str(amount) + if len(str_amount) >= 4: # noqa: PLR2004 + return f"{str_amount[:len(str_amount)-3]},{str_amount[-3:]}" + return str_amount + + +@dataclass +class Project: + """Class representing an Insiders project.""" + + name: str + url: str + + +@dataclass +class Feature: + """Class representing an Insiders feature.""" + + name: str + ref: str + since: date | None + project: Project | None + + def url(self, rel_base: str = "..") -> str: # noqa: D102 + if self.project: + rel_base = self.project.url + return posixpath.join(rel_base, self.ref.lstrip("/")) + + def render(self, rel_base: str = "..", *, badge: bool = False) -> None: # noqa: D102 + new = "" + if badge: + recent = self.since and date.today() - self.since <= timedelta(days=60) # noqa: DTZ011 + if recent: + ft_date = self.since.strftime("%B %d, %Y") # type: ignore[union-attr] + new = f' :material-alert-decagram:{{ .new-feature .vibrate title="Added on {ft_date}" }}' + project = f"[{self.project.name}]({self.project.url}) — " if self.project else "" + print(f"- [{'x' if self.since else ' '}] {project}[{self.name}]({self.url(rel_base)}){new}") + + +@dataclass +class Goal: + """Class representing an Insiders goal.""" + + name: str + amount: int + features: list[Feature] + complete: bool = False + + @property + def human_readable_amount(self) -> str: # noqa: D102 + return human_readable_amount(self.amount) + + def render(self, rel_base: str = "..") -> None: # noqa: D102 + print(f"#### $ {self.human_readable_amount} — {self.name}\n") + for feature in self.features: + feature.render(rel_base) + print("") + + +def load_goals(data: str, funding: int = 0, project: Project | None = None) -> dict[int, Goal]: + """Load goals from JSON data. + + Parameters: + data: The JSON data. + funding: The current total funding, per month. + origin: The origin of the data (URL). + + Returns: + A dictionaries of goals, keys being their target monthly amount. + """ + goals_data = yaml.safe_load(data)["goals"] + return { + amount: Goal( + name=goal_data["name"], + amount=amount, + complete=funding >= amount, + features=[ + Feature( + name=feature_data["name"], + ref=feature_data["ref"], + since=feature_data["since"] + and datetime.strptime(feature_data["since"], "%Y/%m/%d").date(), # noqa: DTZ007 + project=project, + ) + for feature_data in goal_data["features"] + ], + ) + for amount, goal_data in goals_data.items() + } + + +def funding_goals(source: str | list[tuple[str, str, str]], funding: int = 0) -> dict: + """Load funding goals from a given data source. + + Parameters: + source: The data source (local file path or URL). + funding: The current total funding, per month. + + Returns: + A dictionaries of goals, keys being their target monthly amount. + """ + if isinstance(source, str): + try: + data = Path(source).read_text() + except OSError as error: + raise RuntimeError(f"Could not load data from disk: {source}") from error + return load_goals(data, funding) + goals = {} + for project_name, project_url, data_fragment in source: + data_url = urljoin(project_url, data_fragment) + try: + with urlopen(data_url) as response: # noqa: S310 + data = response.read() + except HTTPError as error: + raise RuntimeError(f"Could not load data from network: {data_url}") from error + source_goals = load_goals(data, funding, project=Project(name=project_name, url=project_url)) + for amount, goal in source_goals.items(): + if amount not in goals: + goals[amount] = goal + else: + goals[amount].features.extend(goal.features) + return goals + + +def feature_list(goals: Iterable[Goal]) -> list[Feature]: + """Extract feature list from funding goals. + + Parameters: + goals: A list of funding goals. + + Returns: + A list of features. + """ + return list(chain.from_iterable(goal.features for goal in goals)) + + +def load_json(url: str) -> str | list | dict: # noqa: D103 + with urlopen(url) as response: # noqa: S310 + return json.loads(response.read().decode()) + + +data_source = globals()["data_source"] +sponsor_url = "https://github.com/sponsors/pawamoy" +data_url = "https://raw.githubusercontent.com/pawamoy/sponsors/main" +numbers: dict[str, int] = load_json(f"{data_url}/numbers.json") # type: ignore[assignment] +sponsors: list[dict] = load_json(f"{data_url}/sponsors.json") # type: ignore[assignment] +current_funding = numbers["total"] +sponsors_count = numbers["count"] +goals = funding_goals(data_source, funding=current_funding) +all_features = feature_list(goals.values()) +completed_features = sorted( + (ft for ft in all_features if ft.since), + key=lambda ft: cast(date, ft.since), + reverse=True, +) + + +def print_join_sponsors_button() -> None: # noqa: D103 + btn_classes = "{ .md-button .md-button--primary }" + print( + dedent( + f""" + [:octicons-heart-fill-24:{{ .pulse }} +   Join our {sponsors_count} awesome sponsors]({sponsor_url}){btn_classes} + """, + ), + ) + + +def print_sponsors() -> None: # noqa: D103 + private_sponsors_count = sponsors_count - len(sponsors) + for sponsor in sponsors: + print( + f"""""" + f"""""", + ) + if private_sponsors_count: + print(f"""+{private_sponsors_count}""") diff --git a/scripts/setup.sh b/scripts/setup.sh index 9e7ab1ff..559ae8b1 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -14,7 +14,7 @@ if ! pdm self list 2>/dev/null | grep -q pdm-multirun; then fi if [ -n "${PYTHON_VERSIONS}" ]; then - pdm multirun -vi ${PYTHON_VERSIONS// /,} pdm install + pdm multirun -vi ${PYTHON_VERSIONS// /,} pdm install -G:all else - pdm install + pdm install -G:all fi diff --git a/src/mkdocstrings_handlers/python/handler.py b/src/mkdocstrings_handlers/python/handler.py index ffb645ea..775d7de0 100644 --- a/src/mkdocstrings_handlers/python/handler.py +++ b/src/mkdocstrings_handlers/python/handler.py @@ -109,8 +109,23 @@ class PythonHandler(BaseHandler): "annotations_path": "brief", "preload_modules": None, "load_external_modules": False, + "allow_inspection": True, } """ + Attributes: General options: + allow_inspection (bool): Whether to allow inspecting modules when visiting them is not possible. Default: `True`. + show_bases (bool): Show the base classes of a class. Default: `True`. + show_source (bool): Show the source code of this object. Default: `True`. + preload_modules (list[str] | None): Pre-load modules that are + not specified directly in autodoc instructions (`::: identifier`). + It is useful when you want to render documentation for a particular member of an object, + and this member is imported from another package than its parent. + + For an imported member to be rendered, you need to add it to the `__all__` attribute + of the importing module. + + The modules must be listed as an array of strings. Default: `None`. + Attributes: Headings options: heading_level (int): The initial heading level to use. Default: `2`. show_root_heading (bool): Show the heading of the object at the root of the documentation tree @@ -136,7 +151,6 @@ class PythonHandler(BaseHandler): docstring_style (str): The docstring style to use: `google`, `numpy`, `sphinx`, or `None`. Default: `"google"`. docstring_options (dict): The options for the docstring parser. See parsers under [`griffe.docstrings`][]. docstring_section_style (str): The style used to render docstring sections. Options: `table`, `list`, `spacy`. Default: `"table"`. - line_length (int): Maximum line length when formatting code/signatures. Default: `60`. merge_init_into_class (bool): Whether to merge the `__init__` method into the class' signature and docstring. Default: `False`. show_if_no_docstring (bool): Show the object heading even if it has no docstring or children with docstrings. Default: `False`. show_docstring_attributes (bool): Whether to display the "Attributes" section in the object's docstring. Default: `True`. @@ -152,24 +166,11 @@ class PythonHandler(BaseHandler): Attributes: Signatures/annotations options: annotations_path (str): The verbosity for annotations path: `brief` (recommended), or `source` (as written in the source). Default: `"brief"`. + line_length (int): Maximum line length when formatting code/signatures. Default: `60`. show_signature (bool): Show methods and functions signatures. Default: `True`. show_signature_annotations (bool): Show the type annotations in methods and functions signatures. Default: `False`. separate_signature (bool): Whether to put the whole signature in a code block below the heading. If Black is installed, the signature is also formatted using it. Default: `False`. - - Attributes: Additional options: - show_bases (bool): Show the base classes of a class. Default: `True`. - show_source (bool): Show the source code of this object. Default: `True`. - preload_modules (list[str] | None): Pre-load modules that are - not specified directly in autodoc instructions (`::: identifier`). - It is useful when you want to render documentation for a particular member of an object, - and this member is imported from another package than its parent. - - For an imported member to be rendered, you need to add it to the `__all__` attribute - of the importing module. - - The modules must be listed as an array of strings. Default: `None`. - """ def __init__( @@ -257,6 +258,7 @@ def collect(self, identifier: str, config: Mapping[str, Any]) -> CollectorItem: docstring_options=parser_options, modules_collection=self._modules_collection, lines_collection=self._lines_collection, + allow_inspection=final_config["allow_inspection"], ) try: for pre_loaded_module in final_config.get("preload_modules") or []: @@ -328,11 +330,11 @@ def update_env(self, md: Markdown, config: dict) -> None: # noqa: D102 (ignore self.env.filters["format_signature"] = rendering.do_format_signature self.env.filters["filter_objects"] = rendering.do_filter_objects - def get_anchors(self, data: CollectorItem) -> list[str]: # noqa: D102 (ignore missing docstring) + def get_anchors(self, data: CollectorItem) -> set[str]: # noqa: D102 (ignore missing docstring) try: - return list({data.path, data.canonical_path, *data.aliases}) + return {data.path, data.canonical_path, *data.aliases} except AliasResolutionError: - return [data.path] + return {data.path} def get_handler( diff --git a/src/mkdocstrings_handlers/python/rendering.py b/src/mkdocstrings_handlers/python/rendering.py index 9ee91769..70a34ca3 100644 --- a/src/mkdocstrings_handlers/python/rendering.py +++ b/src/mkdocstrings_handlers/python/rendering.py @@ -185,11 +185,21 @@ def do_filter_objects( Returns: A list of objects. """ - if members_list is not None: - if not members_list: - return [] - return [obj for obj in objects_dictionary.values() if obj.name in set(members_list)] + # no members + if members_list is False or members_list == []: + return [] + objects = list(objects_dictionary.values()) + + # all members + if members_list is True: + return objects + + # list of members + if members_list is not None: + return [obj for obj in objects if obj.name in set(members_list)] + + # none, use filters and docstrings if filters: objects = [obj for obj in objects if _keep_object(obj.name, filters)] if keep_no_docstrings: @@ -202,7 +212,7 @@ def _get_black_formatter() -> Callable[[str, int], str]: try: from black import Mode, format_str except ModuleNotFoundError: - logger.warning("Formatting signatures requires Black to be installed.") + logger.info("Formatting signatures requires Black to be installed.") return lambda text, _: text def formatter(code: str, line_length: int) -> str: diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/docstring/admonition.html b/src/mkdocstrings_handlers/python/templates/material/_base/docstring/admonition.html index b3cab485..7d056df8 100644 --- a/src/mkdocstrings_handlers/python/templates/material/_base/docstring/admonition.html +++ b/src/mkdocstrings_handlers/python/templates/material/_base/docstring/admonition.html @@ -1,5 +1,5 @@ {{ log.debug("Rendering admonition") }} -
+
{{ section.title|convert_markdown(heading_level, html_id, strip_paragraph=True) }} {{ section.value.contents|convert_markdown(heading_level, html_id) }}
\ No newline at end of file