` tag in `convert_markdown` filter ([5351fc8](https://github.com/mkdocstrings/mkdocstrings/commit/5351fc8b417fb20f0681a22f49fcc902579eacdb) by Oleh Prypin). [PR #369](https://github.com/mkdocstrings/mkdocstrings/pull/369)
- Support handlers spanning multiple locations ([f42dfc6](https://github.com/mkdocstrings/mkdocstrings/commit/f42dfc61ce4f9f317c4bd17f568e504ed9764d35) by Timothée Mazzucotelli). [PR #355](https://github.com/mkdocstrings/mkdocstrings/pull/355)
### Code Refactoring
- Prefix logs with the package name only ([6c2b734](https://github.com/mkdocstrings/mkdocstrings/commit/6c2b7348ae40989e4adccc087feae599fcea949d) by Timothée Mazzucotelli). [PR #375](https://github.com/mkdocstrings/mkdocstrings/pull/375)
- Extract the Python handler into its own repository ([74371e4](https://github.com/mkdocstrings/mkdocstrings/commit/74371e49c32059fefd34c7cc7f7b8f085b383237) by Timothée Mazzucotelli). [PR #356](https://github.com/mkdocstrings/mkdocstrings/pull/356)
- Support Jinja2 3.1 ([b377227](https://github.com/mkdocstrings/mkdocstrings/commit/b37722716b1e0ed6393ec71308dfb0f85e142f3b) by Timothée Mazzucotelli). [Issue #360](https://github.com/mkdocstrings/mkdocstrings/issues/360), [PR #361](https://github.com/mkdocstrings/mkdocstrings/pull/361)
- Find templates in new and deprecated namespaces ([d5d5f18](https://github.com/mkdocstrings/mkdocstrings/commit/d5d5f1844dbac3affacc95f2f3eab57a61d2068c) by Timothée Mazzucotelli). [PR #367](https://github.com/mkdocstrings/mkdocstrings/pull/367)
- Support loading handlers from the `mkdocstrings_handlers` namespace ([5c22c6c](https://github.com/mkdocstrings/mkdocstrings/commit/5c22c6ce4e056ac2334e2dfcd47c1f1a7884d352) by Timothée Mazzucotelli). [PR #367](https://github.com/mkdocstrings/mkdocstrings/pull/367)
## [0.17.0](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.17.0) - 2021-12-27
[Compare with 0.16.2](https://github.com/mkdocstrings/mkdocstrings/compare/0.16.2...0.17.0)
### Features
- Add `show_signature` rendering option ([024ee82](https://github.com/mkdocstrings/mkdocstrings/commit/024ee826bb6f0aa297ba857bc18075d6f4162cad) by Will Da Silva). [Issue #341](https://github.com/mkdocstrings/mkdocstrings/issues/341), [PR #342](https://github.com/mkdocstrings/mkdocstrings/pull/342)
- Support Keyword Args and Yields sections ([1286427](https://github.com/mkdocstrings/mkdocstrings/commit/12864271b7f997af7b421a834919b1e686793905) by Timothée Mazzucotelli). [Issue #205](https://github.com/mkdocstrings/mkdocstrings/issues/205) and [#324](https://github.com/mkdocstrings/mkdocstrings/issues/324), [PR #331](https://github.com/mkdocstrings/mkdocstrings/pull/331)
### Bug Fixes
- Do minimum work when falling back to re-collecting an object to get its anchor ([f6cf570](https://github.com/mkdocstrings/mkdocstrings/commit/f6cf570255df17db1088b6e6cd94bcc823b3b17f) by Timothée Mazzucotelli). [Issue #329](https://github.com/mkdocstrings/mkdocstrings/issues/329), [PR #330](https://github.com/mkdocstrings/mkdocstrings/pull/330)
### Code Refactoring
- Return multiple identifiers from fallback method ([78c498c](https://github.com/mkdocstrings/mkdocstrings/commit/78c498c4a6cfc33cc6ceab9829426bd64e518d44) by Timothée Mazzucotelli). [Issue mkdocstrings/autorefs#11](https://github.com/mkdocstrings/autorefs/issues/11), [PR #350](https://github.com/mkdocstrings/mkdocstrings/pull/350)
## [0.16.2](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.16.2) - 2021-10-04
[Compare with 0.16.1](https://github.com/mkdocstrings/mkdocstrings/compare/0.16.1...0.16.2)
### Dependencies
- Support `pymdown-extensions` v9.x ([0831343](https://github.com/mkdocstrings/mkdocstrings/commit/0831343aa8726ed785b17bba1c8d4adf49b46748) by Ofek Lev and [38b22ec](https://github.com/mkdocstrings/mkdocstrings/commit/38b22ec11cded4689115dafc43e16a1e8e40feda) by Timothée Mazzucotelli).
## [0.16.1](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.16.1) - 2021-09-23
[Compare with 0.16.0](https://github.com/mkdocstrings/mkdocstrings/compare/0.16.0...0.16.1)
### Bug Fixes
- Fix ReadTheDocs "return" template ([598621b](https://github.com/mkdocstrings/mkdocstrings/commit/598621bff29d2aeda0e14f350cda36c1a1f418d5) by Timothée Mazzucotelli).
## [0.16.0](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.16.0) - 2021-09-20
[Compare with 0.15.0](https://github.com/mkdocstrings/mkdocstrings/compare/0.15.0...0.16.0)
### Features
- Add a rendering option to change the sorting of members ([b1fff8b](https://github.com/mkdocstrings/mkdocstrings/commit/b1fff8b8ef4d6d77417fc43ed8be4b578d6437e4) by Joe Rickerby). [Issue #114](https://github.com/mkdocstrings/mkdocstrings/issues/114), [PR #274](https://github.com/mkdocstrings/mkdocstrings/pull/274)
- Add option to show Python base classes ([436f550](https://github.com/mkdocstrings/mkdocstrings/commit/436f5504ad72ab6d1f5b4303e6b68bc84562c32b) by Brian Koropoff). [Issue #269](https://github.com/mkdocstrings/mkdocstrings/issues/269), [PR #297](https://github.com/mkdocstrings/mkdocstrings/pull/297)
- Support loading external Python inventories in Sphinx format ([a8418cb](https://github.com/mkdocstrings/mkdocstrings/commit/a8418cb4c6193d35cdc72508b118a712cf0334e1) by Oleh Prypin). [PR #287](https://github.com/mkdocstrings/mkdocstrings/pull/287)
- Support loading external inventories and linking to them ([8b675f4](https://github.com/mkdocstrings/mkdocstrings/commit/8b675f4671f8bbfd2f337ed043e3682b0a0ad0f6) by Oleh Prypin). [PR #277](https://github.com/mkdocstrings/mkdocstrings/pull/277)
- Very basic support for MkDocs theme ([974ca90](https://github.com/mkdocstrings/mkdocstrings/commit/974ca9010efca1b8279767faf8efcd2470a8371d) by Oleh Prypin). [PR #272](https://github.com/mkdocstrings/mkdocstrings/pull/272)
- Generate objects inventory ([14ed959](https://github.com/mkdocstrings/mkdocstrings/commit/14ed959860a784a835cd71f911081f2026d66c81) and [bbd85a9](https://github.com/mkdocstrings/mkdocstrings/commit/bbd85a92fa70bddfe10a907a4d63b8daf0810cb2) by Timothée Mazzucotelli). [Issue #251](https://github.com/mkdocstrings/mkdocstrings/issues/251), [PR #253](https://github.com/mkdocstrings/mkdocstrings/pull/253)
### Bug Fixes
- Don't render empty code blocks for missing type annotations ([d2e9e1e](https://github.com/mkdocstrings/mkdocstrings/commit/d2e9e1ef3cf304081b07f763843a9722bf9b117e) by Oleh Prypin).
- Fix custom handler not being used ([6dcf342](https://github.com/mkdocstrings/mkdocstrings/commit/6dcf342fb83b19e385d56d37235f2b23e8c8c767) by Timothée Mazzucotelli). [Issue #259](https://github.com/mkdocstrings/mkdocstrings/issues/259), [PR #263](https://github.com/mkdocstrings/mkdocstrings/pull/263)
- Don't hide `setup_commands` errors ([92418c4](https://github.com/mkdocstrings/mkdocstrings/commit/92418c4b3e80b67d5116efa73931fc113daa60e9) by Gabriel Vîjială). [PR #258](https://github.com/mkdocstrings/mkdocstrings/pull/258)
### Code Refactoring
- Move writing extra files to an earlier stage in the build ([3890ab5](https://github.com/mkdocstrings/mkdocstrings/commit/3890ab597692e56d7ece576c166373b66ff4e615) by Oleh Prypin). [PR #275](https://github.com/mkdocstrings/mkdocstrings/pull/275)
## [0.15.2](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.15.2) - 2021-06-09
[Compare with 0.15.1](https://github.com/mkdocstrings/mkdocstrings/compare/0.15.1...0.15.2)
### Packaging
- MkDocs default schema needs to be obtained differently now ([b3e122b](https://github.com/mkdocstrings/mkdocstrings/commit/b3e122b36d586632738ddedaed7d3df8d5dead44) by Oleh Prypin). [PR #273](https://github.com/mkdocstrings/mkdocstrings/pull/273)
- Compatibility with MkDocs 1.2: livereload isn't guaranteed now ([36e8024](https://github.com/mkdocstrings/mkdocstrings/commit/36e80248d2ab9e61975f6c83ae517115c9410fc1) by Oleh Prypin). [PR #294](https://github.com/mkdocstrings/mkdocstrings/pull/294)
## [0.15.1](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.15.1) - 2021-05-16
[Compare with 0.15.0](https://github.com/mkdocstrings/mkdocstrings/compare/0.15.0...0.15.1)
### Bug Fixes
- Prevent error during parallel installations ([fac2c71](https://github.com/mkdocstrings/mkdocstrings/commit/fac2c711351f7b62bf5308f19cfc612a3944588a) by Timothée Mazzucotelli).
### Packaging
- Support the upcoming major Jinja and MarkupSafe releases ([bb4f9de](https://github.com/mkdocstrings/mkdocstrings/commit/bb4f9de08a77bef85e550d70deb0db13e6aa0c96) by Oleh Prypin). [PR #283](https://github.com/mkdocstrings/mkdocstrings/pull/283)
- Accept a higher version of mkdocs-autorefs ([c8de08e](https://github.com/mkdocstrings/mkdocstrings/commit/c8de08e177f78290d3baaca2716d1ec64c9059b6) by Oleh Prypin). [PR #282](https://github.com/mkdocstrings/mkdocstrings/pull/282)
## [0.15.0](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.15.0) - 2021-02-28
[Compare with 0.14.0](https://github.com/mkdocstrings/mkdocstrings/compare/0.14.0...0.15.0)
### Breaking Changes
The following items are *possible* breaking changes:
- Cross-linking to arbitrary headings now requires to opt-in to the *autorefs* plugin,
which is installed as a dependency of *mkdocstrings*.
See [Cross-references to any Markdown heading](https://mkdocstrings.github.io/usage/#cross-references-to-any-markdown-heading).
- *mkdocstrings* now respects your configured code highlighting method,
so if you are using the CodeHilite extension, the `.highlight` CSS class in the rendered HTML will become `.codehilite`.
So make sure to adapt your extra CSS accordingly. Or just switch to using [pymdownx.highlight](https://facelessuser.github.io/pymdown-extensions/extensions/highlight/), it's better supported by *mkdocstrings* anyway.
See [Syntax highlighting](https://mkdocstrings.github.io/theming/#syntax-highlighting).
- Most of the [CSS rules that *mkdocstrings* used to recommend](https://mkdocstrings.github.io/handlers/python/#recommended-style-material) for manual addition, now become mandatory (auto-injected into the site). This shouldn't *break* any of your styles, but you are welcome to remove the now-redundant lines that you had copied into `extra_css`, [similarly to this diff](https://github.com/mkdocstrings/mkdocstrings/pull/218/files#diff-7889a1924c66ff9318f1d81c4a3b75658d09bebf0db3b2e4023ba3e40294eb73).
### Features
- Nicer-looking error outputs - no tracebacks from mkdocstrings ([6baf720](https://github.com/mkdocstrings/mkdocstrings/commit/6baf720850d359ddb55713553a757fe7b2283e10) by Oleh Prypin). [PR #230](https://github.com/mkdocstrings/mkdocstrings/pull/230)
- Let handlers add CSS to the pages, do so for Python handler ([05c7a3f](https://github.com/mkdocstrings/mkdocstrings/commit/05c7a3fc83b67d3244ea3bfe97dab19aa53f2d38) by Oleh Prypin). [Issue #189](https://github.com/mkdocstrings/mkdocstrings/issues/189), [PR #218](https://github.com/mkdocstrings/mkdocstrings/pull/218)
- Allow linking to an object heading not only by its canonical identifier, but also by its possible aliases ([4789950](https://github.com/mkdocstrings/mkdocstrings/commit/4789950ff43c354d47afbed5c89d5abb917ffee6) by Oleh Prypin). [PR #217](https://github.com/mkdocstrings/mkdocstrings/pull/217)
### Bug Fixes
- Propagate the CSS class to inline highlighting as well ([c7d80e6](https://github.com/mkdocstrings/mkdocstrings/commit/c7d80e63a042913b7511c38a788967796dd10997) by Oleh Prypin). [PR #245](https://github.com/mkdocstrings/mkdocstrings/pull/245)
- Don't double-escape characters in highlighted headings ([6357144](https://github.com/mkdocstrings/mkdocstrings/commit/6357144b100be6a2e7e6140e035c289c225cec22) by Oleh Prypin). [Issue #228](https://github.com/mkdocstrings/mkdocstrings/issues/228), [PR #241](https://github.com/mkdocstrings/mkdocstrings/pull/241)
### Code Refactoring
- Use the autorefs plugin from its new external location ([e2d74ef](https://github.com/mkdocstrings/mkdocstrings/commit/e2d74efb0d59f9a1aa45e42525ceb1d4b7638426) by Oleh Prypin). [PR #235](https://github.com/mkdocstrings/mkdocstrings/pull/235)
- Split out Markdown extensions from `handlers` to `handlers.rendering` ([7533852](https://github.com/mkdocstrings/mkdocstrings/commit/7533852e3ac0a378b70a380cef1100421b7d5763) by Oleh Prypin). [PR #233](https://github.com/mkdocstrings/mkdocstrings/pull/233)
- Theme-agnostic code highlighting, respecting configs ([f9ea009](https://github.com/mkdocstrings/mkdocstrings/commit/f9ea00979545e39983ba377f1930d73ae94165ea) by Oleh Prypin). [PR #202](https://github.com/mkdocstrings/mkdocstrings/pull/202)
- Split out autorefs plugin, make it optional ([fc67656](https://github.com/mkdocstrings/mkdocstrings/commit/fc676564f9b11269b3e0b0482703ac924069a3fa) by Oleh Prypin). [PR #220](https://github.com/mkdocstrings/mkdocstrings/pull/220)
- Remove the extra wrapper div from the final doc ([7fe438c](https://github.com/mkdocstrings/mkdocstrings/commit/7fe438c4040a2124b00c39e582ef4c38be7c55c9) by Oleh Prypin). [PR #209](https://github.com/mkdocstrings/mkdocstrings/pull/209)
- Don't re-parse the whole subdoc, expose only headings ([15f84f9](https://github.com/mkdocstrings/mkdocstrings/commit/15f84f981982c8e2b15498f5c869ac207f3ce5d7) by Oleh Prypin). [PR #209](https://github.com/mkdocstrings/mkdocstrings/pull/209)
- Actually exclude hidden headings from the doc ([0fdb082](https://github.com/mkdocstrings/mkdocstrings/commit/0fdb0821867eb0e14a972a603c22301aafecf4f4) by Oleh Prypin). [PR #209](https://github.com/mkdocstrings/mkdocstrings/pull/209)
## [0.14.0](https://github.com/pawamoy/mkdocstrings/releases/tag/0.14.0) - 2021-01-06
[Compare with 0.13.6](https://github.com/pawamoy/mkdocstrings/compare/0.13.6...0.14.0)
Special thanks to Oleh [@oprypin](https://github.com/oprypin) Prypin who did an amazing job (this is a euphemism)
at improving *mkdocstrings*, fixing hard-to-fix bugs with clever solutions, implementing great new features
and refactoring the code for better performance and readability! Thanks Oleh!
### Bug Fixes
- Fix double code tags ([e84d401](https://github.com/pawamoy/mkdocstrings/commit/e84d401c6dcb9aecb8cc1a58d3a0f339e1c3e78f) by Timothée Mazzucotelli).
- Don't mutate the original Markdown config for permalinks ([8f6b163](https://github.com/pawamoy/mkdocstrings/commit/8f6b163b50551da22f65e9b736e042562f77f2d7) by Oleh Prypin).
- Preserve text immediately before an autodoc ([07466fa](https://github.com/pawamoy/mkdocstrings/commit/07466fafb54963a4e35e69007b6291a0382aaeb4) by Oleh Prypin). [PR #207](https://github.com/pawamoy/mkdocstrings/pull/207)
- Remove `href` attributes from headings in templates ([d5602ff](https://github.com/pawamoy/mkdocstrings/commit/d5602ff3bb1a75ac1c8c457e972271b6c66eb8dd) by Oleh Prypin). [PR #204](https://github.com/pawamoy/mkdocstrings/pull/204)
- Don't let `toc` extension append its permalink twice ([a154f5c](https://github.com/pawamoy/mkdocstrings/commit/a154f5c4c6ef9abd221e1f89e44847ae2cf25436) by Oleh Prypin). [PR #203](https://github.com/pawamoy/mkdocstrings/pull/203)
- Fix undefined entity for `¶` ([2c29211](https://github.com/pawamoy/mkdocstrings/commit/2c29211002d515db40e5bdabf6cbf32ec8633a05) by Timothée Mazzucotelli).
- Make ids of Markdown sub-documents prefixed with the parent item id ([d493d33](https://github.com/pawamoy/mkdocstrings/commit/d493d33b3827d93e84a7b2e39f0a10dfcb782402) by Oleh Prypin). [Issue #186](https://github.com/pawamoy/mkdocstrings/issues/186) and [#193](https://github.com/pawamoy/mkdocstrings/issues/193), [PR #199](https://github.com/pawamoy/mkdocstrings/pull/199)
- More lenient regex for data-mkdocstrings-identifier ([dcfec8e](https://github.com/pawamoy/mkdocstrings/commit/dcfec8edfdff050debc5856dfc213d3119a84792) by Oleh Prypin).
- Shift Markdown headings according to the current `heading_level` ([13f41ae](https://github.com/pawamoy/mkdocstrings/commit/13f41aec5a95c82c1229baa4ac3caf4abb2add51) by Oleh Prypin). [Issue #192](https://github.com/pawamoy/mkdocstrings/issues/192), [PR #195](https://github.com/pawamoy/mkdocstrings/pull/195)
- Fix footnotes appearing in all following objects ([af24bc2](https://github.com/pawamoy/mkdocstrings/commit/af24bc246a6938ebcae7cf6ff677b194cf1af95c) by Oleh Prypin). [Issue #186](https://github.com/pawamoy/mkdocstrings/issues/186), [PR #195](https://github.com/pawamoy/mkdocstrings/pull/195)
- Fix cross-references from the root index page ([9c9f2a0](https://github.com/pawamoy/mkdocstrings/commit/9c9f2a04af94e0d88f57fd76249f7985166a9b88) by Oleh Prypin). [Issue #184](https://github.com/pawamoy/mkdocstrings/issues/184), [PR #185](https://github.com/pawamoy/mkdocstrings/pull/185)
- Fix incorrect argument name passed to Markdown ([10ce502](https://github.com/pawamoy/mkdocstrings/commit/10ce502d5fd58f1e5a4e14308ffad1bc3d7116ee) by Timothée Mazzucotelli).
- Fix error when a digit immediately follows a code tag ([9b92341](https://github.com/pawamoy/mkdocstrings/commit/9b9234160edc54b53c81a618b12095e7dd829059) by Oleh Prypin). [Issue #169](https://github.com/pawamoy/mkdocstrings/issues/169), [PR #175](https://github.com/pawamoy/mkdocstrings/pull/175)
- Detecting paths relative to template directory in logging ([a50046b](https://github.com/pawamoy/mkdocstrings/commit/a50046b5d58d62df4ba13f4c197e80edd1995eb9) by Oleh Prypin). [Issue #166](https://github.com/pawamoy/mkdocstrings/issues/166)
### Code Refactoring
- BlockProcessor already receives strings, use them as such ([bcf7da9](https://github.com/pawamoy/mkdocstrings/commit/bcf7da911a310a63351c5082e84bb763d90d5b3b) by Oleh Prypin).
- Remove some unused code ([8504084](https://github.com/pawamoy/mkdocstrings/commit/850408421cc027be8374673cc74c71fff26f3833) by Oleh Prypin). [PR #206](https://github.com/pawamoy/mkdocstrings/pull/206)
- Improve XML parsing error handling ([ad86410](https://github.com/pawamoy/mkdocstrings/commit/ad864100b644ab1ee8daaa0d3923bc87dee1c5ca) by Timothée Mazzucotelli).
- Explicitly use MarkupSafe ([6b9ebe7](https://github.com/pawamoy/mkdocstrings/commit/6b9ebe7d510e82971acef89e9e946af3c0cc96d3) by Oleh Prypin).
- Split out the handler cache, expose it through the plugin ([6453026](https://github.com/pawamoy/mkdocstrings/commit/6453026fac287387090a67cce70c078377d107dd) by Oleh Prypin). [Issue #179](https://github.com/pawamoy/mkdocstrings/issues/179), [PR #191](https://github.com/pawamoy/mkdocstrings/pull/191)
- Use ChainMap instead of copying dicts ([c634d2c](https://github.com/pawamoy/mkdocstrings/commit/c634d2ce6377de26caa553048bb28ef1e672f7aa) by Oleh Prypin). [PR #171](https://github.com/pawamoy/mkdocstrings/pull/171)
- Rename logging to loggers to avoid confusion ([7a119cc](https://github.com/pawamoy/mkdocstrings/commit/7a119ccf27cf77cf2cbd114e7fad0a9e4e97bbd8) by Timothée Mazzucotelli).
- Simplify logging ([409f93e](https://github.com/pawamoy/mkdocstrings/commit/409f93ed26d7d8292a8bc7a6c32cb270b3769409) by Timothée Mazzucotelli).
### Features
- Allow specifying `heading_level` as a Markdown heading ([10efc28](https://github.com/pawamoy/mkdocstrings/commit/10efc281e04b2a430cec53e49208ccc09e591667) by Oleh Prypin). [PR #170](https://github.com/pawamoy/mkdocstrings/pull/170)
- Allow any characters in identifiers ([7ede68a](https://github.com/pawamoy/mkdocstrings/commit/7ede68a0917b494eda2198931a8ad1c97fc8fce4) by Oleh Prypin). [PR #174](https://github.com/pawamoy/mkdocstrings/pull/174)
- Allow namespace packages for handlers ([39b0465](https://github.com/pawamoy/mkdocstrings/commit/39b046548f57dc59993241b24d2cf12fb5e488eb) by Timothée Mazzucotelli).
- Add template debugging/logging ([33b32c1](https://github.com/pawamoy/mkdocstrings/commit/33b32c1410bf6e8432768865c8aa86b8e091ab59) by Timothée Mazzucotelli).
- Add initial support for the ReadTheDocs theme ([1028115](https://github.com/pawamoy/mkdocstrings/commit/1028115682ed0806d6570c749af0e382c67d6120) by Timothée Mazzucotelli). [Issue #107](https://github.com/pawamoy/mkdocstrings/issues/107), [PR #159](https://github.com/pawamoy/mkdocstrings/pull/159)
- Add option to show type annotations in signatures ([f94ce9b](https://github.com/pawamoy/mkdocstrings/commit/f94ce9bdb2afc2c41c21a53636980ca077b757ce) by Timothée Mazzucotelli). [Issue #106](https://github.com/pawamoy/mkdocstrings/issues/106)
### Packaging
- Accept verions of `pytkdocs` up to 0.10.x (see [changelog](https://pawamoy.github.io/pytkdocs/changelog/#0100-2020-12-06)).
### Performance Improvements
- Call `update_env` only once per `Markdown` instance ([b198c74](https://github.com/pawamoy/mkdocstrings/commit/b198c74338dc3b54b999eadeef9946d69277ad77) by Oleh Prypin). [PR #201](https://github.com/pawamoy/mkdocstrings/pull/201)
- Disable Jinja's `auto_reload` to reduce disk reads ([3b28c58](https://github.com/pawamoy/mkdocstrings/commit/3b28c58c77642071419d4a98e007d5a854b7984f) by Oleh Prypin). [PR #200](https://github.com/pawamoy/mkdocstrings/pull/200)
- Rework autorefs replacement to not re-parse the final HTML ([22a9e4b](https://github.com/pawamoy/mkdocstrings/commit/22a9e4bf1b73f9b9b1a7c4876f0c677f919bc4d7) by Oleh Prypin). [Issue #187](https://github.com/pawamoy/mkdocstrings/issues/187), [PR #188](https://github.com/pawamoy/mkdocstrings/pull/188)
## [0.13.6](https://github.com/pawamoy/mkdocstrings/releases/tag/0.13.6) - 2020-09-28
[Compare with 0.13.5](https://github.com/pawamoy/mkdocstrings/compare/0.13.5...0.13.6)
### Bug Fixes
- Fix rendering when clicking on hidden toc entries ([2af4d31](https://github.com/pawamoy/mkdocstrings/commit/2af4d310adefec614235a2c1d04d5ff56bf9c220) by Timothée Mazzucotelli). Issue [#60](https://github.com/pawamoy/mkdocstrings/issues/60).
## [0.13.5](https://github.com/pawamoy/mkdocstrings/releases/tag/0.13.5) - 2020-09-28
[Compare with 0.13.4](https://github.com/pawamoy/mkdocstrings/compare/0.13.4...0.13.5)
## Packaging
- Accept `pytkdocs` version up to 0.9.x ([changelog](https://pawamoy.github.io/pytkdocs/changelog/#090-2020-09-28)).
## [0.13.4](https://github.com/pawamoy/mkdocstrings/releases/tag/0.13.4) - 2020-09-25
[Compare with 0.13.3](https://github.com/pawamoy/mkdocstrings/compare/0.13.3...0.13.4)
### Bug Fixes
- Bring back arbitrary `**config` to Python handler ([fca7d4c](https://github.com/pawamoy/mkdocstrings/commit/fca7d4c75ffd7a84eaeccd27facd5575604dbfab) by Florimond Manca). Issue [#154](https://github.com/pawamoy/mkdocstrings/issues/154), PR [#155](https://github.com/pawamoy/mkdocstrings/pull/155)
## [0.13.3](https://github.com/pawamoy/mkdocstrings/releases/tag/0.13.3) - 2020-09-25
[Compare with 0.13.2](https://github.com/pawamoy/mkdocstrings/compare/0.13.2...0.13.3)
### Packaging
- Accept `pytkdocs` version up to 0.8.x ([changelog](https://pawamoy.github.io/pytkdocs/changelog/#080-2020-09-25)).
## [0.13.2](https://github.com/pawamoy/mkdocstrings/releases/tag/0.13.2) - 2020-09-08
[Compare with 0.13.1](https://github.com/pawamoy/mkdocstrings/compare/0.13.1...0.13.2)
### Bug Fixes
- Fix relative URLs when `use_directory_urls` is false ([421d189](https://github.com/pawamoy/mkdocstrings/commit/421d189fff9ea2608e40d85e0a93e30334782b90) by Timothée Mazzucotelli). References: [#149](https://github.com/pawamoy/mkdocstrings/issues/149)
## [0.13.1](https://github.com/pawamoy/mkdocstrings/releases/tag/0.13.1) - 2020-09-03
[Compare with 0.13.0](https://github.com/pawamoy/mkdocstrings/compare/0.13.0...0.13.1)
### Bug Fixes
- Use relative links for cross-references ([9c77f1f](https://github.com/pawamoy/mkdocstrings/commit/9c77f1f461fa87842ae39945f9521ee85b1e413b) by Timothée Mazzucotelli). References: [#144](https://github.com/pawamoy/mkdocstrings/issues/144), [#147](https://github.com/pawamoy/mkdocstrings/issues/147)
## [0.13.0](https://github.com/pawamoy/mkdocstrings/releases/tag/0.13.0) - 2020-08-21
[Compare with 0.12.2](https://github.com/pawamoy/mkdocstrings/compare/0.12.2...0.13.0)
### Bug Fixes
- Accept dashes in module names ([fcf79d0](https://github.com/pawamoy/mkdocstrings/commit/fcf79d0024ec46c3862c94202864e054c04a6d0b) by Timothée Mazzucotelli). References: [#140](https://github.com/pawamoy/mkdocstrings/issues/140)
### Features
- Add option to show full path of direct members only ([d1b9401](https://github.com/pawamoy/mkdocstrings/commit/d1b9401afecb20d3123eec7334605cb15bf9d877) by Aaron Dunmore). References: [#134](https://github.com/pawamoy/mkdocstrings/issues/134), [#136](https://github.com/pawamoy/mkdocstrings/issues/136)
### Packaging
- Accept `pymdown-extensions` versions up to 0.8.x ([see release notes](https://facelessuser.github.io/pymdown-extensions/about/releases/8.0/#8.0)) ([178d48d](https://github.com/pawamoy/mkdocstrings/commit/178d48da7a62daf285dfc5f6ff230e8bce82ed53) by Hugo van Kemenade). PR [#146](https://github.com/pawamoy/mkdocstrings/issue/146)
## [0.12.2](https://github.com/pawamoy/mkdocstrings/releases/tag/0.12.2) - 2020-07-24
[Compare with 0.12.1](https://github.com/pawamoy/mkdocstrings/compare/0.12.1...0.12.2)
### Packaging
- Accept `pytkdocs` version up to 0.7.x ([changelog](https://pawamoy.github.io/pytkdocs/changelog/#070-2020-07-24)).
## [0.12.1](https://github.com/pawamoy/mkdocstrings/releases/tag/0.12.1) - 2020-07-07
[Compare with 0.12.0](https://github.com/pawamoy/mkdocstrings/compare/0.12.0...0.12.1)
### Bug Fixes
- Fix HTML-escaped sequence parsing as XML ([db297f1](https://github.com/pawamoy/mkdocstrings/commit/db297f19013fc402eeff1f2827057a959e481c66) by Timothée Mazzucotelli).
- Allow running mkdocs from non-default interpreter ([283dd7b](https://github.com/pawamoy/mkdocstrings/commit/283dd7b83eeba675a16d96d2e829851c1273a625) by Jared Khan).
## [0.12.0](https://github.com/pawamoy/mkdocstrings/releases/tag/0.12.0) - 2020-06-14
[Compare with 0.11.4](https://github.com/pawamoy/mkdocstrings/compare/0.11.4...0.12.0)
### Features
- Support attributes section in Google-style docstrings ([8300253](https://github.com/pawamoy/mkdocstrings/commit/83002532b2294ea33dcec4f2672a5a6d0f64def1) by Timothée Mazzucotelli). References: [#88](https://github.com/pawamoy/mkdocstrings/issues/88)
- Support examples section in Google-style docstrings ([650c754](https://github.com/pawamoy/mkdocstrings/commit/650c754afdd5d4fb96b1e2529f378d025a2e7daf) by Iago González). References: [#112](https://github.com/pawamoy/mkdocstrings/issues/112)
### Packaging
- Accept `pytkdocs` version up to 0.6.x ([changelog](https://pawamoy.github.io/pytkdocs/changelog/#060-2020-06-14)).
## [0.11.4](https://github.com/pawamoy/mkdocstrings/releases/tag/0.11.4) - 2020-06-08
[Compare with 0.11.3](https://github.com/pawamoy/mkdocstrings/compare/0.11.3...0.11.4)
### Packaging
- Accept `pytkdocs` version up to 0.5.x ([changelog](https://pawamoy.github.io/pytkdocs/changelog/#050-2020-06-08)).
If it breaks your docs, please [open issues on `pytkdocs`' bug-tracker](https://github.com/pawamoy/pytkdocs/issues),
or pin `pytkdocs` version to while waiting for bug fixes <0.5.0 :clown:.
## [0.11.3](https://github.com/pawamoy/mkdocstrings/releases/tag/0.11.3) - 2020-06-07
[Compare with 0.11.2](https://github.com/pawamoy/mkdocstrings/compare/0.11.2...0.11.3)
### Bug Fixes
- Support custom theme directory configuration ([1243cf6](https://github.com/pawamoy/mkdocstrings/commit/1243cf673aaf371e5cbf42a3e0d1aa80482398a3) by Abhishek Thakur). References: [#120](https://github.com/pawamoy/mkdocstrings/issues/120), [#121](https://github.com/pawamoy/mkdocstrings/issues/121)
## [0.11.2](https://github.com/pawamoy/mkdocstrings/releases/tag/0.11.2) - 2020-05-20
[Compare with 0.11.1](https://github.com/pawamoy/mkdocstrings/compare/0.11.1...0.11.2)
### Packaging
- Increase `pytkdocs` version range to accept 0.4.0
([changelog](https://pawamoy.github.io/pytkdocs/changelog/#040-2020-05-17)).
## [0.11.1](https://github.com/pawamoy/mkdocstrings/releases/tag/0.11.1) - 2020-05-14
[Compare with 0.11.0](https://github.com/pawamoy/mkdocstrings/compare/0.11.0...0.11.1)
### Bug Fixes
- Fix integration with mkdocs logging *une bonne fois pour toute* ([3293cbf](https://github.com/pawamoy/mkdocstrings/commit/3293cbf161f05d36de6c1d50b5de9742bf99066e) by Timothée Mazzucotelli).
- Discard setup commands stdout ([ea44cea](https://github.com/pawamoy/mkdocstrings/commit/ea44cea33159ed3a6b0b34b4cd52a17a40bd6460) by Timothée Mazzucotelli). References: [#91](https://github.com/pawamoy/mkdocstrings/issues/91)
- Use the proper python executable to start subprocesses ([9fe3b39](https://github.com/pawamoy/mkdocstrings/commit/9fe3b3915bd8f15011f8f3632a227d1eb56603fd) by Reece Dunham). References: [#91](https://github.com/pawamoy/mkdocstrings/issues/91), [#103](https://github.com/pawamoy/mkdocstrings/issues/103)
## [0.11.0](https://github.com/pawamoy/mkdocstrings/releases/tag/0.11.0) - 2020-04-23
[Compare with 0.10.3](https://github.com/pawamoy/mkdocstrings/compare/0.10.3...0.11.0)
### Bug Fixes
- Properly raise on errors (respect strict mode) ([2097208](https://github.com/pawamoy/mkdocstrings/commit/20972082a94b64bec02c77d6a80384d8042f60ea) by Timothée Mazzucotelli). Related issues/PRs: [#86](https://github.com/pawamoy/mkdocstrings/issues/86)
- Hook properly to MkDocs logging ([b23daed](https://github.com/pawamoy/mkdocstrings/commit/b23daed3743bbd2d3f024df34582a317c51a1af0) by Timothée Mazzucotelli). Related issues/PRs: [#86](https://github.com/pawamoy/mkdocstrings/issues/86)
### Features
- Add `setup_commands` option to python handler ([599f8e5](https://github.com/pawamoy/mkdocstrings/commit/599f8e528f55093b0011b732da959b747c1e02c0) by Ross Mechanic). Related issues/PRs: [#89](https://github.com/pawamoy/mkdocstrings/issues/89), [#90](https://github.com/pawamoy/mkdocstrings/issues/90)
- Add option to allow overriding templates ([7360021](https://github.com/pawamoy/mkdocstrings/commit/7360021ab4753706d0f6209ed960050f5d424ad8) by Mikaël Capelle). Related issues/PRs: [#59](https://github.com/pawamoy/mkdocstrings/issues/59), [#82](https://github.com/pawamoy/mkdocstrings/issues/82)
## [0.10.3](https://github.com/pawamoy/mkdocstrings/releases/tag/0.10.3) - 2020-04-10
[Compare with 0.10.2](https://github.com/pawamoy/mkdocstrings/compare/0.10.2...0.10.3)
### Bug Fixes
- Handle `site_url` not being defined ([9fb4a9b](https://github.com/pawamoy/mkdocstrings/commit/9fb4a9bbebe2457b389921ba1ee3e1f924c5691b) by Timothée Mazzucotelli). Related issues/PRs: [#77](https://github.com/pawamoy/mkdocstrings/issues/77)
### Packaging
This version increases the accepted range of versions for the `pytkdocs` dependency to `>=0.2.0, <0.4.0`.
The `pytkdocs` project just released [version 0.3.0](https://pawamoy.github.io/pytkdocs/changelog/#030-2020-04-10)
which:
- adds support for complex markup in docstrings sections items descriptions
- adds support for different indentations in docstrings sections (tabulations or less/more than 4 spaces)
- fixes docstring parsing for arguments whose names start with `*`, like `*args` and `**kwargs`
## [0.10.2](https://github.com/pawamoy/mkdocstrings/releases/tag/0.10.2) - 2020-04-07
[Compare with 0.10.1](https://github.com/pawamoy/mkdocstrings/compare/0.10.1...0.10.2)
### Packaging
This version increases the accepted range of versions for the `pymdown-extensions` dependency,
as well as for the `mkdocs-material` development dependency. Indeed, both these projects recently
released major versions 7 and 5 respectively. Users who wish to use these new versions will be able to.
See issue [#74](https://github.com/pawamoy/mkdocstrings/issues/74).
## [0.10.1](https://github.com/pawamoy/mkdocstrings/releases/tag/0.10.1) - 2020-04-03
[Compare with 0.10.0](https://github.com/pawamoy/mkdocstrings/compare/0.10.0...0.10.1)
### Bug Fixes
- Fix jinja2 error for jinja2 < 2.11 ([387f970](https://github.com/pawamoy/mkdocstrings/commit/387f97088ad2b7b25389ae6cf303bae071e90e6c) by Timothée Mazzucotelli). Related issues/PRs: [#67](https://github.com/pawamoy/mkdocstrings/issues/67), [#72](https://github.com/pawamoy/mkdocstrings/issues/72)
- Fix missing dependency pymdown-extensions ([648b99d](https://github.com/pawamoy/mkdocstrings/commit/648b99dab9d1af87db474ce7683de50c9bf8996d) by Timothée Mazzucotelli). Related issues/PRs: [#66](https://github.com/pawamoy/mkdocstrings/issues/66)
- Fix heading level of hidden toc entries ([475cc62](https://github.com/pawamoy/mkdocstrings/commit/475cc62b1cf4342b82ca8685166306441e4b83c4) by Timothée Mazzucotelli). Related issues/PRs: [#65](https://github.com/pawamoy/mkdocstrings/issues/65)
- Fix rendering signatures containing keyword_only ([c6c5add](https://github.com/pawamoy/mkdocstrings/commit/c6c5addd8be65beaf7055c9d0f512e0de8b3eba4) by Timothée Mazzucotelli). Related issues/PRs: [#68](https://github.com/pawamoy/mkdocstrings/issues/68)
## [0.10.0](https://github.com/pawamoy/mkdocstrings/releases/tag/0.10.0) - 2020-03-27
[Compare with 0.9.1](https://github.com/pawamoy/mkdocstrings/compare/0.9.1...0.10.0)
### Features
- Prepare for new `pytkdocs` version ([336421a](https://github.com/pawamoy/mkdocstrings/commit/336421af95d752671276c2e88c5c173bff4093cc)).
Add options `filters` and `members` to the Python collector to reflect the new `pytkdocs` options.
See [the default configuration of the Python collector](https://pawamoy.github.io/mkdocstrings/reference/handlers/python/#mkdocstrings.handlers.python.PythonCollector.DEFAULT_CONFIG).
## [0.9.1](https://github.com/pawamoy/mkdocstrings/releases/tag/0.9.1) - 2020-03-21
[Compare with 0.9.0](https://github.com/pawamoy/mkdocstrings/compare/0.9.0...0.9.1)
### Bug fixes
- Fix cross-references when deploying to GitHub pages ([36f804b](https://github.com/pawamoy/mkdocstrings/commit/36f804beab01531c0331ed89d21f3e5e15bd8585)).
## [0.9.0](https://github.com/pawamoy/mkdocstrings/releases/tag/0.9.0) - 2020-03-21
[Compare with 0.8.0](https://github.com/pawamoy/mkdocstrings/compare/0.8.0...0.9.0)
This version is a big refactor. We will just list the new features without pointing to particular commits.
The documentation rendering looks slightly different, and should be better than before.
No identified breaking changes for end-users.
### Features
- **Language agnostic:** we moved the code responsible for loading Python documentation into a new project,
[`pytkdocs`](https://github.com/pawamoy/pytkdocs), and implemented a "handlers" logic, effectively allowing to
support any given language. Waiting for your handlers contributions :wink:!
- **Multiple themes support:** handlers can offer templates for multiple `mkdocs` themes.
- **Better cross-references:** cross-references now not only work between documented objects (between all languages,
given the objects' identifiers are unique), but also for every heading of your Markdown pages.
- **Configuration options:** the rendering of Python documentation can now be configured,
(globally and locally thanks to the handlers system),
[check the docs!](https://pawamoy.github.io/mkdocstrings/reference/handlers/python/#mkdocstrings.handlers.python.PythonRenderer.DEFAULT_CONFIG)
Also see the [recommended CSS](https://pawamoy.github.io/mkdocstrings/handlers/python/#recommended-style).
- **Proper logging messages:** `mkdocstrings` now logs debug, warning and error messages, useful when troubleshooting.
### Bug fixes
- Various fixes and better error handling.
## [0.8.0](https://github.com/pawamoy/mkdocstrings/releases/tag/0.8.0) - 2020-03-04
[Compare with 0.7.2](https://github.com/pawamoy/mkdocstrings/compare/0.7.2...0.8.0)
### Breaking Changes
- Be compatible with Mkdocs >= 1.1 ([5a974a4](https://github.com/pawamoy/mkdocstrings/commit/5a974a4eb810904d6836e216d8539affc8acaa6f)).
This is a breaking change as we're not compatible with versions of Mkdocs below 1.1 anymore.
If you cannot upgrade Mkdocs to 1.1, pin mkdocstrings' version to 0.7.2.
## [0.7.2](https://github.com/pawamoy/mkdocstrings/releases/tag/0.7.2) - 2020-03-04
[Compare with 0.7.1](https://github.com/pawamoy/mkdocstrings/compare/0.7.1...0.7.2)
### Bug Fixes
- Catch `OSError` when trying to get source lines ([8e8d604](https://github.com/pawamoy/mkdocstrings/commit/8e8d604ba95363c140841c84535d2350d7ebbfe3)).
- Do not render signature empty sentinel ([16dfd73](https://github.com/pawamoy/mkdocstrings/commit/16dfd73cf30d01314dba756d3f10308b99c87dcc)).
- Fix for nested classes and their attributes ([7fef903](https://github.com/pawamoy/mkdocstrings/commit/7fef9037c5299d6106347b0db29f85a644f85c16)).
- Fix `relative_file_path` method ([52715ad](https://github.com/pawamoy/mkdocstrings/commit/52715adc59fe2e26a9e91df88bac8b8b32d4635e)).
- Wrap file path in backticks to escape it ([2525f39](https://github.com/pawamoy/mkdocstrings/commit/2525f39ad8c181679fa33db8e6dfaa28eb39c289)).
## [0.7.1](https://github.com/pawamoy/mkdocstrings/releases/tag/0.7.1) - 2020-02-18
[Compare with 0.7.0](https://github.com/pawamoy/mkdocstrings/compare/0.7.0...0.7.1)
### Bug Fixes
- Replace literal slash with os.sep for Windows compatibility ([70f9af5](https://github.com/pawamoy/mkdocstrings/commit/70f9af5e33cda694cda33870c84a770c853d84b5)).
## [0.7.0](https://github.com/pawamoy/mkdocstrings/releases/tag/0.7.0) - 2020-01-13
[Compare with 0.6.1](https://github.com/pawamoy/mkdocstrings/compare/0.6.1...0.7.0)
### Bug Fixes
- Don't mark args or kwargs as required ([4049d6f](https://github.com/pawamoy/mkdocstrings/commit/4049d6f27c332b05db06bcfe6cc564f3c1c0f013)).
- Filter submodules ([7b11095](https://github.com/pawamoy/mkdocstrings/commit/7b110959529c5fc0275fb98c6d97e7c71e205900)).
### Code Refactoring
- Don't guess lang in generated docs ([db4f60a](https://github.com/pawamoy/mkdocstrings/commit/db4f60a13dd0d191d7515683d7d42ae374b39fae)).
- Render at HTML step with custom markdown converter ([9b5a3e1](https://github.com/pawamoy/mkdocstrings/commit/9b5a3e126cd584893a8d0858ca9b67b8251e88f1)).
### Features
- Change forward ref to ref, fix optional unions ([2f0bfaa](https://github.com/pawamoy/mkdocstrings/commit/2f0bfaabf367bfa513c20fb1230409306e43add2)).
- Discover package submodules ([231062a](https://github.com/pawamoy/mkdocstrings/commit/231062a3a107abc02134f102a06693969cf603ad)).
- Implement watched source code (hacks) ([4a67953](https://github.com/pawamoy/mkdocstrings/commit/4a67953c0af9da363d52ba058b3c51cf4cbfaabe)).
## [0.6.1](https://github.com/pawamoy/mkdocstrings/releases/tag/0.6.1) - 2020-01-02
[Compare with 0.6.0](https://github.com/pawamoy/mkdocstrings/compare/0.6.0...0.6.1)
### Bug Fixes
- Break docstring discarding loop if found ([5a17fec](https://github.com/pawamoy/mkdocstrings/commit/5a17fec5beed2003d19ccdcb359b46b79bfcf469)).
- Fix discarding docstring ([143f7cb](https://github.com/pawamoy/mkdocstrings/commit/143f7cb00f02a7d3179cc5606ed7903566250227)).
- Fix getting annotation from nodes ([ecde72b](https://github.com/pawamoy/mkdocstrings/commit/ecde72bb22ccedb36aa847dd50504c63ad04498c)).
- Fix various things ([affbf06](https://github.com/pawamoy/mkdocstrings/commit/affbf064d457d4b626e8f67d38e94d7919bc2df2)).
### Code Refactoring
- Break as soon as we find the same attr in a parent class while trying to discard the docstring ([65d7908](https://github.com/pawamoy/mkdocstrings/commit/65d7908f489ec465b2803ea8f55c70d0f9d7586b)).
- Split Docstring.parse method to improve readability ([2226e2d](https://github.com/pawamoy/mkdocstrings/commit/2226e2d55a6b9bbdd5a56183f1a9ba3c5f01b5ac)).
## [0.6.0](https://github.com/pawamoy/mkdocstrings/releases/tag/0.6.0) - 2019-12-28
[Compare with 0.5.0](https://github.com/pawamoy/mkdocstrings/compare/0.5.0...0.6.0)
### Bug Fixes
- Fix GenericMeta import error on Python 3.7+ ([febf2b9](https://github.com/pawamoy/mkdocstrings/commit/febf2b9749d97cce80f5d20339372842fdffc908)).
### Code Refactoring
- More classes. Still ugly code though :'( ([f41c119](https://github.com/pawamoy/mkdocstrings/commit/f41c11988d8d849a0310cca511c2d93a74cab86f)).
- Split into more modules ([f1872a4](https://github.com/pawamoy/mkdocstrings/commit/f1872a4c8d41a0b9603b7f344de3186110a4e1bd)).
- Use Object subclasses ([40dd106](https://github.com/pawamoy/mkdocstrings/commit/40dd1062188e6ad6ef6fbc12ddead2132fe6af1e)).
## [0.5.0](https://github.com/pawamoy/mkdocstrings/releases/tag/0.5.0) - 2019-12-22
[Compare with 0.4.0](https://github.com/pawamoy/mkdocstrings/compare/0.4.0...0.5.0)
### Features
- Use divs in HTML contents to ease styling ([2a36a0e](https://github.com/pawamoy/mkdocstrings/commit/2a36a0eba7f52c43a3eba593ddd971acaa0a9c92)).
## [0.4.0](https://github.com/pawamoy/mkdocstrings/releases/tag/0.4.0) - 2019-12-22
[Compare with 0.3.0](https://github.com/pawamoy/mkdocstrings/compare/0.3.0...0.4.0)
### Features
- Parse docstrings Google-style blocks, get types from signature ([5af0c7b](https://github.com/pawamoy/mkdocstrings/commit/5af0c7b766ea7158d603b44c6df278dbcd189864)).
## [0.3.0](https://github.com/pawamoy/mkdocstrings/releases/tag/0.3.0) - 2019-12-21
[Compare with 0.2.0](https://github.com/pawamoy/mkdocstrings/compare/0.2.0...0.3.0)
### Features
- Allow object referencing in docstrings ([2dd50c0](https://github.com/pawamoy/mkdocstrings/commit/2dd50c06f96acaf0e2f969f217f0cbcfb1de2fd4)).
## [0.2.0](https://github.com/pawamoy/mkdocstrings/releases/tag/0.2.0) - 2019-12-15
[Compare with 0.1.0](https://github.com/pawamoy/mkdocstrings/compare/0.1.0...0.2.0)
### Misc
- Refactor, features, etc. ([111fa85](https://github.com/pawamoy/mkdocstrings/commit/111fa85a6305a198ac4e19a75bb491b98683929c)).
## [0.1.0](https://github.com/pawamoy/mkdocstrings/releases/tag/0.1.0) - 2019-12-12
[Compare with first commit](https://github.com/pawamoy/mkdocstrings/compare/f1dd8fb2b4a4ae81f9144fe062ca9743ae82bd69...0.1.0)
### Misc
- Clean up (delete unused files) ([c227043](https://github.com/pawamoy/mkdocstrings/commit/c227043814381b95031e426725e97106931f4ef9)).
- Clean up unused makefile rules ([edc01e9](https://github.com/pawamoy/mkdocstrings/commit/edc01e99aa7b762e800d9ae25cd5b842812dc326)).
- Initial commit ([f1dd8fb](https://github.com/pawamoy/mkdocstrings/commit/f1dd8fb2b4a4ae81f9144fe062ca9743ae82bd69)).
- Update readme ([ae56bdd](https://github.com/pawamoy/mkdocstrings/commit/ae56bdd9ac5692665409e99eb0fd509d8dfc605e)).
- Add plugin ([6ed5cb1](https://github.com/pawamoy/mkdocstrings/commit/6ed5cb1879b498ddc8d0fe1c04db7e3527f2ff81)).
- First PoC, needs better theming ([18a00b9](https://github.com/pawamoy/mkdocstrings/commit/18a00b9405a94405256a1ad2ae45886da40296e4)).
- Get attributes docstrings ([7838fff](https://github.com/pawamoy/mkdocstrings/commit/7838fffa5b1d5a481fd2ea5a94d305a96b06c321)).
- Refactor ([f68f1a8](https://github.com/pawamoy/mkdocstrings/commit/f68f1a89d477a55a6e86a9eb4c92bd5d6416b5cc)).
mkdocstrings-0.25.1/CODE_OF_CONDUCT.md 0000664 0000000 0000000 00000012550 14615711373 0017120 0 ustar 00root root 0000000 0000000 # Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
dev@pawamoy.fr.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
mkdocstrings-0.25.1/CONTRIBUTING.md 0000664 0000000 0000000 00000010236 14615711373 0016551 0 ustar 00root root 0000000 0000000 # Contributing
Contributions are welcome, and they are greatly appreciated!
Every little bit helps, and credit will always be given.
## Environment setup
Nothing easier!
Fork and clone the repository, then:
```bash
cd mkdocstrings
make setup
```
> NOTE:
> If it fails for some reason,
> you'll need to install
> [uv](https://github.com/astral-sh/uv)
> manually.
>
> You can install it with:
>
> ```bash
> python3 -m pip install --user pipx
> pipx install uv
> ```
>
> Now you can try running `make setup` again,
> or simply `uv install`.
You now have the dependencies installed.
Run `make help` to see all the available actions!
## Tasks
This project uses [duty](https://github.com/pawamoy/duty) to run tasks.
A Makefile is also provided. The Makefile will try to run certain tasks
on multiple Python versions. If for some reason you don't want to run the task
on multiple Python versions, you run the task directly with `make run duty TASK`.
The Makefile detects if a virtual environment is activated,
so `make` will work the same with the virtualenv activated or not.
If you work in VSCode, we provide
[an action to configure VSCode](https://pawamoy.github.io/copier-uv/work/#vscode-setup)
for the project.
## Development
As usual:
1. create a new branch: `git switch -c feature-or-bugfix-name`
1. edit the code and/or the documentation
**Before committing:**
1. run `make format` to auto-format the code
1. run `make check` to check everything (fix any warning)
1. run `make test` to run the tests (fix any issue)
1. if you updated the documentation or the project dependencies:
1. run `make docs`
1. go to http://localhost:8000 and check that everything looks good
1. follow our [commit message convention](#commit-message-convention)
If you are unsure about how to fix or ignore a warning,
just let the continuous integration fail,
and we will help you during the review.
Don't bother updating the changelog, we will take care of this.
## Commit message convention
Commit messages must follow our convention based on the
[Angular style](https://gist.github.com/stephenparish/9941e89d80e2bc58a153#format-of-the-commit-message)
or the [Karma convention](https://karma-runner.github.io/4.0/dev/git-commit-msg.html):
```
`
sponsors.forEach(function (sponsor) {
html += `
Print print print! Print A! Print B! With a custom title:
With the identifier as title:
full.path.object2 Please see the Hello, World! section. Check out the tips See installer.records
to learn about records. tag from around the whole output.
Returns:
An HTML string.
"""
treeprocessors = self._md.treeprocessors
treeprocessors[HeadingShiftingTreeprocessor.name].shift_by = heading_level # type: ignore[attr-defined]
treeprocessors[IdPrependingTreeprocessor.name].id_prefix = html_id and html_id + "--" # type: ignore[attr-defined]
treeprocessors[ParagraphStrippingTreeprocessor.name].strip = strip_paragraph # type: ignore[attr-defined]
try:
return Markup(self._md.convert(text))
finally:
treeprocessors[HeadingShiftingTreeprocessor.name].shift_by = 0 # type: ignore[attr-defined]
treeprocessors[IdPrependingTreeprocessor.name].id_prefix = "" # type: ignore[attr-defined]
treeprocessors[ParagraphStrippingTreeprocessor.name].strip = False # type: ignore[attr-defined]
self._md.reset()
def do_heading(
self,
content: Markup,
heading_level: int,
*,
role: str | None = None,
hidden: bool = False,
toc_label: str | None = None,
**attributes: str,
) -> Markup:
"""Render an HTML heading and register it for the table of contents. For use inside templates.
Arguments:
content: The HTML within the heading.
heading_level: The level of heading (e.g. 3 -> `h3`).
role: An optional role for the object bound to this heading.
hidden: If True, only register it for the table of contents, don't render anything.
toc_label: The title to use in the table of contents ('data-toc-label' attribute).
**attributes: Any extra HTML attributes of the heading.
Returns:
An HTML string.
"""
# First, produce the "fake" heading, for ToC only.
el = Element(f"h{heading_level}", attributes)
if toc_label is None:
toc_label = content.unescape() if isinstance(content, Markup) else content
el.set("data-toc-label", toc_label)
if role:
el.set("data-role", role)
self._headings.append(el)
if hidden:
return Markup('').format(attributes["id"])
# Now produce the actual HTML to be rendered. The goal is to wrap the HTML content into a heading.
# Start with a heading that has just attributes (no text), and add a placeholder into it.
el = Element(f"h{heading_level}", attributes)
el.append(Element("mkdocstrings-placeholder"))
# Tell the 'toc' extension to make its additions if configured so.
toc = cast(TocTreeprocessor, self._md.treeprocessors["toc"])
if toc.use_anchors:
toc.add_anchor(el, attributes["id"])
if toc.use_permalinks:
toc.add_permalink(el, attributes["id"])
# The content we received is HTML, so it can't just be inserted into the tree. We had marked the middle
# of the heading with a placeholder that can never occur (text can't directly contain angle brackets).
# Now this HTML wrapper can be "filled" by replacing the placeholder.
html_with_placeholder = tostring(el, encoding="unicode")
assert ( # noqa: S101
html_with_placeholder.count(" element around the whole output."""
name = "mkdocstrings_strip_paragraph"
strip = False
def run(self, root: Element) -> Element | None: # noqa: D102 (ignore missing docstring)
if self.strip and len(root) == 1 and root[0].tag == "p":
# Turn the single element into the root element and inherit its tag name (it's significant!)
root[0].tag = root.tag
return root[0]
return None
class MkdocstringsInnerExtension(Extension):
"""Extension that should always be added to Markdown sub-documents that handlers request (and *only* them)."""
def __init__(self, headings: list[Element]):
"""Initialize the object.
Arguments:
headings: A list that will be populated with all HTML heading elements encountered in the document.
"""
super().__init__()
self.headings = headings
def extendMarkdown(self, md: Markdown) -> None: # noqa: N802 (casing: parent method's name)
"""Register the extension.
Arguments:
md: A `markdown.Markdown` instance.
"""
md.registerExtension(self)
md.treeprocessors.register(
HeadingShiftingTreeprocessor(md, 0),
HeadingShiftingTreeprocessor.name,
priority=12,
)
md.treeprocessors.register(
IdPrependingTreeprocessor(md, ""),
IdPrependingTreeprocessor.name,
priority=4, # Right after 'toc' (needed because that extension adds ids to headers).
)
md.treeprocessors.register(
_HeadingReportingTreeprocessor(md, self.headings),
_HeadingReportingTreeprocessor.name,
priority=1, # Close to the end.
)
md.treeprocessors.register(
ParagraphStrippingTreeprocessor(md),
ParagraphStrippingTreeprocessor.name,
priority=0.99, # Close to the end.
)
mkdocstrings-0.25.1/src/mkdocstrings/inventory.py 0000664 0000000 0000000 00000011740 14615711373 0022226 0 ustar 00root root 0000000 0000000 """Module responsible for the objects inventory."""
# Credits to Brian Skinn and the sphobjinv project:
# https://github.com/bskinn/sphobjinv
from __future__ import annotations
import re
import zlib
from textwrap import dedent
from typing import BinaryIO, Collection
class InventoryItem:
"""Inventory item."""
def __init__(
self,
name: str,
domain: str,
role: str,
uri: str,
priority: int = 1,
dispname: str | None = None,
):
"""Initialize the object.
Arguments:
name: The item name.
domain: The item domain, like 'python' or 'crystal'.
role: The item role, like 'class' or 'method'.
uri: The item URI.
priority: The item priority. Only used internally by mkdocstrings and Sphinx.
dispname: The item display name.
"""
self.name: str = name
self.domain: str = domain
self.role: str = role
self.uri: str = uri
self.priority: int = priority
self.dispname: str = dispname or name
def format_sphinx(self) -> str:
"""Format this item as a Sphinx inventory line.
Returns:
A line formatted for an `objects.inv` file.
"""
dispname = self.dispname
if dispname == self.name:
dispname = "-"
uri = self.uri
if uri.endswith(self.name):
uri = uri[: -len(self.name)] + "$"
return f"{self.name} {self.domain}:{self.role} {self.priority} {uri} {dispname}"
sphinx_item_regex = re.compile(r"^(.+?)\s+(\S+):(\S+)\s+(-?\d+)\s+(\S+)\s*(.*)$")
@classmethod
def parse_sphinx(cls, line: str) -> InventoryItem:
"""Parse a line from a Sphinx v2 inventory file and return an `InventoryItem` from it."""
match = cls.sphinx_item_regex.search(line)
if not match:
raise ValueError(line)
name, domain, role, priority, uri, dispname = match.groups()
if uri.endswith("$"):
uri = uri[:-1] + name
if dispname == "-":
dispname = name
return cls(name, domain, role, uri, int(priority), dispname)
class Inventory(dict):
"""Inventory of collected and rendered objects."""
def __init__(self, items: list[InventoryItem] | None = None, project: str = "project", version: str = "0.0.0"):
"""Initialize the object.
Arguments:
items: A list of items.
project: The project name.
version: The project version.
"""
super().__init__()
items = items or []
for item in items:
self[item.name] = item
self.project = project
self.version = version
def register(
self,
name: str,
domain: str,
role: str,
uri: str,
priority: int = 1,
dispname: str | None = None,
) -> None:
"""Create and register an item.
Arguments:
name: The item name.
domain: The item domain, like 'python' or 'crystal'.
role: The item role, like 'class' or 'method'.
uri: The item URI.
priority: The item priority. Only used internally by mkdocstrings and Sphinx.
dispname: The item display name.
"""
self[name] = InventoryItem(
name=name,
domain=domain,
role=role,
uri=uri,
priority=priority,
dispname=dispname,
)
def format_sphinx(self) -> bytes:
"""Format this inventory as a Sphinx `objects.inv` file.
Returns:
The inventory as bytes.
"""
header = (
dedent(
f"""
# Sphinx inventory version 2
# Project: {self.project}
# Version: {self.version}
# The remainder of this file is compressed using zlib.
""",
)
.lstrip()
.encode("utf8")
)
lines = [
item.format_sphinx().encode("utf8")
for item in sorted(self.values(), key=lambda item: (item.domain, item.name))
]
return header + zlib.compress(b"\n".join(lines) + b"\n", 9)
@classmethod
def parse_sphinx(cls, in_file: BinaryIO, *, domain_filter: Collection[str] = ()) -> Inventory:
"""Parse a Sphinx v2 inventory file and return an `Inventory` from it.
Arguments:
in_file: The binary file-like object to read from.
domain_filter: A collection of domain values to allow (and filter out all other ones).
Returns:
An inventory containing the collected items.
"""
for _ in range(4):
in_file.readline()
lines = zlib.decompress(in_file.read()).splitlines()
items = [InventoryItem.parse_sphinx(line.decode("utf8")) for line in lines]
if domain_filter:
items = [item for item in items if item.domain in domain_filter]
return cls(items)
mkdocstrings-0.25.1/src/mkdocstrings/loggers.py 0000664 0000000 0000000 00000013217 14615711373 0021634 0 ustar 00root root 0000000 0000000 """Logging functions."""
from __future__ import annotations
import logging
from contextlib import suppress
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, MutableMapping, Sequence
try:
from jinja2 import pass_context
except ImportError: # TODO: remove once Jinja2 < 3.1 is dropped
from jinja2 import contextfunction as pass_context # type: ignore[attr-defined,no-redef]
try:
import mkdocstrings_handlers
except ImportError:
TEMPLATES_DIRS: Sequence[Path] = ()
else:
TEMPLATES_DIRS = tuple(mkdocstrings_handlers.__path__)
if TYPE_CHECKING:
from jinja2.runtime import Context
class LoggerAdapter(logging.LoggerAdapter):
"""A logger adapter to prefix messages.
This adapter also adds an additional parameter to logging methods
called `once`: if `True`, the message will only be logged once.
Examples:
In Python code:
>>> logger = get_logger("myplugin")
>>> logger.debug("This is a debug message.")
>>> logger.info("This is an info message.", once=True)
In Jinja templates (logger available in context as `log`):
```jinja
{{ log.debug("This is a debug message.") }}
{{ log.info("This is an info message.", once=True) }}
```
"""
def __init__(self, prefix: str, logger: logging.Logger):
"""Initialize the object.
Arguments:
prefix: The string to insert in front of every message.
logger: The logger instance.
"""
super().__init__(logger, {})
self.prefix = prefix
self._logged: set[tuple[LoggerAdapter, str]] = set()
def process(self, msg: str, kwargs: MutableMapping[str, Any]) -> tuple[str, Any]:
"""Process the message.
Arguments:
msg: The message:
kwargs: Remaining arguments.
Returns:
The processed message.
"""
return f"{self.prefix}: {msg}", kwargs
def log(self, level: int, msg: object, *args: object, **kwargs: object) -> None:
"""Log a message.
Arguments:
level: The logging level.
msg: The message.
*args: Additional arguments passed to parent method.
**kwargs: Additional keyword arguments passed to parent method.
"""
if kwargs.pop("once", False):
if (key := (self, str(msg))) in self._logged:
return
self._logged.add(key)
super().log(level, msg, *args, **kwargs) # type: ignore[arg-type]
class TemplateLogger:
"""A wrapper class to allow logging in templates.
The logging methods provided by this class all accept
two parameters:
- `msg`: The message to log.
- `once`: If `True`, the message will only be logged once.
Methods:
debug: Function to log a DEBUG message.
info: Function to log an INFO message.
warning: Function to log a WARNING message.
error: Function to log an ERROR message.
critical: Function to log a CRITICAL message.
"""
def __init__(self, logger: LoggerAdapter):
"""Initialize the object.
Arguments:
logger: A logger adapter.
"""
self.debug = get_template_logger_function(logger.debug)
self.info = get_template_logger_function(logger.info)
self.warning = get_template_logger_function(logger.warning)
self.error = get_template_logger_function(logger.error)
self.critical = get_template_logger_function(logger.critical)
def get_template_logger_function(logger_func: Callable) -> Callable:
"""Create a wrapper function that automatically receives the Jinja template context.
Arguments:
logger_func: The logger function to use within the wrapper.
Returns:
A function.
"""
@pass_context
def wrapper(context: Context, msg: str | None = None, **kwargs: Any) -> str:
"""Log a message.
Arguments:
context: The template context, automatically provided by Jinja.
msg: The message to log.
**kwargs: Additional arguments passed to the logger function.
Returns:
An empty string.
"""
template_path = get_template_path(context)
logger_func(f"{template_path}: {msg or 'Rendering'}", **kwargs)
return ""
return wrapper
def get_template_path(context: Context) -> str:
"""Return the path to the template currently using the given context.
Arguments:
context: The template context.
Returns:
The relative path to the template.
"""
context_name: str = str(context.name)
filename = context.environment.get_template(context_name).filename
if filename:
for template_dir in TEMPLATES_DIRS:
with suppress(ValueError):
return str(Path(filename).relative_to(template_dir))
with suppress(ValueError):
return str(Path(filename).relative_to(Path.cwd()))
return filename
return context_name
def get_logger(name: str) -> LoggerAdapter:
"""Return a pre-configured logger.
Arguments:
name: The name to use with `logging.getLogger`.
Returns:
A logger configured to work well in MkDocs.
"""
logger = logging.getLogger(f"mkdocs.plugins.{name}")
return LoggerAdapter(name.split(".", 1)[0], logger)
def get_template_logger(handler_name: str | None = None) -> TemplateLogger:
"""Return a logger usable in templates.
Parameters:
handler_name: The name of the handler.
Returns:
A template logger.
"""
handler_name = handler_name or "base"
return TemplateLogger(get_logger(f"mkdocstrings_handlers.{handler_name}.templates"))
mkdocstrings-0.25.1/src/mkdocstrings/plugin.py 0000664 0000000 0000000 00000031256 14615711373 0021473 0 ustar 00root root 0000000 0000000 """This module contains the "mkdocstrings" plugin for MkDocs.
The plugin instantiates a Markdown extension ([`MkdocstringsExtension`][mkdocstrings.extension.MkdocstringsExtension]),
and adds it to the list of Markdown extensions used by `mkdocs`
during the [`on_config` event hook](https://www.mkdocs.org/user-guide/plugins/#on_config).
Once the documentation is built, the [`on_post_build` event hook](https://www.mkdocs.org/user-guide/plugins/#on_post_build)
is triggered and calls the [`handlers.teardown()` method][mkdocstrings.handlers.base.Handlers.teardown]. This method is
used to teardown the handlers that were instantiated during documentation buildup.
Finally, when serving the documentation, it can add directories to watch
during the [`on_serve` event hook](https://www.mkdocs.org/user-guide/plugins/#on_serve).
"""
from __future__ import annotations
import datetime
import functools
import os
import sys
from concurrent import futures
from io import BytesIO
from typing import TYPE_CHECKING, Any, Callable, Iterable, List, Mapping, Tuple, TypeVar
from mkdocs.config import Config
from mkdocs.config import config_options as opt
from mkdocs.plugins import BasePlugin
from mkdocs.utils import write_file
from mkdocs_autorefs.plugin import AutorefsPlugin
from mkdocstrings._cache import download_and_cache_url, download_url_with_gz
from mkdocstrings.extension import MkdocstringsExtension
from mkdocstrings.handlers.base import BaseHandler, Handlers
from mkdocstrings.loggers import get_logger
if TYPE_CHECKING:
from jinja2.environment import Environment
from mkdocs.config.defaults import MkDocsConfig
if sys.version_info < (3, 10):
from typing_extensions import ParamSpec
else:
from typing import ParamSpec
log = get_logger(__name__)
InventoryImportType = List[Tuple[str, Mapping[str, Any]]]
InventoryLoaderType = Callable[..., Iterable[Tuple[str, str]]]
P = ParamSpec("P")
R = TypeVar("R")
def list_to_tuple(function: Callable[P, R]) -> Callable[P, R]:
"""Decorater to convert lists to tuples in the arguments."""
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
safe_args = [tuple(item) if isinstance(item, list) else item for item in args]
if kwargs:
kwargs = {key: tuple(value) if isinstance(value, list) else value for key, value in kwargs.items()} # type: ignore[assignment]
return function(*safe_args, **kwargs) # type: ignore[arg-type]
return wrapper
class PluginConfig(Config):
"""The configuration options of `mkdocstrings`, written in `mkdocs.yml`."""
handlers = opt.Type(dict, default={})
"""
Global configuration of handlers.
You can set global configuration per handler, applied everywhere,
but overridable in each "autodoc" instruction. Example:
```yaml
plugins:
- mkdocstrings:
handlers:
python:
options:
option1: true
option2: "value"
rust:
options:
option9: 2
```
"""
default_handler = opt.Type(str, default="python")
"""The default handler to use. The value is the name of the handler module. Default is "python"."""
custom_templates = opt.Optional(opt.Dir(exists=True))
"""Location of custom templates to use when rendering API objects.
Value should be the path of a directory relative to the MkDocs configuration file.
"""
enable_inventory = opt.Optional(opt.Type(bool))
"""Whether to enable object inventory creation."""
enabled = opt.Type(bool, default=True)
"""Whether to enable the plugin. Default is true. If false, *mkdocstrings* will not collect or render anything."""
class MkdocstringsPlugin(BasePlugin[PluginConfig]):
"""An `mkdocs` plugin.
This plugin defines the following event hooks:
- `on_config`
- `on_env`
- `on_post_build`
Check the [Developing Plugins](https://www.mkdocs.org/user-guide/plugins/#developing-plugins) page of `mkdocs`
for more information about its plugin system.
"""
css_filename = "assets/_mkdocstrings.css"
def __init__(self) -> None:
"""Initialize the object."""
super().__init__()
self._handlers: Handlers | None = None
@property
def handlers(self) -> Handlers:
"""Get the instance of [mkdocstrings.handlers.base.Handlers][] for this plugin/build.
Raises:
RuntimeError: If the plugin hasn't been initialized with a config.
Returns:
An instance of [mkdocstrings.handlers.base.Handlers][] (the same throughout the build).
"""
if not self._handlers:
raise RuntimeError("The plugin hasn't been initialized with a config yet")
return self._handlers
def on_config(self, config: MkDocsConfig) -> MkDocsConfig | None:
"""Instantiate our Markdown extension.
Hook for the [`on_config` event](https://www.mkdocs.org/user-guide/plugins/#on_config).
In this hook, we instantiate our [`MkdocstringsExtension`][mkdocstrings.extension.MkdocstringsExtension]
and add it to the list of Markdown extensions used by `mkdocs`.
We pass this plugin's configuration dictionary to the extension when instantiating it (it will need it
later when processing markdown to get handlers and their global configurations).
Arguments:
config: The MkDocs config object.
Returns:
The modified config.
"""
if not self.plugin_enabled:
log.debug("Plugin is not enabled. Skipping.")
return config
log.debug("Adding extension to the list")
theme_name = config.theme.name or os.path.dirname(config.theme.dirs[0])
to_import: InventoryImportType = []
for handler_name, conf in self.config.handlers.items():
for import_item in conf.pop("import", ()):
if isinstance(import_item, str):
import_item = {"url": import_item} # noqa: PLW2901
to_import.append((handler_name, import_item))
extension_config = {
"theme_name": theme_name,
"mdx": config.markdown_extensions,
"mdx_configs": config.mdx_configs,
"mkdocstrings": self.config,
"mkdocs": config,
}
self._handlers = Handlers(extension_config)
autorefs: AutorefsPlugin
try:
# If autorefs plugin is explicitly enabled, just use it.
autorefs = config.plugins["autorefs"] # type: ignore[assignment]
log.debug(f"Picked up existing autorefs instance {autorefs!r}")
except KeyError:
# Otherwise, add a limited instance of it that acts only on what's added through `register_anchor`.
autorefs = AutorefsPlugin()
autorefs.scan_toc = False
config.plugins["autorefs"] = autorefs
log.debug(f"Added a subdued autorefs instance {autorefs!r}")
# Add collector-based fallback in either case.
autorefs.get_fallback_anchor = self.handlers.get_anchors
mkdocstrings_extension = MkdocstringsExtension(extension_config, self.handlers, autorefs)
config.markdown_extensions.append(mkdocstrings_extension) # type: ignore[arg-type]
config.extra_css.insert(0, self.css_filename) # So that it has lower priority than user files.
self._inv_futures = {}
if to_import:
inv_loader = futures.ThreadPoolExecutor(4)
for handler_name, import_item in to_import:
loader = self.get_handler(handler_name).load_inventory
future = inv_loader.submit(
self._load_inventory, # type: ignore[misc]
loader,
**import_item,
)
self._inv_futures[future] = (loader, import_item)
inv_loader.shutdown(wait=False)
return config
@property
def inventory_enabled(self) -> bool:
"""Tell if the inventory is enabled or not.
Returns:
Whether the inventory is enabled.
"""
inventory_enabled = self.config.enable_inventory
if inventory_enabled is None:
inventory_enabled = any(handler.enable_inventory for handler in self.handlers.seen_handlers)
return inventory_enabled
@property
def plugin_enabled(self) -> bool:
"""Tell if the plugin is enabled or not.
Returns:
Whether the plugin is enabled.
"""
return self.config.enabled
def on_env(self, env: Environment, config: MkDocsConfig, *args: Any, **kwargs: Any) -> None: # noqa: ARG002
"""Extra actions that need to happen after all Markdown rendering and before HTML rendering.
Hook for the [`on_env` event](https://www.mkdocs.org/user-guide/plugins/#on_env).
- Write mkdocstrings' extra files into the site dir.
- Gather results from background inventory download tasks.
"""
if not self.plugin_enabled:
return
if self._handlers:
css_content = "\n".join(handler.extra_css for handler in self.handlers.seen_handlers)
write_file(css_content.encode("utf-8"), os.path.join(config.site_dir, self.css_filename))
if self.inventory_enabled:
log.debug("Creating inventory file objects.inv")
inv_contents = self.handlers.inventory.format_sphinx()
write_file(inv_contents, os.path.join(config.site_dir, "objects.inv"))
if self._inv_futures:
log.debug(f"Waiting for {len(self._inv_futures)} inventory download(s)")
futures.wait(self._inv_futures, timeout=30)
results = {}
# Reversed order so that pages from first futures take precedence:
for fut in reversed(list(self._inv_futures)):
try:
results.update(fut.result())
except Exception as error: # noqa: BLE001
loader, import_item = self._inv_futures[fut]
loader_name = loader.__func__.__qualname__
log.error(f"Couldn't load inventory {import_item} through {loader_name}: {error}") # noqa: TRY400
for page, identifier in results.items():
config.plugins["autorefs"].register_url(page, identifier) # type: ignore[attr-defined]
self._inv_futures = {}
def on_post_build(
self,
config: MkDocsConfig, # noqa: ARG002
**kwargs: Any, # noqa: ARG002
) -> None:
"""Teardown the handlers.
Hook for the [`on_post_build` event](https://www.mkdocs.org/user-guide/plugins/#on_post_build).
This hook is used to teardown all the handlers that were instantiated and cached during documentation buildup.
For example, a handler could open a subprocess in the background and keep it open
to feed it "autodoc" instructions and get back JSON data. If so, it should then close the subprocess at some point:
the proper place to do this is in the handler's `teardown` method, which is indirectly called by this hook.
Arguments:
config: The MkDocs config object.
**kwargs: Additional arguments passed by MkDocs.
"""
if not self.plugin_enabled:
return
for future in self._inv_futures:
future.cancel()
if self._handlers:
log.debug("Tearing handlers down")
self.handlers.teardown()
def get_handler(self, handler_name: str) -> BaseHandler:
"""Get a handler by its name. See [mkdocstrings.handlers.base.Handlers.get_handler][].
Arguments:
handler_name: The name of the handler.
Returns:
An instance of a subclass of [`BaseHandler`][mkdocstrings.handlers.base.BaseHandler].
"""
return self.handlers.get_handler(handler_name)
@classmethod
# lru_cache does not allow mutable arguments such lists, but that is what we load from YAML config.
@list_to_tuple
@functools.lru_cache(maxsize=None)
def _load_inventory(cls, loader: InventoryLoaderType, url: str, **kwargs: Any) -> Mapping[str, str]:
"""Download and process inventory files using a handler.
Arguments:
loader: A function returning a sequence of pairs (identifier, url).
url: The URL to download and process.
**kwargs: Extra arguments to pass to the loader.
Returns:
A mapping from identifier to absolute URL.
"""
log.debug(f"Downloading inventory from {url!r}")
content = download_and_cache_url(url, download_url_with_gz, datetime.timedelta(days=1))
result = dict(loader(BytesIO(content), url=url, **kwargs))
log.debug(f"Loaded inventory from {url!r}: {len(result)} items")
return result
mkdocstrings-0.25.1/src/mkdocstrings/py.typed 0000664 0000000 0000000 00000000000 14615711373 0021301 0 ustar 00root root 0000000 0000000 mkdocstrings-0.25.1/tests/ 0000775 0000000 0000000 00000000000 14615711373 0015460 5 ustar 00root root 0000000 0000000 mkdocstrings-0.25.1/tests/__init__.py 0000664 0000000 0000000 00000000245 14615711373 0017572 0 ustar 00root root 0000000 0000000 """Tests suite for `mkdocstrings`."""
from pathlib import Path
TESTS_DIR = Path(__file__).parent
TMP_DIR = TESTS_DIR / "tmp"
FIXTURES_DIR = TESTS_DIR / "fixtures"
mkdocstrings-0.25.1/tests/conftest.py 0000664 0000000 0000000 00000003762 14615711373 0017667 0 ustar 00root root 0000000 0000000 """Configuration for the pytest test suite."""
from __future__ import annotations
from collections import ChainMap
from typing import TYPE_CHECKING, Any, Iterator
import pytest
from markdown.core import Markdown
from mkdocs.config.defaults import MkDocsConfig
if TYPE_CHECKING:
from pathlib import Path
from mkdocs import config
from mkdocstrings.plugin import MkdocstringsPlugin
@pytest.fixture(name="mkdocs_conf")
def fixture_mkdocs_conf(request: pytest.FixtureRequest, tmp_path: Path) -> Iterator[config.Config]:
"""Yield a MkDocs configuration object."""
conf = MkDocsConfig()
while hasattr(request, "_parent_request") and hasattr(request._parent_request, "_parent_request"):
request = request._parent_request
conf_dict = {
"site_name": "foo",
"site_url": "https://example.org/",
"site_dir": str(tmp_path),
"plugins": [{"mkdocstrings": {"default_handler": "python"}}],
**getattr(request, "param", {}),
}
# Re-create it manually as a workaround for https://github.com/mkdocs/mkdocs/issues/2289
mdx_configs: dict[str, Any] = dict(ChainMap(*conf_dict.get("markdown_extensions", [])))
conf.load_dict(conf_dict)
assert conf.validate() == ([], [])
conf["mdx_configs"] = mdx_configs
conf["markdown_extensions"].insert(0, "toc") # Guaranteed to be added by MkDocs.
conf = conf["plugins"]["mkdocstrings"].on_config(conf)
conf = conf["plugins"]["autorefs"].on_config(conf)
yield conf
conf["plugins"]["mkdocstrings"].on_post_build(conf)
@pytest.fixture(name="plugin")
def fixture_plugin(mkdocs_conf: config.Config) -> MkdocstringsPlugin:
"""Return a plugin instance."""
return mkdocs_conf["plugins"]["mkdocstrings"]
@pytest.fixture(name="ext_markdown")
def fixture_ext_markdown(mkdocs_conf: MkDocsConfig) -> Markdown:
"""Return a Markdown instance with MkdocstringsExtension."""
return Markdown(extensions=mkdocs_conf["markdown_extensions"], extension_configs=mkdocs_conf["mdx_configs"])
mkdocstrings-0.25.1/tests/fixtures/ 0000775 0000000 0000000 00000000000 14615711373 0017331 5 ustar 00root root 0000000 0000000 mkdocstrings-0.25.1/tests/fixtures/__init__.py 0000664 0000000 0000000 00000000037 14615711373 0021442 0 ustar 00root root 0000000 0000000 """Some fixtures for tests."""
mkdocstrings-0.25.1/tests/fixtures/cross_reference.py 0000664 0000000 0000000 00000000044 14615711373 0023050 0 ustar 00root root 0000000 0000000 """
Link to [something.Else][].
"""
mkdocstrings-0.25.1/tests/fixtures/footnotes.py 0000664 0000000 0000000 00000000253 14615711373 0021723 0 ustar 00root root 0000000 0000000 def func_a():
"""func_a[^1].
[^1]: Footnote\x20A
"""
def func_b():
"""func_b[^x].
[^x]: Footnote\x20B
"""
def func_c():
"""func_c.
"""
mkdocstrings-0.25.1/tests/fixtures/headings.py 0000664 0000000 0000000 00000000045 14615711373 0021464 0 ustar 00root root 0000000 0000000 """
Foo
===
### Bar
###### Baz
"""
mkdocstrings-0.25.1/tests/fixtures/headings_many.py 0000664 0000000 0000000 00000000207 14615711373 0022510 0 ustar 00root root 0000000 0000000 def heading_1():
"""## Heading one"""
def heading_2():
"""### Heading two"""
def heading_3():
"""#### Heading three"""
mkdocstrings-0.25.1/tests/fixtures/html_tokens.py 0000664 0000000 0000000 00000000057 14615711373 0022234 0 ustar 00root root 0000000 0000000 def func(foo="
If you sponsor publicly, you're automatically added here with a link to
your profile and avatar to show your support for *mkdocstrings*.
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
### Goals
The following section lists all funding goals. Each goal contains a list of
features prefixed with a checkmark symbol, denoting whether a feature is
:octicons-check-circle-fill-24:{ style="color: #00e676" } already available or
:octicons-check-circle-fill-24:{ style="color: var(--md-default-fg-color--lightest)" } planned,
but not yet implemented. When the funding goal is hit,
the features are released for general availability.
```python exec="1" session="insiders" idprefix=""
for goal in goals.values():
if not goal.complete:
goal.render()
```
### Goals completed
This section lists all funding goals that were previously completed, which means
that those features were part of Insiders, but are now generally available and
can be used by all users.
```python exec="1" session="insiders" idprefix=""
for goal in goals.values():
if goal.complete:
goal.render()
```
## Frequently asked questions
### Compatibility
> We're building an open source project and want to allow outside collaborators
to use *mkdocstrings* locally without having access to Insiders.
Is this still possible?
Yes. Insiders is compatible with *mkdocstrings*. 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 insiders@pawamoy.fr.
### Terms
> Are we allowed to use Insiders under the same terms and conditions as
*mkdocstrings*?
Yes. Whether you're an individual or a company, you may use *mkdocstrings
Insiders* precisely under the same terms as *mkdocstrings*, 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.md
[private forks]: https://docs.github.com/en/github/setting-up-and-managing-your-github-user-account/removing-a-collaborator-from-a-personal-repository
mkdocstrings-0.25.1/docs/insiders/installation.md 0000664 0000000 0000000 00000015511 14615711373 0022114 0 ustar 00root root 0000000 0000000 ---
title: Getting started with Insiders
---
# Getting started with Insiders
*mkdocstrings Insiders* is a compatible drop-in replacement for *mkdocstrings*,
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 PyPI Insiders
[PyPI Insiders](https://pawamoy.github.io/pypi-insiders/)
is a tool that helps you keep up-to-date versions
of Insiders projects in the PyPI index of your choice
(self-hosted, Google registry, Artifactory, etc.).
See [how to install it](https://pawamoy.github.io/pypi-insiders/#installation)
and [how to use it](https://pawamoy.github.io/pypi-insiders/#usage).
### with pip (ssh/https)
*mkdocstrings Insiders* can be installed with `pip` [using SSH][using ssh]:
```bash
pip install git+ssh://git@github.com/pawamoy-insiders/mkdocstrings.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/mkdocstrings.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* 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/mkdocstrings/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](index.md#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/mkdocstrings
> cd mkdocstrings
>
> # 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`,
> 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 Insiders* directly from `git`:
```
git clone git@github.com:pawamoy-insiders/mkdocstrings
```
When cloning from `git`, the package must be installed:
```
pip install -e mkdocstrings
```
## Upgrading
When upgrading Insiders, you should always check the version of *mkdocstrings*
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
mkdocstrings-0.25.1/docs/js/ 0000775 0000000 0000000 00000000000 14615711373 0015662 5 ustar 00root root 0000000 0000000 mkdocstrings-0.25.1/docs/js/insiders.js 0000664 0000000 0000000 00000005473 14615711373 0020051 0 ustar 00root root 0000000 0000000 function humanReadableAmount(amount) {
const strAmount = String(amount);
if (strAmount.length >= 4) {
return `${strAmount.slice(0, strAmount.length - 3)},${strAmount.slice(-3)}`;
}
return strAmount;
}
function getJSON(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'json';
xhr.onload = function () {
var status = xhr.status;
if (status === 200) {
callback(null, xhr.response);
} else {
callback(status, xhr.response);
}
};
xhr.send();
}
function updatePremiumSponsors(dataURL, rank) {
let capRank = rank.charAt(0).toUpperCase() + rank.slice(1);
getJSON(dataURL + `/sponsors${capRank}.json`, function (err, sponsors) {
const sponsorsDiv = document.getElementById(`${rank}-sponsors`);
if (sponsors.length > 0) {
let html = '';
html += `${capRank} sponsors
`
});
html += '
`;
});
if (privateSponsors > 0) {
sponsorsElem.innerHTML += `
+${privateSponsors}
`;
}
});
});
updatePremiumSponsors(dataURL, "gold");
updatePremiumSponsors(dataURL, "silver");
updatePremiumSponsors(dataURL, "bronze");
}
mkdocstrings-0.25.1/docs/license.md 0000664 0000000 0000000 00000000044 14615711373 0017210 0 ustar 00root root 0000000 0000000 # License
```
--8<-- "LICENSE"
```
mkdocstrings-0.25.1/docs/logo.svg 0000664 0000000 0000000 00000002056 14615711373 0016732 0 ustar 00root root 0000000 0000000
mkdocstrings-0.25.1/docs/recipes.md 0000664 0000000 0000000 00000027716 14615711373 0017237 0 ustar 00root root 0000000 0000000 On this page you will find various recipes, tips and tricks
for *mkdocstrings* and more generally Markdown documentation.
## Automatic code reference pages
*mkdocstrings* allows to inject documentation for any object
into Markdown pages. But as the project grows, it quickly becomes
quite tedious to keep the autodoc instructions, or even the dedicated
Markdown files in sync with all your source files and objects.
In this recipe, we will iteratively automate the process
of generating these pages at each build of the documentation.
---
Let say you have a project called `project`.
This project has a lot of source files, or modules,
which live in the `src` folder:
```tree
repo/
src/
project/
lorem
ipsum
dolor
sit
amet
```
Without an automatic process, you will have to manually
create a Markdown page for each one of these modules,
with the corresponding autodoc instruction, for example `::: project.lorem`,
and also add entry in MkDocs' navigation option (`nav` in `mkdocs.yml`).
With a lot of modules, this is quickly getting cumbersome.
Lets fix that.
### Generate pages on-the-fly
In this recipe, we suggest to use the [mkdocs-gen-files plugin](https://github.com/oprypin/mkdocs-gen-files).
This plugin exposes utilities to generate files at build time.
These files won't be written to the docs directory: you don't have
to track and version them. They are transparently generated each
time you build your docs. This is perfect for our use-case!
Add `mkdocs-gen-files` to your project's docs dependencies,
and configure it like so:
```yaml title="mkdocs.yml"
plugins:
- search # (1)!
- gen-files:
scripts:
- scripts/gen_ref_pages.py # (2)!
- mkdocstrings
```
1. Don't forget to load the `search` plugin when redefining the `plugins` item.
2. The magic happens here, see below how it works.
mkdocs-gen-files is able to run Python scripts at build time.
The Python script that we will execute lives in a scripts folder,
and is named `gen_ref_pages.py`, like "generate code reference pages".
```tree
repo/
docs/
index.md
scripts/
gen_ref_pages.py
src/
project/
mkdocs.yml
```
```python title="scripts/gen_ref_pages.py"
"""Generate the code reference pages."""
from pathlib import Path
import mkdocs_gen_files
root = Path(__file__).parent.parent
src = root / "src" # (1)!
for path in sorted(src.rglob("*.py")): # (2)!
module_path = path.relative_to(src).with_suffix("") # (3)!
doc_path = path.relative_to(src).with_suffix(".md") # (4)!
full_doc_path = Path("reference", doc_path) # (5)!
parts = tuple(module_path.parts)
if parts[-1] == "__init__": # (6)!
parts = parts[:-1]
elif parts[-1] == "__main__":
continue
with mkdocs_gen_files.open(full_doc_path, "w") as fd: # (7)!
identifier = ".".join(parts) # (8)!
print("::: " + identifier, file=fd) # (9)!
mkdocs_gen_files.set_edit_path(full_doc_path, path.relative_to(root)) # (10)!
```
1. It's important to build a path relative to the script itself,
to make it possible to build the docs with MkDocs'
[`-f` option](https://www.mkdocs.org/user-guide/cli/#mkdocs-build).
2. Here we recursively list all `.py` files, but you can adapt the code to list
files with other extensions of course, supporting other languages than Python.
3. The module path will look like `project/lorem`.
It will be used to build the *mkdocstrings* autodoc identifier.
4. This is the partial path of the Markdown page for the module.
5. This is the full path of the Markdown page within the docs.
Here we put all reference pages into a `reference` folder.
6. This part is only relevant for Python modules. We skip `__main__` modules and
remove `__init__` from the module parts as it's implicit during imports.
7. Magic! Add the file to MkDocs pages, without actually writing it in the docs folder.
8. Build the autodoc identifier. Here we document Python modules, so the identifier
is a dot-separated path, like `project.lorem`.
9. Actually write to the magic file.
10. We can even set the `edit_uri` on the pages.
> NOTE:
> It is important to look out for correct edit page behaviour when using generated pages.
> For example, if we have `edit_uri` set to `blob/master/docs/` and the following
> file structure:
>
> ```tree
> repo/
> mkdocs.yml
> docs/
> index.md
> scripts/
> gen_ref_pages.py
> src/
> project/
> lorem.py
> ipsum.py
> dolor.py
> sit.py
> amet.py
> ```
>
> Then we will have to change our `set_edit_path` call to:
>
> ```python
> mkdocs_gen_files.set_edit_path(full_doc_path, Path("../") / path) # (1)!
> ```
>
> 1. Path can be used to traverse the structure in any way you may need, but
> remember to use relative paths!
>
> ...so that it correctly sets the edit path of (for example) `lorem.py` to
> `
Documentation for
MyClass
method_a(self)
method_b(self)
Object 1
` element directly, but rather the level gets shifted to fit the encompassing document structure. If you're curious about the implementation, check out [mkdocstrings.handlers.rendering.HeadingShiftingTreeprocessor][] and others.
### Cross-references to other projects / inventories
TIP: **New in version 0.16.**
Python developers coming from Sphinx might know about its `intersphinx` extension,
that allows to cross-reference items between several projects.
*mkdocstrings* has a similar feature.
To reference an item from another project, you must first tell *mkdocstrings*
to load the inventory it provides. Each handler will be responsible of loading
inventories specific to its language. For example, the Python handler
can load Sphinx-generated inventories (`objects.inv`).
In the following snippet, we load the inventory provided by `installer`:
```yaml title="mkdocs.yml"
plugins:
- mkdocstrings:
handlers:
python:
import:
- https://installer.readthedocs.io/en/stable/objects.inv
```
Now it is possible to cross-reference `installer`'s items. For example:
=== "Markdown"
```md
See [installer.records][] to learn about records.
```
=== "Result (HTML)"
```html
'
root = Path(__file__).parent.parent
src = root / "src"
for path in sorted(src.rglob("*.py")):
module_path = path.relative_to(src).with_suffix("")
doc_path = path.relative_to(src / "mkdocstrings").with_suffix(".md")
full_doc_path = Path("reference", doc_path)
parts = tuple(module_path.parts)
if parts[-1] == "__init__":
parts = parts[:-1]
doc_path = doc_path.with_name("index.md")
full_doc_path = full_doc_path.with_name("index.md")
elif parts[-1].startswith("_"):
continue
nav_parts = [f"{mod_symbol} {part}" for part in parts]
nav[tuple(nav_parts)] = doc_path.as_posix()
with mkdocs_gen_files.open(full_doc_path, "w") as fd:
ident = ".".join(parts)
fd.write(f"::: {ident}")
mkdocs_gen_files.set_edit_path(full_doc_path, ".." / path.relative_to(root))
with mkdocs_gen_files.open("reference/SUMMARY.md", "w") as nav_file:
nav_file.writelines(nav.build_literate_nav())
mkdocstrings-0.25.1/scripts/insiders.py 0000664 0000000 0000000 00000015320 14615711373 0020200 0 ustar 00root root 0000000 0000000 """Functions related to Insiders funding goals."""
from __future__ import annotations
import json
import logging
import os
import posixpath
from dataclasses import dataclass
from datetime import date, datetime, timedelta
from itertools import chain
from pathlib import Path
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 | None
since: date | None
project: Project | None
def url(self, rel_base: str = "..") -> str | None: # noqa: D102
if not self.ref:
return None
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 ""
feature = f"[{self.name}]({self.url(rel_base)})" if self.ref else self.name
print(f"- [{'x' if self.since else ' '}] {project}{feature}{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")
if self.features:
for feature in self.features:
feature.render(rel_base)
print("")
else:
print("There are no features in this goal for this project. ")
print(
"[See the features in this goal **for all Insiders projects.**]"
f"(https://pawamoy.github.io/insiders/#{self.amount}-{self.name.lower().replace(' ', '-')})",
)
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.get("ref"),
since=feature_data.get("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 _load_goals_from_disk(path: str, funding: int = 0) -> dict[int, Goal]:
project_dir = os.getenv("MKDOCS_CONFIG_DIR", ".")
try:
data = Path(project_dir, path).read_text()
except OSError as error:
raise RuntimeError(f"Could not load data from disk: {path}") from error
return load_goals(data, funding)
def _load_goals_from_url(source_data: tuple[str, str, str], funding: int = 0) -> dict[int, Goal]:
project_name, project_url, data_fragment = source_data
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
return load_goals(data, funding, project=Project(name=project_name, url=project_url))
def _load_goals(source: str | tuple[str, str, str], funding: int = 0) -> dict[int, Goal]:
if isinstance(source, str):
return _load_goals_from_disk(source, funding)
return _load_goals_from_url(source, funding)
def funding_goals(source: str | list[str | tuple[str, str, str]], funding: int = 0) -> dict[int, Goal]:
"""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):
return _load_goals_from_disk(source, funding)
goals = {}
for src in source:
source_goals = _load_goals(src, funding)
for amount, goal in source_goals.items():
if amount not in goals:
goals[amount] = goal
else:
goals[amount].features.extend(goal.features)
return {amount: goals[amount] for amount in sorted(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)
ongoing_goals = [goal for goal in goals.values() if not goal.complete]
unreleased_features = sorted(
(ft for ft in feature_list(ongoing_goals) if ft.since),
key=lambda ft: cast(date, ft.since),
reverse=True,
)
mkdocstrings-0.25.1/scripts/make 0000775 0000000 0000000 00000010307 14615711373 0016651 0 ustar 00root root 0000000 0000000 #!/usr/bin/env bash
set -e
export PYTHON_VERSIONS=${PYTHON_VERSIONS-3.8 3.9 3.10 3.11 3.12}
exe=""
prefix=""
# Install runtime and development dependencies,
# as well as current project in editable mode.
uv_install() {
uv pip compile pyproject.toml devdeps.txt | uv pip install -r -
if [ -z "${CI}" ]; then
uv pip install -e .
else
uv pip install "mkdocstrings @ ."
fi
}
# Setup the development environment by installing dependencies
# in multiple Python virtual environments with uv:
# one venv per Python version in `.venvs/$py`,
# and an additional default venv in `.venv`.
setup() {
if ! command -v uv &>/dev/null; then
echo "make: setup: uv must be installed, see https://github.com/astral-sh/uv" >&2
return 1
fi
if [ -n "${PYTHON_VERSIONS}" ]; then
for version in ${PYTHON_VERSIONS}; do
if [ ! -d ".venvs/${version}" ]; then
uv venv --python "${version}" ".venvs/${version}"
fi
VIRTUAL_ENV="${PWD}/.venvs/${version}" uv_install
done
fi
if [ ! -d .venv ]; then uv venv --python python; fi
uv_install
}
# Activate a Python virtual environments.
# The annoying operating system also requires
# that we set some global variables to help it find commands...
activate() {
local path
if [ -f "$1/bin/activate" ]; then
source "$1/bin/activate"
return 0
fi
if [ -f "$1/Scripts/activate.bat" ]; then
"$1/Scripts/activate.bat"
exe=".exe"
prefix="$1/Scripts/"
return 0
fi
echo "run: Cannot activate venv $1" >&2
return 1
}
# Run a command in all configured Python virtual environments.
# We handle the case when the `PYTHON_VERSIONS` environment variable
# is unset or empty, for robustness.
multirun() {
local cmd="$1"
shift
if [ -n "${PYTHON_VERSIONS}" ]; then
for version in ${PYTHON_VERSIONS}; do
(activate ".venvs/${version}" && MULTIRUN=1 "${prefix}${cmd}${exe}" "$@")
done
else
(activate .venv && "${prefix}${cmd}${exe}" "$@")
fi
}
# Run a command in the default Python virtual environment.
# We rely on `multirun`'s handling of empty `PYTHON_VERSIONS`.
singlerun() {
PYTHON_VERSIONS= multirun "$@"
}
# Record options following a command name,
# until a non-option argument is met or there are no more arguments.
# Output each option on a new line, so the parent caller can store them in an array.
# Return the number of times the parent caller must shift arguments.
options() {
local shift_count=0
for arg in "$@"; do
if [[ "${arg}" =~ ^- || "${arg}" =~ ^.+= ]]; then
echo "${arg}"
((shift_count++))
else
break
fi
done
return ${shift_count}
}
# Main function.
main() {
local cmd
while [ $# -ne 0 ]; do
cmd="$1"
shift
# Handle `run` early to simplify `case` below.
if [ "${cmd}" = "run" ]; then
singlerun "$@"
exit $?
fi
# Handle `multirun` early to simplify `case` below.
if [ "${cmd}" = "multirun" ]; then
multirun "$@"
exit $?
fi
# All commands except `run` and `multirun` can be chained on a single line.
# Some of them accept options in two formats: `-f`, `--flag` and `param=value`.
# Some of them don't, and will print warnings/errors if options were given.
opts=($(options "$@")) || shift $?
case "${cmd}" in
# The following commands require special handling.
help|"")
singlerun duty --list ;;
setup)
setup ;;
check)
multirun duty check-quality check-types check-docs
singlerun duty check-dependencies check-api
;;
# The following commands run in all venvs.
check-quality|\
check-docs|\
check-types|\
test)
multirun duty "${cmd}" "${opts[@]}" ;;
# The following commands run in the default venv only.
*)
singlerun duty "${cmd}" "${opts[@]}" ;;
esac
done
}
# Execute the main function.
main "$@"
mkdocstrings-0.25.1/src/ 0000775 0000000 0000000 00000000000 14615711373 0015105 5 ustar 00root root 0000000 0000000 mkdocstrings-0.25.1/src/mkdocstrings/ 0000775 0000000 0000000 00000000000 14615711373 0017614 5 ustar 00root root 0000000 0000000 mkdocstrings-0.25.1/src/mkdocstrings/__init__.py 0000664 0000000 0000000 00000000120 14615711373 0021716 0 ustar 00root root 0000000 0000000 """mkdocstrings package.
Automatic documentation from sources, for MkDocs.
"""
mkdocstrings-0.25.1/src/mkdocstrings/_cache.py 0000664 0000000 0000000 00000005272 14615711373 0021376 0 ustar 00root root 0000000 0000000 import datetime
import gzip
import hashlib
import os
import urllib.parse
import urllib.request
from typing import BinaryIO, Callable
import click
import platformdirs
from mkdocstrings.loggers import get_logger
log = get_logger(__name__)
def download_url_with_gz(url: str) -> bytes:
req = urllib.request.Request( # noqa: S310
url,
headers={"Accept-Encoding": "gzip", "User-Agent": "mkdocstrings/0.15.0"},
)
with urllib.request.urlopen(req) as resp: # noqa: S310
content: BinaryIO = resp
if "gzip" in resp.headers.get("content-encoding", ""):
content = gzip.GzipFile(fileobj=resp) # type: ignore[assignment]
return content.read()
# This is mostly a copy of https://github.com/mkdocs/mkdocs/blob/master/mkdocs/utils/cache.py
# In the future maybe they can be deduplicated.
def download_and_cache_url(
url: str,
download: Callable[[str], bytes],
cache_duration: datetime.timedelta,
comment: bytes = b"# ",
) -> bytes:
"""Downloads a file from the URL, stores it under ~/.cache/, and returns its content.
For tracking the age of the content, a prefix is inserted into the stored file, rather than relying on mtime.
Args:
url: URL to use.
download: Callback that will accept the URL and actually perform the download.
cache_duration: How long to consider the URL content cached.
comment: The appropriate comment prefix for this file format.
"""
directory = os.path.join(platformdirs.user_cache_dir("mkdocs"), "mkdocstrings_url_cache")
name_hash = hashlib.sha256(url.encode()).hexdigest()[:32]
path = os.path.join(directory, name_hash + os.path.splitext(url)[1])
now = int(datetime.datetime.now(datetime.timezone.utc).timestamp())
prefix = b"%s%s downloaded at timestamp " % (comment, url.encode())
# Check for cached file and try to return it
if os.path.isfile(path):
try:
with open(path, "rb") as f:
line = f.readline()
if line.startswith(prefix):
line = line[len(prefix) :]
timestamp = int(line)
if datetime.timedelta(seconds=(now - timestamp)) <= cache_duration:
log.debug(f"Using cached '{path}' for '{url}'")
return f.read()
except (OSError, ValueError) as e:
log.debug(f"{type(e).__name__}: {e}")
# Download and cache the file
log.debug(f"Downloading '{url}' to '{path}'")
content = download(url)
os.makedirs(directory, exist_ok=True)
with click.open_file(path, "wb", atomic=True) as f:
f.write(b"%s%d\n" % (prefix, now))
f.write(content)
return content
mkdocstrings-0.25.1/src/mkdocstrings/debug.py 0000664 0000000 0000000 00000005425 14615711373 0021262 0 ustar 00root root 0000000 0000000 """Debugging utilities."""
from __future__ import annotations
import os
import platform
import sys
from dataclasses import dataclass
from importlib import metadata
@dataclass
class Variable:
"""Dataclass describing an environment variable."""
name: str
"""Variable name."""
value: str
"""Variable value."""
@dataclass
class Package:
"""Dataclass describing a Python package."""
name: str
"""Package name."""
version: str
"""Package version."""
@dataclass
class Environment:
"""Dataclass to store environment information."""
interpreter_name: str
"""Python interpreter name."""
interpreter_version: str
"""Python interpreter version."""
interpreter_path: str
"""Path to Python executable."""
platform: str
"""Operating System."""
packages: list[Package]
"""Installed packages."""
variables: list[Variable]
"""Environment variables."""
def _interpreter_name_version() -> tuple[str, str]:
if hasattr(sys, "implementation"):
impl = sys.implementation.version
version = f"{impl.major}.{impl.minor}.{impl.micro}"
kind = impl.releaselevel
if kind != "final":
version += kind[0] + str(impl.serial)
return sys.implementation.name, version
return "", "0.0.0"
def get_version(dist: str = "mkdocstrings") -> str:
"""Get version of the given distribution.
Parameters:
dist: A distribution name.
Returns:
A version number.
"""
try:
return metadata.version(dist)
except metadata.PackageNotFoundError:
return "0.0.0"
def get_debug_info() -> Environment:
"""Get debug/environment information.
Returns:
Environment information.
"""
py_name, py_version = _interpreter_name_version()
packages = ["mkdocstrings"]
variables = ["PYTHONPATH", *[var for var in os.environ if var.startswith("MKDOCSTRINGS")]]
return Environment(
interpreter_name=py_name,
interpreter_version=py_version,
interpreter_path=sys.executable,
platform=platform.platform(),
variables=[Variable(var, val) for var in variables if (val := os.getenv(var))],
packages=[Package(pkg, get_version(pkg)) for pkg in packages],
)
def print_debug_info() -> None:
"""Print debug/environment information."""
info = get_debug_info()
print(f"- __System__: {info.platform}")
print(f"- __Python__: {info.interpreter_name} {info.interpreter_version} ({info.interpreter_path})")
print("- __Environment variables__:")
for var in info.variables:
print(f" - `{var.name}`: `{var.value}`")
print("- __Installed packages__:")
for pkg in info.packages:
print(f" - `{pkg.name}` v{pkg.version}")
if __name__ == "__main__":
print_debug_info()
mkdocstrings-0.25.1/src/mkdocstrings/extension.py 0000664 0000000 0000000 00000030010 14615711373 0022174 0 ustar 00root root 0000000 0000000 """This module holds the code of the Markdown extension responsible for matching "autodoc" instructions.
The extension is composed of a Markdown [block processor](https://python-markdown.github.io/extensions/api/#blockparser)
that matches indented blocks starting with a line like `::: identifier`.
For each of these blocks, it uses a [handler][mkdocstrings.handlers.base.BaseHandler] to collect documentation about
the given identifier and render it with Jinja templates.
Both the collection and rendering process can be configured by adding YAML configuration under the "autodoc"
instruction:
```yaml
::: some.identifier
handler: python
options:
option1: value1
option2:
- value2a
- value2b
option_x: etc
```
"""
from __future__ import annotations
import re
from collections import ChainMap
from typing import TYPE_CHECKING, Any, MutableSequence
from xml.etree.ElementTree import Element
import yaml
from jinja2.exceptions import TemplateNotFound
from markdown.blockprocessors import BlockProcessor
from markdown.extensions import Extension
from markdown.treeprocessors import Treeprocessor
from mkdocs.exceptions import PluginError
from mkdocstrings.handlers.base import BaseHandler, CollectionError, CollectorItem, Handlers
from mkdocstrings.loggers import get_logger
if TYPE_CHECKING:
from markdown import Markdown
from markdown.blockparser import BlockParser
from mkdocs_autorefs.plugin import AutorefsPlugin
log = get_logger(__name__)
class AutoDocProcessor(BlockProcessor):
"""Our "autodoc" Markdown block processor.
It has a [`test` method][mkdocstrings.extension.AutoDocProcessor.test] that tells if a block matches a criterion,
and a [`run` method][mkdocstrings.extension.AutoDocProcessor.run] that processes it.
It also has utility methods allowing to get handlers and their configuration easily, useful when processing
a matched block.
"""
regex = re.compile(r"^(?P
{result.text}
')
return Markup(result)
class IdPrependingTreeprocessor(Treeprocessor):
"""Prepend the configured prefix to IDs of all HTML elements."""
name = "mkdocstrings_ids"
id_prefix: str
"""The prefix to add to every ID. It is prepended without any separator; specify your own separator if needed."""
def __init__(self, md: Markdown, id_prefix: str):
"""Initialize the object.
Arguments:
md: A `markdown.Markdown` instance.
id_prefix: The prefix to add to every ID. It is prepended without any separator.
"""
super().__init__(md)
self.id_prefix = id_prefix
def run(self, root: Element) -> None: # noqa: D102 (ignore missing docstring)
if self.id_prefix:
self._prefix_ids(root)
def _prefix_ids(self, root: Element) -> None:
index = len(root)
for el in reversed(root): # Reversed mainly for the ability to mutate during iteration.
index -= 1
self._prefix_ids(el)
href_attr = el.get("href")
if id_attr := el.get("id"):
if el.tag == "a" and not href_attr:
# An anchor with id and no href is used by autorefs:
# leave it untouched and insert a copy with updated id after it.
new_el = copy.deepcopy(el)
new_el.set("id", self.id_prefix + id_attr)
root.insert(index + 1, new_el)
else:
# Anchors with id and href are not used by autorefs:
# update in place.
el.set("id", self.id_prefix + id_attr)
# Always update hrefs, names and labels-for:
# there will always be a corresponding id.
if href_attr and href_attr.startswith("#"):
el.set("href", "#" + self.id_prefix + href_attr[1:])
if name_attr := el.get("name"):
el.set("name", self.id_prefix + name_attr)
if el.tag == "label":
for_attr = el.get("for")
if for_attr:
el.set("for", self.id_prefix + for_attr)
class HeadingShiftingTreeprocessor(Treeprocessor):
"""Shift levels of all Markdown headings according to the configured base level."""
name = "mkdocstrings_headings"
regex = re.compile(r"([Hh])([1-6])")
shift_by: int
"""The number of heading "levels" to add to every heading. `` with `shift_by = 3` becomes `
`."""
def __init__(self, md: Markdown, shift_by: int):
"""Initialize the object.
Arguments:
md: A `markdown.Markdown` instance.
shift_by: The number of heading "levels" to add to every heading.
"""
super().__init__(md)
self.shift_by = shift_by
def run(self, root: Element) -> None: # noqa: D102 (ignore missing docstring)
if not self.shift_by:
return
for el in root.iter():
match = self.regex.fullmatch(el.tag)
if match:
level = int(match[2]) + self.shift_by
level = max(1, min(level, 6))
el.tag = f"{match[1]}{level}"
class _HeadingReportingTreeprocessor(Treeprocessor):
"""Records the heading elements encountered in the document."""
name = "mkdocstrings_headings_list"
regex = re.compile(r"[Hh][1-6]")
headings: list[Element]
"""The list (the one passed in the initializer) that is used to record the heading elements (by appending to it)."""
def __init__(self, md: Markdown, headings: list[Element]):
super().__init__(md)
self.headings = headings
def run(self, root: Element) -> None:
permalink_class = self.md.treeprocessors["toc"].permalink_class # type: ignore[attr-defined]
for el in root.iter():
if self.regex.fullmatch(el.tag):
el = copy.copy(el) # noqa: PLW2901
# 'toc' extension's first pass (which we require to build heading stubs/ids) also edits the HTML.
# Undo the permalink edit so we can pass this heading to the outer pass of the 'toc' extension.
if len(el) > 0 and el[-1].get("class") == permalink_class:
del el[-1]
self.headings.append(el)
class ParagraphStrippingTreeprocessor(Treeprocessor):
"""Unwraps the
HELLO
"):
"""test"""
mkdocstrings-0.25.1/tests/fixtures/markdown_anchors.py 0000664 0000000 0000000 00000000273 14615711373 0023244 0 ustar 00root root 0000000 0000000 """Module docstring.
[](){#anchor}
Paragraph.
[](){#heading-anchor-1}
[](){#heading-anchor-2}
[](){#heading-anchor-3}
## Heading
[](#has-href1)
[](#has-href2){#with-id}
Pararaph.
""" mkdocstrings-0.25.1/tests/fixtures/string_annotation.py 0000664 0000000 0000000 00000000167 14615711373 0023447 0 ustar 00root root 0000000 0000000 from typing import Literal
class Foo:
@property
def foo() -> Literal["hi"]:
"hi"
return "hi"
mkdocstrings-0.25.1/tests/test_extension.py 0000664 0000000 0000000 00000021411 14615711373 0021104 0 ustar 00root root 0000000 0000000 """Tests for the extension module."""
from __future__ import annotations
import re
import sys
from textwrap import dedent
from typing import TYPE_CHECKING
import pytest
if TYPE_CHECKING:
from markdown import Markdown
from mkdocstrings.plugin import MkdocstringsPlugin
@pytest.mark.parametrize("ext_markdown", [{"markdown_extensions": [{"footnotes": {}}]}], indirect=["ext_markdown"])
def test_multiple_footnotes(ext_markdown: Markdown) -> None:
"""Assert footnotes don't get added to subsequent docstrings."""
output = ext_markdown.convert(
dedent(
"""
Top.[^aaa]
::: tests.fixtures.footnotes.func_a
::: tests.fixtures.footnotes.func_b
::: tests.fixtures.footnotes.func_c
[^aaa]: Top footnote
""",
),
)
assert output.count("Footnote A") == 1
assert output.count("Footnote B") == 1
assert output.count("Top footnote") == 1
def test_markdown_heading_level(ext_markdown: Markdown) -> None:
"""Assert that Markdown headings' level doesn't exceed heading_level."""
output = ext_markdown.convert("::: tests.fixtures.headings\n options:\n show_root_heading: true")
assert ">Foo" in output
assert ">Bar" in output
assert ">Baz" in output
def test_keeps_preceding_text(ext_markdown: Markdown) -> None:
"""Assert that autodoc is recognized in the middle of a block and preceding text is kept."""
output = ext_markdown.convert("**preceding**\n::: tests.fixtures.headings")
assert "preceding" in output
assert ">Foo" in output
assert ":::" not in output
def test_reference_inside_autodoc(ext_markdown: Markdown) -> None:
"""Assert cross-reference Markdown extension works correctly."""
output = ext_markdown.convert("::: tests.fixtures.cross_reference")
assert re.search(r"Link to <.*something\.Else.*>something\.Else<.*>\.", output)
@pytest.mark.skipif(sys.version_info < (3, 8), reason="typing.Literal requires Python 3.8")
def test_quote_inside_annotation(ext_markdown: Markdown) -> None:
"""Assert that inline highlighting doesn't double-escape HTML."""
output = ext_markdown.convert("::: tests.fixtures.string_annotation.Foo")
assert ";hi&" in output
assert "&" not in output
def test_html_inside_heading(ext_markdown: Markdown) -> None:
"""Assert that headings don't double-escape HTML."""
output = ext_markdown.convert("::: tests.fixtures.html_tokens")
assert "'<" in output
assert "&" not in output
@pytest.mark.parametrize(
("ext_markdown", "expect_permalink"),
[
({"markdown_extensions": [{"toc": {"permalink": "@@@"}}]}, "@@@"),
({"markdown_extensions": [{"toc": {"permalink": "TeSt"}}]}, "TeSt"),
({"markdown_extensions": [{"toc": {"permalink": True}}]}, "¶"),
],
indirect=["ext_markdown"],
)
def test_no_double_toc(ext_markdown: Markdown, expect_permalink: str) -> None:
"""Assert that the 'toc' extension doesn't apply its modification twice."""
output = ext_markdown.convert(
dedent(
"""
# aa
::: tests.fixtures.headings
options:
show_root_toc_entry: false
# bb
""",
),
)
assert output.count(expect_permalink) == 5
assert 'id="tests.fixtures.headings--foo"' in output
assert ext_markdown.toc_tokens == [ # type: ignore[attr-defined] # the member gets populated only with 'toc' extension
{
"level": 1,
"id": "aa",
"html": "aa",
"name": "aa",
"data-toc-label": "",
"children": [
{
"level": 2,
"id": "tests.fixtures.headings--foo",
"html": "Foo",
"name": "Foo",
"data-toc-label": "",
"children": [
{
"level": 4,
"id": "tests.fixtures.headings--bar",
"html": "Bar",
"name": "Bar",
"data-toc-label": "",
"children": [
{
"level": 6,
"id": "tests.fixtures.headings--baz",
"html": "Baz",
"name": "Baz",
"data-toc-label": "",
"children": [],
},
],
},
],
},
],
},
{
"level": 1,
"id": "bb",
"html": "bb",
"name": "bb",
"data-toc-label": "",
"children": [],
},
]
def test_use_custom_handler(ext_markdown: Markdown) -> None:
"""Assert that we use the custom handler declared in an individual autodoc instruction."""
with pytest.raises(ModuleNotFoundError):
ext_markdown.convert("::: tests.fixtures.headings\n handler: not_here")
def test_dont_register_every_identifier_as_anchor(plugin: MkdocstringsPlugin, ext_markdown: Markdown) -> None:
"""Assert that we don't preemptively register all identifiers of a rendered object."""
handler = plugin._handlers.get_handler("python") # type: ignore[union-attr]
ids = ("id1", "id2", "id3")
handler.get_anchors = lambda _: ids # type: ignore[method-assign]
ext_markdown.convert("::: tests.fixtures.headings")
autorefs = ext_markdown.parser.blockprocessors["mkdocstrings"]._autorefs # type: ignore[attr-defined]
for identifier in ids:
assert identifier not in autorefs._url_map
assert identifier not in autorefs._abs_url_map
def test_use_options_yaml_key(ext_markdown: Markdown) -> None:
"""Check that using the 'options' YAML key works as expected."""
assert "h1" in ext_markdown.convert("::: tests.fixtures.headings\n options:\n heading_level: 1")
assert "h1" not in ext_markdown.convert("::: tests.fixtures.headings\n options:\n heading_level: 2")
def test_use_yaml_options_after_blank_line(ext_markdown: Markdown) -> None:
"""Check that YAML options are detected even after a blank line."""
assert "h1" not in ext_markdown.convert("::: tests.fixtures.headings\n\n options:\n heading_level: 2")
@pytest.mark.parametrize("ext_markdown", [{"markdown_extensions": [{"admonition": {}}]}], indirect=["ext_markdown"])
def test_removing_duplicated_headings(ext_markdown: Markdown) -> None:
"""Assert duplicated headings are removed from the output."""
output = ext_markdown.convert(
dedent(
"""
::: tests.fixtures.headings_many.heading_1
!!! note
::: tests.fixtures.headings_many.heading_2
::: tests.fixtures.headings_many.heading_3
""",
),
)
assert output.count(">Heading one<") == 1
assert output.count(">Heading two<") == 1
assert output.count(">Heading three<") == 1
assert output.count('class="mkdocstrings') == 0
def _assert_contains_in_order(items: list[str], string: str) -> None:
index = 0
for item in items:
assert item in string[index:]
index = string.index(item, index) + len(item)
@pytest.mark.parametrize("ext_markdown", [{"markdown_extensions": [{"attr_list": {}}]}], indirect=["ext_markdown"])
def test_backup_of_anchors(ext_markdown: Markdown) -> None:
"""Anchors with empty `href` are backed up."""
output = ext_markdown.convert("::: tests.fixtures.markdown_anchors")
# Anchors with id and no href have been backed up and updated.
_assert_contains_in_order(
[
'id="anchor"',
'id="tests.fixtures.markdown_anchors--anchor"',
'id="heading-anchor-1"',
'id="tests.fixtures.markdown_anchors--heading-anchor-1"',
'id="heading-anchor-2"',
'id="tests.fixtures.markdown_anchors--heading-anchor-2"',
'id="heading-anchor-3"',
'id="tests.fixtures.markdown_anchors--heading-anchor-3"',
],
output,
)
# Anchors with href and with or without id have been updated but not backed up.
_assert_contains_in_order(
[
'id="tests.fixtures.markdown_anchors--with-id"',
],
output,
)
assert 'id="with-id"' not in output
_assert_contains_in_order(
[
'href="#tests.fixtures.markdown_anchors--has-href1"',
'href="#tests.fixtures.markdown_anchors--has-href2"',
],
output,
)
assert 'href="#has-href1"' not in output
assert 'href="#has-href2"' not in output
mkdocstrings-0.25.1/tests/test_handlers.py 0000664 0000000 0000000 00000007205 14615711373 0020675 0 ustar 00root root 0000000 0000000 """Tests for the handlers.base module."""
from __future__ import annotations
from typing import TYPE_CHECKING
import pytest
from jinja2.exceptions import TemplateNotFound
from markdown import Markdown
from mkdocstrings.handlers.base import Highlighter
if TYPE_CHECKING:
from pathlib import Path
from mkdocstrings.plugin import MkdocstringsPlugin
@pytest.mark.parametrize("extension_name", ["codehilite", "pymdownx.highlight"])
def test_highlighter_without_pygments(extension_name: str) -> None:
"""Assert that it's possible to disable Pygments highlighting.
Arguments:
extension_name: The "user-chosen" Markdown extension for syntax highlighting.
"""
configs = {extension_name: {"use_pygments": False, "css_class": "hiiii"}}
md = Markdown(extensions=[extension_name], extension_configs=configs)
hl = Highlighter(md)
assert (
hl.highlight("import foo", language="python")
== '
'
)
assert (
hl.highlight("import foo", language="python", inline=True)
== f'import foo
import foo
'
)
@pytest.mark.parametrize("extension_name", [None, "codehilite", "pymdownx.highlight"])
@pytest.mark.parametrize("inline", [False, True])
def test_highlighter_basic(extension_name: str | None, inline: bool) -> None:
"""Assert that Pygments syntax highlighting works.
Arguments:
extension_name: The "user-chosen" Markdown extension for syntax highlighting.
inline: Whether the highlighting was inline.
"""
md = Markdown(extensions=[extension_name], extension_configs={extension_name: {}}) if extension_name else Markdown()
hl = Highlighter(md)
actual = hl.highlight("import foo", language="python", inline=inline)
assert "import" in actual
assert "import foo" not in actual # Highlighting has split it up.
def test_extended_templates(tmp_path: Path, plugin: MkdocstringsPlugin) -> None:
"""Test the extended templates functionality.
Parameters:
tmp_path: Temporary folder.
plugin: Instance of our plugin.
"""
handler = plugin._handlers.get_handler("python") # type: ignore[union-attr]
# monkeypatch Jinja env search path
search_paths = [
base_theme := tmp_path / "base_theme",
base_fallback_theme := tmp_path / "base_fallback_theme",
extended_theme := tmp_path / "extended_theme",
extended_fallback_theme := tmp_path / "extended_fallback_theme",
]
handler.env.loader.searchpath = search_paths # type: ignore[union-attr]
# assert "new" template is not found
with pytest.raises(expected_exception=TemplateNotFound):
handler.env.get_template("new.html")
# check precedence: base theme, base fallback theme, extended theme, extended fallback theme
# start with last one and go back up
handler.env.cache = None
extended_fallback_theme.mkdir()
extended_fallback_theme.joinpath("new.html").write_text("extended fallback new")
assert handler.env.get_template("new.html").render() == "extended fallback new"
extended_theme.mkdir()
extended_theme.joinpath("new.html").write_text("extended new")
assert handler.env.get_template("new.html").render() == "extended new"
base_fallback_theme.mkdir()
base_fallback_theme.joinpath("new.html").write_text("base fallback new")
assert handler.env.get_template("new.html").render() == "base fallback new"
base_theme.mkdir()
base_theme.joinpath("new.html").write_text("base new")
assert handler.env.get_template("new.html").render() == "base new"
mkdocstrings-0.25.1/tests/test_inventory.py 0000664 0000000 0000000 00000004737 14615711373 0021141 0 ustar 00root root 0000000 0000000 """Tests for the inventory module."""
from __future__ import annotations
import sys
from io import BytesIO
from os.path import join
from typing import TYPE_CHECKING
import pytest
from mkdocs.commands.build import build
from mkdocs.config import load_config
from mkdocstrings.inventory import Inventory, InventoryItem
if TYPE_CHECKING:
from mkdocstrings.plugin import MkdocstringsPlugin
sphinx = pytest.importorskip("sphinx.util.inventory", reason="Sphinx is not installed")
@pytest.mark.parametrize(
"our_inv",
[
Inventory(),
Inventory([InventoryItem(name="object_path", domain="py", role="obj", uri="page_url")]),
Inventory([InventoryItem(name="object_path", domain="py", role="obj", uri="page_url#object_path")]),
Inventory([InventoryItem(name="object_path", domain="py", role="obj", uri="page_url#other_anchor")]),
],
)
def test_sphinx_load_inventory_file(our_inv: Inventory) -> None:
"""Perform the 'live' inventory load test."""
buffer = BytesIO(our_inv.format_sphinx())
sphinx_inv = sphinx.InventoryFile.load(buffer, "", join)
sphinx_inv_length = sum(len(sphinx_inv[key]) for key in sphinx_inv)
assert sphinx_inv_length == len(our_inv.values())
for item in our_inv.values():
assert item.name in sphinx_inv[f"{item.domain}:{item.role}"]
@pytest.mark.skipif(sys.version_info < (3, 7), reason="using plugins that require Python 3.7")
def test_sphinx_load_mkdocstrings_inventory_file() -> None:
"""Perform the 'live' inventory load test on mkdocstrings own inventory."""
mkdocs_config = load_config()
mkdocs_config["plugins"].run_event("startup", command="build", dirty=False)
try:
build(mkdocs_config)
finally:
mkdocs_config["plugins"].run_event("shutdown")
own_inv = mkdocs_config["plugins"]["mkdocstrings"].handlers.inventory
with open("site/objects.inv", "rb") as fp:
sphinx_inv = sphinx.InventoryFile.load(fp, "", join)
sphinx_inv_length = sum(len(sphinx_inv[key]) for key in sphinx_inv)
assert sphinx_inv_length == len(own_inv.values())
for item in own_inv.values():
assert item.name in sphinx_inv[f"{item.domain}:{item.role}"]
def test_load_inventory(plugin: MkdocstringsPlugin) -> None:
"""Test the plugin inventory loading method.
Parameters:
plugin: A mkdocstrings plugin instance.
"""
plugin._load_inventory(loader=lambda *args, **kwargs: (), url="https://example.com", domains=["a", "b"]) # type: ignore[misc,arg-type]
mkdocstrings-0.25.1/tests/test_loggers.py 0000664 0000000 0000000 00000003352 14615711373 0020536 0 ustar 00root root 0000000 0000000 """Tests for the loggers module."""
from unittest.mock import MagicMock
import pytest
from mkdocstrings.loggers import get_logger, get_template_logger
@pytest.mark.parametrize(
"kwargs",
[
{},
{"once": False},
{"once": True},
],
)
def test_logger(kwargs: dict, caplog: pytest.LogCaptureFixture) -> None:
"""Test logger methods.
Parameters:
kwargs: Keyword arguments passed to the logger methods.
"""
logger = get_logger("mkdocstrings.test")
caplog.set_level(0)
for _ in range(2):
logger.debug("Debug message", **kwargs)
logger.info("Info message", **kwargs)
logger.warning("Warning message", **kwargs)
logger.error("Error message", **kwargs)
logger.critical("Critical message", **kwargs)
if kwargs.get("once", False):
assert len(caplog.records) == 5
else:
assert len(caplog.records) == 10
@pytest.mark.parametrize(
"kwargs",
[
{},
{"once": False},
{"once": True},
],
)
def test_template_logger(kwargs: dict, caplog: pytest.LogCaptureFixture) -> None:
"""Test template logger methods.
Parameters:
kwargs: Keyword arguments passed to the template logger methods.
"""
logger = get_template_logger()
mock = MagicMock()
caplog.set_level(0)
for _ in range(2):
logger.debug(mock, "Debug message", **kwargs)
logger.info(mock, "Info message", **kwargs)
logger.warning(mock, "Warning message", **kwargs)
logger.error(mock, "Error message", **kwargs)
logger.critical(mock, "Critical message", **kwargs)
if kwargs.get("once", False):
assert len(caplog.records) == 5
else:
assert len(caplog.records) == 10
mkdocstrings-0.25.1/tests/test_plugin.py 0000664 0000000 0000000 00000004451 14615711373 0020373 0 ustar 00root root 0000000 0000000 """Tests for the mkdocstrings plugin."""
from __future__ import annotations
from typing import TYPE_CHECKING
from mkdocs.commands.build import build
from mkdocs.config import load_config
from mkdocstrings.plugin import MkdocstringsPlugin
if TYPE_CHECKING:
from pathlib import Path
def test_disabling_plugin(tmp_path: Path) -> None:
"""Test disabling plugin."""
docs_dir = tmp_path / "docs"
site_dir = tmp_path / "site"
docs_dir.mkdir()
site_dir.mkdir()
docs_dir.joinpath("index.md").write_text("::: mkdocstrings")
mkdocs_config = load_config()
mkdocs_config["docs_dir"] = str(docs_dir)
mkdocs_config["site_dir"] = str(site_dir)
mkdocs_config["plugins"]["mkdocstrings"].config["enabled"] = False
mkdocs_config["plugins"].run_event("startup", command="build", dirty=False)
try:
build(mkdocs_config)
finally:
mkdocs_config["plugins"].run_event("shutdown")
# make sure the instruction was not processed
assert "::: mkdocstrings" in site_dir.joinpath("index.html").read_text()
def test_plugin_default_config(tmp_path: Path) -> None:
"""Test default config options are set for Plugin."""
config_file_path = tmp_path / "mkdocs.yml"
plugin = MkdocstringsPlugin()
errors, warnings = plugin.load_config({}, config_file_path=str(config_file_path))
assert errors == []
assert warnings == []
assert plugin.config == {
"handlers": {},
"default_handler": "python",
"custom_templates": None,
"enable_inventory": None,
"enabled": True,
}
def test_plugin_config_custom_templates(tmp_path: Path) -> None:
"""Test custom_templates option is relative to config file."""
config_file_path = tmp_path / "mkdocs.yml"
options = {"custom_templates": "docs/templates"}
template_dir = tmp_path / options["custom_templates"]
# Path must exist or config validation will fail.
template_dir.mkdir(parents=True)
plugin = MkdocstringsPlugin()
errors, warnings = plugin.load_config(options, config_file_path=str(config_file_path))
assert errors == []
assert warnings == []
assert plugin.config == {
"handlers": {},
"default_handler": "python",
"custom_templates": str(template_dir),
"enable_inventory": None,
"enabled": True,
}
mkdocstrings-0.25.1/tests/tmp/ 0000775 0000000 0000000 00000000000 14615711373 0016260 5 ustar 00root root 0000000 0000000 mkdocstrings-0.25.1/tests/tmp/.gitkeep 0000664 0000000 0000000 00000000000 14615711373 0017677 0 ustar 00root root 0000000 0000000