diff --git a/.copier-answers.yml b/.copier-answers.yml
index 8cacdedb..f7a640c0 100644
--- a/.copier-answers.yml
+++ b/.copier-answers.yml
@@ -1,5 +1,5 @@
# Changes here will be overwritten by Copier
-_commit: 0.10.6
+_commit: 0.11.2
_src_path: gh:pawamoy/copier-pdm
author_email: pawamoy@pm.me
author_fullname: Timothée Mazzucotelli
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f9abeb46..f7fd9bb8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -31,7 +31,7 @@ jobs:
python-version: "3.8"
- name: Resolving dependencies
- run: pdm lock
+ run: pdm lock -v
- name: Install dependencies
run: pdm install -G duty -G docs -G quality -G typing -G security
@@ -74,6 +74,9 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
+ - name: Resolving dependencies
+ run: pdm lock -v
+
- name: Install dependencies
run: pdm install --no-editable -G duty -G tests -G docs
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 28ea412e..3f6dd2bb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,26 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
+## [0.9.0](https://github.com/mkdocstrings/python/releases/tag/0.9.0) - 2023-04-03
+
+[Compare with 0.8.3](https://github.com/mkdocstrings/python/compare/0.8.3...0.9.0)
+
+### Features
+
+- Allow resolving alias to external modules ([02052e2](https://github.com/mkdocstrings/python/commit/02052e248b125a113ab788faa9a075adbdc92ca6) by Gilad). [PR #61](https://github.com/mkdocstrings/python/pull/61), [Follow-up of PR #60](https://github.com/mkdocstrings/python/pull/60)
+- Allow pre-loading modules ([36002cb](https://github.com/mkdocstrings/python/commit/36002cb9c89fba35d23afb07a866dd8c6877f742) by Gilad). [Issue mkdocstrings/mkdocstrings#503](https://github.com/mkdocstrings/mkdocstrings/issues/503), [PR #60](https://github.com/mkdocstrings/python/pull/60)
+- Add show options for docstrings ([a6c55fb](https://github.com/mkdocstrings/python/commit/a6c55fb52f362dd49b1a7e334a631f6ea3b1b963) by Jeremy Goh). [Issue mkdocstrings/mkdocstrings#466](https://github.com/mkdocstrings/mkdocstrings/issues/466), [PR #56](https://github.com/mkdocstrings/python/pull/56)
+- Allow custom list of domains for inventories ([f5ea6fd](https://github.com/mkdocstrings/python/commit/f5ea6fd81f7a531e8a97bb0e48267188d72936c1) by Sorin Sbarnea). [Issue mkdocstrings/mkdocstrings#510](https://github.com/mkdocstrings/mkdocstrings/issues/510), [PR #49](https://github.com/mkdocstrings/python/pull/49)
+
+### Bug Fixes
+
+- Prevent alias resolution error when searching for anchors ([a190e2c](https://github.com/mkdocstrings/python/commit/a190e2c4a752e74a05ad03702837a0914c198742) by Timothée Mazzucotelli). [Issue #64](https://github.com/mkdocstrings/python/issues/64)
+
+### Code Refactoring
+
+- Support Griffe 0.26 ([075735c](https://github.com/mkdocstrings/python/commit/075735ce8d86921fbf092d7ad1d009bbb3a2e0bb) by Timothée Mazzucotelli).
+- Log (debug) unresolved aliases ([9164742](https://github.com/mkdocstrings/python/commit/9164742f87362e8241dea11bec0fd96f6b9d9dda) by Timothée Mazzucotelli).
+
## [0.8.3](https://github.com/mkdocstrings/python/releases/tag/0.8.3) - 2023-01-04
[Compare with 0.8.2](https://github.com/mkdocstrings/python/compare/0.8.2...0.8.3)
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 35f1f538..fe3eefbf 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -2,73 +2,132 @@
## Our Pledge
-In the interest of fostering an open and welcoming environment, we as
-contributors and maintainers pledge to making participation in our project and
-our community a harassment-free experience for everyone, regardless of age, body
-size, disability, ethnicity, gender identity and expression, level of experience,
-nationality, personal appearance, race, religion, or sexual identity and
-orientation.
+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 creating a positive environment
-include:
+Examples of behavior that contributes to a positive environment for our
+community include:
-* Using welcoming and inclusive language
-* Being respectful of differing viewpoints and experiences
-* Gracefully accepting constructive criticism
-* Focusing on what is best for the community
-* Showing empathy towards other community members
+* 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 by participants include:
+Examples of unacceptable behavior include:
-* The use of sexualized language or imagery and unwelcome sexual attention or
-advances
-* Trolling, insulting/derogatory comments, and personal or political attacks
+* 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 electronic
- address, without explicit permission
+* 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
-## Our Responsibilities
+## Enforcement Responsibilities
-Project maintainers are responsible for clarifying the standards of acceptable
-behavior and are expected to take appropriate and fair corrective action in
-response to any instances of unacceptable behavior.
+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.
-Project maintainers 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, or to ban temporarily or
-permanently any contributor for other behaviors 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 both within project spaces and in public spaces
-when an individual is representing the project or its community. Examples of
-representing a project or community include using an official project e-mail
-address, posting via an official social media account, or acting as an appointed
-representative at an online or offline event. Representation of a project may be
-further defined and clarified by project maintainers.
+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 by contacting the project team at pawamoy@pm.me. All
-complaints will be reviewed and investigated and will result in a response that
-is deemed necessary and appropriate to the circumstances. The project team is
-obligated to maintain confidentiality with regard to the reporter of an incident.
-Further details of specific enforcement policies may be posted separately.
+reported to the community leaders responsible for enforcement at
+pawamoy@pm.me.
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series of
+actions.
-Project maintainers who do not follow or enforce the Code of Conduct in good
-faith may face temporary or permanent repercussions as determined by other
-members of the project's leadership.
+**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 1.4,
-available at [http://contributor-covenant.org/version/1/4][version]
+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
-[homepage]: http://contributor-covenant.org
-[version]: http://contributor-covenant.org/version/1/4/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ba0c5d2b..488292a7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -19,14 +19,14 @@ make setup
> you'll need to install
> [PDM](https://github.com/pdm-project/pdm)
> manually.
->
+>
> You can install it with:
->
+>
> ```bash
> python3 -m pip install --user pipx
> pipx install pdm
> ```
->
+>
> Now you can try running `make setup` again,
> or simply `pdm install`.
@@ -75,8 +75,9 @@ Don't bother updating the changelog, we will take care of this.
## Commit message convention
-Commits messages must follow the
-[Angular style](https://gist.github.com/stephenparish/9941e89d80e2bc58a153#format-of-the-commit-message):
+Commit messages must follow our convention based on the
+[Angular style](https://gist.github.com/stephenparish/9941e89d80e2bc58a153#format-of-the-commit-message)
+or the [Karma convention](https://karma-runner.github.io/4.0/dev/git-commit-msg.html):
```
[(scope)]: Subject
@@ -84,34 +85,52 @@ Commits messages must follow the
[Body]
```
+**Subject and body must be valid Markdown.**
+Subject must have proper casing (uppercase for first letter
+if it makes sense), but no dot at the end, and no punctuation
+in general.
+
Scope and body are optional. Type can be:
- `build`: About packaging, building wheels, etc.
- `chore`: About packaging or repo/files management.
- `ci`: About Continuous Integration.
+- `deps`: Dependencies update.
- `docs`: About documentation.
- `feat`: New feature.
- `fix`: Bug fix.
- `perf`: About performance.
-- `refactor`: Changes which are not features nor bug fixes.
+- `refactor`: Changes that are not features or bug fixes.
- `style`: A change in code style/format.
- `tests`: About tests.
-**Subject (and body) must be valid Markdown.**
-If you write a body, please add issues references at the end:
+If you write a body, please add trailers at the end
+(for example issues and PR references, or co-authors),
+without relying on GitHub's flavored Markdown:
```
Body.
-References: #10, #11.
-Fixes #15.
+Issue #10: https://github.com/namespace/project/issues/10
+Related to PR namespace/other-project#15: https://github.com/namespace/other-project/pull/15
```
+These "trailers" must appear at the end of the body,
+without any blank lines between them. The trailer title
+can contain any character except colons `:`.
+We expect a full URI for each trailer, not just GitHub autolinks
+(for example, full GitHub URLs for commits and issues,
+not the hash or the #issue-number).
+
+We do not enforce a line length on commit messages summary and body,
+but please avoid very long summaries, and very long lines in the body,
+unless they are part of code blocks that must not be wrapped.
+
## Pull requests guidelines
Link to any related issue in the Pull Request message.
-During review, we recommend using fixups:
+During the review, we recommend using fixups:
```bash
# SHA is the SHA of the commit you want to fix
diff --git a/Makefile b/Makefile
index 58291575..b034ffff 100644
--- a/Makefile
+++ b/Makefile
@@ -41,7 +41,7 @@ setup:
.PHONY: check
check:
- @bash scripts/multirun.sh duty check-quality check-types check-docs
+ @pdm multirun duty check-quality check-types check-docs
@$(DUTY) check-dependencies
.PHONY: $(BASIC_DUTIES)
@@ -50,4 +50,4 @@ $(BASIC_DUTIES):
.PHONY: $(QUALITY_DUTIES)
$(QUALITY_DUTIES):
- @bash scripts/multirun.sh duty $@ $(call args,$@)
+ @pdm multirun duty $@ $(call args,$@)
diff --git a/README.md b/README.md
index f446ee8a..b59516ef 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ dependencies = [
]
```
-You can also explicitely depend on the handler:
+You can also explicitly depend on the handler:
```toml title="pyproject.toml"
# PEP 621 dependencies declaration
@@ -59,11 +59,11 @@ dependencies = [
[Griffe](https://github.com/mkdocstrings/griffe).
- **Support for type annotations:** Griffe collects your type annotations and *mkdocstrings* uses them
- to display parameters types or return types. It is even able to automatically add cross-references
- to other objects from your API, from the standard library or from third-party libraries!
+ to display parameter types or return types. It is even able to automatically add cross-references
+ to other objects from your API, from the standard library or third-party libraries!
See [how to load inventories](https://mkdocstrings.github.io/usage/#cross-references-to-other-projects-inventories) to enable it.
-- **Recursive documentation of Python objects:** just use the module dotted-path as identifier, and you get the full
+- **Recursive documentation of Python objects:** just use the module dotted-path as an identifier, and you get the full
module docs. You don't need to inject documentation for each class, function, etc.
- **Support for documented attributes:** attributes (variables) followed by a docstring (triple-quoted string) will
@@ -77,7 +77,7 @@ dependencies = [
*We do not support nested admonitions in docstrings!*
- **Every object has a TOC entry:** we render a heading for each object, meaning *MkDocs* picks them into the Table
- of Contents, which is nicely display by the Material theme. Thanks to *mkdocstrings* cross-reference ability,
+ of Contents, which is nicely displayed by the Material theme. Thanks to *mkdocstrings* cross-reference ability,
you can reference other objects within your docstrings, with the classic Markdown syntax:
`[this object][package.module.object]` or directly with `[package.module.object][]`
diff --git a/config/black.toml b/config/black.toml
new file mode 100644
index 00000000..d24affe5
--- /dev/null
+++ b/config/black.toml
@@ -0,0 +1,3 @@
+[tool.black]
+line-length = 120
+exclude = "tests/fixtures"
diff --git a/config/coverage.ini b/config/coverage.ini
index 418d1bb9..19b34d9b 100644
--- a/config/coverage.ini
+++ b/config/coverage.ini
@@ -11,11 +11,15 @@ equivalent =
__pypackages__/
[coverage:report]
+include_namespace_packages = true
precision = 2
omit =
src/*/__init__.py
src/*/__main__.py
tests/__init__.py
+exclude_lines =
+ pragma: no cover
+ if TYPE_CHECKING
[coverage:json]
output = htmlcov/coverage.json
diff --git a/config/flake8.ini b/config/flake8.ini
deleted file mode 100644
index 4126bd51..00000000
--- a/config/flake8.ini
+++ /dev/null
@@ -1,108 +0,0 @@
-[flake8]
-exclude = fixtures,site
-max-line-length = 132
-docstring-convention = google
-ban-relative-imports = true
-ignore =
- # redundant with W0622 (builtin override), which is more precise about line number
- A001
- # missing docstring in magic method
- D105
- # multi-line docstring summary should start at the first line
- D212
- # does not support Parameters sections
- D417
- # whitespace before ':' (incompatible with Black)
- E203
- # redundant with E0602 (undefined variable)
- F821
- # black already deals with quoting
- Q000
- # use of assert
- S101
- # we are not parsing XML
- S405
- # line break before binary operator (incompatible with Black)
- W503
- # two-lowercase-letters variable DO conform to snake_case naming style
- C0103
- # redundant with D102 (missing docstring)
- C0116
- # line too long
- C0301
- # too many instance attributes
- R0902
- # too few public methods
- R0903
- # too many public methods
- R0904
- # too many branches
- R0912
- # too many methods
- R0913
- # too many local variables
- R0914
- # too many statements
- R0915
- # redundant with F401 (unused import)
- W0611
- # lazy formatting for logging calls
- W1203
- # short name
- VNE001
- # f-strings
- WPS305
- # common variable names (too annoying)
- WPS110
- # redundant with W0622 (builtin override), which is more precise about line number
- WPS125
- # too many imports
- WPS201
- # too many module members
- WPS202
- # overused expression
- WPS204
- # too many local variables
- WPS210
- # too many arguments
- WPS211
- # too many expressions
- WPS213
- # too many methods
- WPS214
- # too deep nesting
- WPS220
- # high Jones complexity
- WPS221
- # too many elif branches
- WPS223
- # string over-use: can't disable it per file?
- WPS226
- # too many public instance attributes
- WPS230
- # too complex f-string
- WPS237
- # too cumbersome, asks to write class A(object)
- WPS306
- # multi-line parameters (incompatible with Black)
- WPS317
- # multi-line strings (incompatible with attributes docstrings)
- WPS322
- # implicit string concatenation
- WPS326
- # explicit string concatenation
- WPS336
- # noqa overuse
- WPS402
- # __init__ modules with logic
- WPS412
- # print statements
- WPS421
- # statement with no effect (not compatible with attribute docstrings)
- WPS428
- # redundant with C0415 (not top-level import)
- WPS433
- # multiline attribute docstring
- WPS462
- # implicit dict.get usage (generally false-positive)
- WPS529
diff --git a/config/pytest.ini b/config/pytest.ini
index ad72bbe6..5a493959 100644
--- a/config/pytest.ini
+++ b/config/pytest.ini
@@ -14,3 +14,9 @@ addopts =
--cov-config config/coverage.ini
testpaths =
tests
+
+# action:message_regex:warning_class:module_regex:line
+filterwarnings =
+ error
+ # TODO: remove once pytest-xdist 4 is released
+ ignore:.*rsyncdir:DeprecationWarning:xdist
diff --git a/config/ruff.toml b/config/ruff.toml
new file mode 100644
index 00000000..55bab1a8
--- /dev/null
+++ b/config/ruff.toml
@@ -0,0 +1,100 @@
+target-version = "py37"
+line-length = 132
+exclude = [
+ "fixtures",
+ "site",
+]
+select = [
+ "A",
+ "ANN",
+ "ARG",
+ "B",
+ "BLE",
+ "C",
+ "C4",
+ "COM",
+ "D",
+ "DTZ",
+ "E",
+ "ERA",
+ "EXE",
+ "F",
+ "FBT",
+ "G",
+ "I",
+ "ICN",
+ "INP",
+ "ISC",
+ "N",
+ "PGH",
+ "PIE",
+ "PL",
+ "PLC",
+ "PLE",
+ "PLR",
+ "PLW",
+ "PT",
+ "PYI",
+ "Q",
+ "RUF",
+ "RSE",
+ "RET",
+ "S",
+ "SIM",
+ "SLF",
+ "T",
+ "T10",
+ "T20",
+ "TCH",
+ "TID",
+ "TRY",
+ "UP",
+ "W",
+ "YTT",
+]
+ignore = [
+ "A001", # Variable is shadowing a Python builtin
+ "ANN101", # Missing type annotation for self
+ "ANN102", # Missing type annotation for cls
+ "ANN204", # Missing return type annotation for special method __str__
+ "ANN401", # Dynamically typed expressions (typing.Any) are disallowed
+ "ARG005", # Unused lambda argument
+ "C901", # Too complex
+ "D105", # Missing docstring in magic method
+ "D417", # Missing argument description in the docstring
+ "E501", # Line too long
+ "G004", # Logging statement uses f-string
+ "PLR0911", # Too many return statements
+ "PLR0912", # Too many branches
+ "PLR0913", # Too many arguments to function call
+ "PLR0915", # Too many statements
+ "SLF001", # Private member accessed
+ "TRY003", # Avoid specifying long messages outside the exception class
+]
+
+[per-file-ignores]
+"src/*/cli.py" = [
+ "T201", # Print statement
+]
+"scripts/*.py" = [
+ "INP001", # File is part of an implicit namespace package
+ "T201", # Print statement
+]
+"tests/*.py" = [
+ "ARG005", # Unused lambda argument
+ "FBT001", # Boolean positional arg in function definition
+ "PLR2004", # Magic value used in comparison
+ "S101", # Use of assert detected
+]
+
+[flake8-quotes]
+docstring-quotes = "double"
+
+[flake8-tidy-imports]
+ban-relative-imports = "all"
+
+[isort]
+known-first-party = ["mkdocstrings_handlers"]
+
+[pydocstyle]
+convention = "google"
diff --git a/docs/credits.md b/docs/credits.md
index 02e1dd81..9db45873 100644
--- a/docs/credits.md
+++ b/docs/credits.md
@@ -1,3 +1,8 @@
+---
+hide:
+- toc
+---
+
```python exec="yes"
--8<-- "scripts/gen_credits.py"
```
diff --git a/docs/customization.md b/docs/customization.md
index d1d02cca..5e729675 100644
--- a/docs/customization.md
+++ b/docs/customization.md
@@ -124,7 +124,7 @@ See them [in the repository](https://github.com/mkdocstrings/python/tree/master/
See the general *mkdocstrings* documentation to learn how to override them: https://mkdocstrings.github.io/theming/#templates.
In preparation for Jinja2 blocks, which will improve customization,
-each one of these templates extends in fact a base version in `theme/_base`. Example:
+each one of these templates extends a base version in `theme/_base`. Example:
```html+jinja title="theme/docstring/admonition.html"
{% extends "_base/docstring/admonition.html" %}
@@ -139,7 +139,7 @@ each one of these templates extends in fact a base version in `theme/_base`. Exa
```
It means you will be able to customize only *parts* of a template
-without having to fully copy-paste it in your project:
+without having to fully copy-paste it into your project:
```jinja title="templates/theme/docstring.html"
{% extends "_base/docstring.html" %}
diff --git a/docs/schema.json b/docs/schema.json
index a68b9041..2d2a29f7 100644
--- a/docs/schema.json
+++ b/docs/schema.json
@@ -26,6 +26,14 @@
"base_url": {
"title": "Base URL used to build references URLs.",
"type": "string"
+ },
+ "domains": {
+ "title": "Domains to import from the inventory.",
+ "description": "If not defined it will only import 'py' domain.",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
}
}
}
@@ -41,6 +49,12 @@
"format": "path"
}
},
+ "load_external_modules": {
+ "title": "Load external modules to resolve aliases.",
+ "markdownDescription": "https://mkdocstrings.github.io/python/usage/#global-only-options",
+ "type": "boolean",
+ "default": false
+ },
"options": {
"title": "Options for collecting and rendering objects.",
"markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options",
@@ -132,8 +146,68 @@
"type": "boolean",
"default": false
},
+ "show_docstring_attributes": {
+ "title": "Whether to display the \"Attributes\" section in the object's docstring.",
+ "markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options",
+ "type": "boolean",
+ "default": true
+ },
+ "show_docstring_description": {
+ "title": "Whether to display the textual block (including admonitions) in the object's docstring.",
+ "markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options",
+ "type": "boolean",
+ "default": true
+ },
+ "show_docstring_examples": {
+ "title": "Whether to display the \"Examples\" section in the object's docstring.",
+ "markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options",
+ "type": "boolean",
+ "default": true
+ },
+ "show_docstring_other_parameters": {
+ "title": "Whether to display the \"Other Parameters\" section in the object's docstring.",
+ "markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options",
+ "type": "boolean",
+ "default": true
+ },
+ "show_docstring_parameters": {
+ "title": "Whether to display the \"Parameters\" section in the object's docstring.",
+ "markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options",
+ "type": "boolean",
+ "default": true
+ },
+ "show_docstring_raises": {
+ "title": "Whether to display the \"Raises\" section in the object's docstring.",
+ "markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options",
+ "type": "boolean",
+ "default": true
+ },
+ "show_docstring_receives": {
+ "title": "Whether to display the \"Receives\" section in the object's docstring.",
+ "markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options",
+ "type": "boolean",
+ "default": true
+ },
+ "show_docstring_returns": {
+ "title": "Whether to display the \"Returns\" section in the object's docstring.",
+ "markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options",
+ "type": "boolean",
+ "default": true
+ },
+ "show_docstring_warns": {
+ "title": "Whether to display the \"Warns\" section in the object's docstring.",
+ "markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options",
+ "type": "boolean",
+ "default": true
+ },
+ "show_docstring_yields": {
+ "title": "Whether to display the \"Yields\" section in the object's docstring.",
+ "markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options",
+ "type": "boolean",
+ "default": true
+ },
"show_source": {
- "title": "Show the source code of this object..",
+ "title": "Show the source code of this object.",
"markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options",
"type": "boolean",
"default": true
@@ -194,6 +268,14 @@
"markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options",
"enum": ["brief", "source"],
"default": "brief"
+ },
+ "preload_modules": {
+ "title": "Pre-load modules. It permits to resolve aliases pointing to these modules (packages), and therefore render members of an object that are external to the given object (originating from another package).",
+ "markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options",
+ "type": "array",
+ "items": {
+ "type":"string"
+ }
}
},
"additionalProperties": false
@@ -203,4 +285,4 @@
}
},
"additionalProperties": false
-}
\ No newline at end of file
+}
diff --git a/docs/usage.md b/docs/usage.md
index de28ca16..332a72ad 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -52,6 +52,15 @@ Some options are **global only**, and go directly under the handler's name.
More details at [Finding modules](#finding-modules).
+- `load_external_modules`:
+ this option allows resolving aliases to any external module.
+ Enabling this option will tell handler that when it encounters an import that is made public
+ through the `__all__` variable, it will resolve it recursively to *any* module.
+ **Use with caution:** this can load a *lot* of modules, slowing down your build
+ or triggering errors that we do not yet handle.
+ **We recommend using the `preload_modules` option instead**,
+ which acts as an include-list rather than as include-all.
+
## Global/local options
The other options can be used both globally *and* locally, under the `options` key.
@@ -74,7 +83,7 @@ plugins:
do_something: false
```
-These options affect how the documentation is collected from sources and renderered:
+These options affect how the documentation is collected from sources and rendered:
headings, members, docstrings, etc.
::: mkdocstrings_handlers.python.handler.PythonHandler.default_config
@@ -202,7 +211,7 @@ TIP: **This is the recommended method.**
```
Except for case 1, which is supported by default, **we strongly recommend
-to set the path to your packages using this option, even if it works without it**
+setting the path to your packages using this option, even if it works without it**
(for example because your project manager automatically adds `src` to PYTHONPATH),
to make sure anyone can build your docs from any location on their filesystem.
@@ -211,7 +220,7 @@ to make sure anyone can build your docs from any location on their filesystem.
WARNING: **This method has limitations.**
This method might work for you, with your current setup,
but not for others trying your build your docs with their own setup/environment.
-We recommend to use the [`paths` method](#using-the-paths-option) instead.
+We recommend using the [`paths` method](#using-the-paths-option) instead.
You can take advantage of the usual Python loading mechanisms.
In Bash and other shells, you can run your command like this
@@ -270,7 +279,7 @@ In Bash and other shells, you can run your command like this
WARNING: **This method has limitations.**
This method might work for you, with your current setup,
but not for others trying your build your docs with their own setup/environment.
-We recommend to use the [`paths` method](#using-the-paths-option) instead.
+We recommend using the [`paths` method](#using-the-paths-option) instead.
Install your package in the current environment, and run MkDocs:
diff --git a/duties.py b/duties.py
index 1b64bcda..51cef860 100644
--- a/duties.py
+++ b/duties.py
@@ -1,153 +1,90 @@
"""Development tasks."""
-import importlib
+from __future__ import annotations
+
import os
-import re
import sys
-from io import StringIO
from pathlib import Path
-from typing import List, Optional, Pattern
-from urllib.request import urlopen
+from typing import TYPE_CHECKING
from duty import duty
+from duty.callables import black, blacken_docs, coverage, lazy, mkdocs, mypy, pytest, ruff, safety
+
+if TYPE_CHECKING:
+ from duty.context import Context
-PY_SRC_PATHS = (Path(_) for _ in ("src", "tests", "duties.py", "docs"))
+PY_SRC_PATHS = (Path(_) for _ in ("src", "tests", "duties.py", "scripts"))
PY_SRC_LIST = tuple(str(_) for _ in PY_SRC_PATHS)
PY_SRC = " ".join(PY_SRC_LIST)
TESTING = os.environ.get("TESTING", "0") in {"1", "true"}
CI = os.environ.get("CI", "0") in {"1", "true", "yes", ""}
WINDOWS = os.name == "nt"
PTY = not WINDOWS and not CI
+MULTIRUN = os.environ.get("PDM_MULTIRUN", "0") == "1"
-def _latest(lines: List[str], regex: Pattern) -> Optional[str]:
- for line in lines:
- match = regex.search(line)
- if match:
- return match.groupdict()["version"]
- return None
-
-
-def _unreleased(versions, last_release):
- for index, version in enumerate(versions):
- if version.tag == last_release:
- return versions[:index]
- return versions
-
-
-def update_changelog(
- inplace_file: str,
- marker: str,
- version_regex: str,
- template_url: str,
-) -> None:
- """
- Update the given changelog file in place.
-
- Arguments:
- inplace_file: The file to update in-place.
- marker: The line after which to insert new contents.
- version_regex: A regular expression to find currently documented versions in the file.
- template_url: The URL to the Jinja template used to render contents.
- """
- from git_changelog.build import Changelog
- from git_changelog.commit import AngularStyle
- from jinja2.sandbox import SandboxedEnvironment
-
- AngularStyle.DEFAULT_RENDER.insert(0, AngularStyle.TYPES["build"])
- env = SandboxedEnvironment(autoescape=False)
- template_text = urlopen(template_url).read().decode("utf8") # noqa: S310
- template = env.from_string(template_text)
- changelog = Changelog(".", style="angular")
-
- if len(changelog.versions_list) == 1:
- last_version = changelog.versions_list[0]
- if last_version.planned_tag is None:
- planned_tag = "0.1.0"
- last_version.tag = planned_tag
- last_version.url += planned_tag
- last_version.compare_url = last_version.compare_url.replace("HEAD", planned_tag)
-
- with open(inplace_file, "r") as changelog_file:
- lines = changelog_file.read().splitlines()
-
- last_released = _latest(lines, re.compile(version_regex))
- if last_released:
- changelog.versions_list = _unreleased(changelog.versions_list, last_released)
- rendered = template.render(changelog=changelog, inplace=True)
- lines[lines.index(marker)] = rendered
-
- with open(inplace_file, "w") as changelog_file: # noqa: WPS440
- changelog_file.write("\n".join(lines).rstrip("\n") + "\n")
+def pyprefix(title: str) -> str: # noqa: D103
+ if MULTIRUN:
+ prefix = f"(python{sys.version_info.major}.{sys.version_info.minor})"
+ return f"{prefix:14}{title}"
+ return title
@duty
-def changelog(ctx):
- """
- Update the changelog in-place with latest commits.
+def changelog(ctx: Context) -> None:
+ """Update the changelog in-place with latest commits.
- Arguments:
+ Parameters:
ctx: The context instance (passed automatically).
"""
- commit = "166758a98d5e544aaa94fda698128e00733497f4"
- template_url = f"https://raw.githubusercontent.com/pawamoy/jinja-templates/{commit}/keepachangelog.md"
+ from git_changelog.cli import build_and_render
+
+ git_changelog = lazy("git_changelog")(build_and_render)
ctx.run(
- update_changelog,
- kwargs={
- "inplace_file": "CHANGELOG.md",
- "marker": "",
- "version_regex": r"^## \[v?(?P[^\]]+)",
- "template_url": template_url,
- },
+ git_changelog(
+ repository=".",
+ output="CHANGELOG.md",
+ convention="angular",
+ template="keepachangelog",
+ parse_trailers=True,
+ parse_refs=False,
+ sections=("build", "deps", "feat", "fix", "refactor"),
+ bump_latest=True,
+ in_place=True,
+ ),
title="Updating changelog",
- pty=PTY,
)
@duty(pre=["check_quality", "check_types", "check_docs", "check_dependencies"])
-def check(ctx):
- """
- Check it all!
+def check(ctx: Context) -> None: # noqa: ARG001
+ """Check it all!
- Arguments:
+ Parameters:
ctx: The context instance (passed automatically).
"""
@duty
-def check_quality(ctx, files=PY_SRC):
- """
- Check the code quality.
+def check_quality(ctx: Context) -> None:
+ """Check the code quality.
- Arguments:
+ Parameters:
ctx: The context instance (passed automatically).
- files: The files to check.
"""
- ctx.run(f"flake8 --config=config/flake8.ini {files}", title="Checking code quality", pty=PTY)
+ ctx.run(
+ ruff.check(*PY_SRC_LIST, config="config/ruff.toml"),
+ title=pyprefix("Checking code quality"),
+ )
@duty
-def check_dependencies(ctx):
- """
- Check for vulnerabilities in dependencies.
+def check_dependencies(ctx: Context) -> None:
+ """Check for vulnerabilities in dependencies.
- Arguments:
+ Parameters:
ctx: The context instance (passed automatically).
"""
- # undo possible patching
- # see https://github.com/pyupio/safety/issues/348
- for module in sys.modules: # noqa: WPS528
- if module.startswith("safety.") or module == "safety":
- del sys.modules[module] # noqa: WPS420
-
- importlib.invalidate_caches()
-
- # reload original, unpatched safety
- from safety.formatter import SafetyFormatter
- from safety.safety import calculate_remediations
- from safety.safety import check as safety_check
- from safety.util import read_requirements
-
# retrieve the list of dependencies
requirements = ctx.run(
["pdm", "export", "-f", "requirements", "--without-hashes"],
@@ -155,57 +92,40 @@ def check_dependencies(ctx):
allow_overrides=False,
)
- # check using safety as a library
- def safety(): # noqa: WPS430
- packages = list(read_requirements(StringIO(requirements)))
- vulns, db_full = safety_check(packages=packages, ignore_vulns="")
- remediations = calculate_remediations(vulns, db_full)
- output_report = SafetyFormatter("text").render_vulnerabilities(
- announcements=[],
- vulnerabilities=vulns,
- remediations=remediations,
- full=True,
- packages=packages,
- )
- if vulns:
- print(output_report)
- return False
- return True
-
- ctx.run(safety, title="Checking dependencies")
+ ctx.run(safety.check(requirements), title="Checking dependencies")
@duty
-def check_docs(ctx):
- """
- Check if the documentation builds correctly.
+def check_docs(ctx: Context) -> None:
+ """Check if the documentation builds correctly.
- Arguments:
+ Parameters:
ctx: The context instance (passed automatically).
"""
Path("htmlcov").mkdir(parents=True, exist_ok=True)
Path("htmlcov/index.html").touch(exist_ok=True)
- ctx.run("mkdocs build -s", title="Building documentation", pty=PTY)
+ ctx.run(mkdocs.build(strict=True), title=pyprefix("Building documentation"))
-@duty # noqa: WPS231
-def check_types(ctx): # noqa: WPS231
- """
- Check that the code is correctly typed.
+@duty
+def check_types(ctx: Context) -> None:
+ """Check that the code is correctly typed.
- Arguments:
+ Parameters:
ctx: The context instance (passed automatically).
"""
os.environ["MYPYPATH"] = "src"
- ctx.run(f"mypy --config-file config/mypy.ini {PY_SRC}", title="Type-checking", pty=PTY)
+ ctx.run(
+ mypy.run(*PY_SRC_LIST, config_file="config/mypy.ini"),
+ title=pyprefix("Type-checking"),
+ )
@duty(silent=True)
-def clean(ctx):
- """
- Delete temporary files.
+def clean(ctx: Context) -> None:
+ """Delete temporary files.
- Arguments:
+ Parameters:
ctx: The context instance (passed automatically).
"""
ctx.run("rm -rf .coverage*")
@@ -222,63 +142,65 @@ def clean(ctx):
@duty
-def docs(ctx):
- """
- Build the documentation locally.
+def docs(ctx: Context) -> None:
+ """Build the documentation locally.
- Arguments:
+ Parameters:
ctx: The context instance (passed automatically).
"""
- ctx.run("mkdocs build", title="Building documentation")
+ ctx.run(mkdocs.build, title="Building documentation")
@duty
-def docs_serve(ctx, host="127.0.0.1", port=8000):
- """
- Serve the documentation (localhost:8000).
+def docs_serve(ctx: Context, host: str = "127.0.0.1", port: int = 8000) -> None:
+ """Serve the documentation (localhost:8000).
- Arguments:
+ Parameters:
ctx: The context instance (passed automatically).
host: The host to serve the docs from.
port: The port to serve the docs on.
"""
- ctx.run(f"mkdocs serve -a {host}:{port}", title="Serving documentation", capture=False)
+ ctx.run(
+ mkdocs.serve(dev_addr=f"{host}:{port}"),
+ title="Serving documentation",
+ capture=False,
+ )
@duty
-def docs_deploy(ctx):
- """
- Deploy the documentation on GitHub pages.
+def docs_deploy(ctx: Context) -> None:
+ """Deploy the documentation on GitHub pages.
- Arguments:
+ Parameters:
ctx: The context instance (passed automatically).
"""
- ctx.run("mkdocs gh-deploy", title="Deploying documentation")
+ ctx.run(mkdocs.gh_deploy, title="Deploying documentation")
@duty
-def format(ctx):
- """
- Run formatting tools on the code.
+def format(ctx: Context) -> None:
+ """Run formatting tools on the code.
- Arguments:
+ Parameters:
ctx: The context instance (passed automatically).
"""
ctx.run(
- f"autoflake -ir --exclude tests/fixtures --remove-all-unused-imports {PY_SRC}",
- title="Removing unused imports",
- pty=PTY,
+ ruff.check(*PY_SRC_LIST, config="config/ruff.toml", fix_only=True, exit_zero=True),
+ title="Auto-fixing code",
+ )
+ ctx.run(black.run(*PY_SRC_LIST, config="config/black.toml"), title="Formatting code")
+ ctx.run(
+ blacken_docs.run(*PY_SRC_LIST, "docs", exts=["py", "md"], line_length=120),
+ title="Formatting docs",
+ nofail=True,
)
- ctx.run(f"isort {PY_SRC}", title="Ordering imports", pty=PTY)
- ctx.run(f"black {PY_SRC}", title="Formatting code", pty=PTY)
@duty
-def release(ctx, version):
- """
- Release a new Python package.
+def release(ctx: Context, version: str) -> None:
+ """Release a new Python package.
- Arguments:
+ Parameters:
ctx: The context instance (passed automatically).
version: The new version number to use.
"""
@@ -293,32 +215,29 @@ def release(ctx, version):
docs_deploy.run()
-@duty(silent=True)
-def coverage(ctx):
- """
- Report coverage as text and HTML.
+@duty(silent=True, aliases=["coverage"])
+def cov(ctx: Context) -> None:
+ """Report coverage as text and HTML.
- Arguments:
+ Parameters:
ctx: The context instance (passed automatically).
"""
- ctx.run("coverage combine", nofail=True)
- ctx.run("coverage report --rcfile=config/coverage.ini", capture=False)
- ctx.run("coverage html --rcfile=config/coverage.ini")
+ ctx.run(coverage.combine, nofail=True)
+ ctx.run(coverage.report(rcfile="config/coverage.ini"), capture=False)
+ ctx.run(coverage.html(rcfile="config/coverage.ini"))
@duty
-def test(ctx, match: str = ""):
- """
- Run the test suite.
+def test(ctx: Context, match: str = "") -> None:
+ """Run the test suite.
- Arguments:
+ Parameters:
ctx: The context instance (passed automatically).
match: A pytest expression to filter selected tests.
"""
py_version = f"{sys.version_info.major}{sys.version_info.minor}"
os.environ["COVERAGE_FILE"] = f".coverage.{py_version}"
ctx.run(
- ["pytest", "-c", "config/pytest.ini", "-n", "auto", "-k", match, "tests"],
- title="Running tests",
- pty=PTY,
+ pytest.run("-n", "auto", "tests", config_file="config/pytest.ini", select=match),
+ title=pyprefix("Running tests"),
)
diff --git a/mkdocs.yml b/mkdocs.yml
index 6b851eed..f07a73d2 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -29,6 +29,8 @@ theme:
- navigation.tabs
- navigation.tabs.sticky
- navigation.top
+ - search.highlight
+ - search.suggest
palette:
- media: "(prefers-color-scheme: light)"
scheme: default
@@ -69,9 +71,9 @@ plugins:
- markdown-exec
- gen-files:
scripts:
- - docs/gen_ref_nav.py
+ - scripts/gen_ref_nav.py
- literate-nav:
- nav_file: SUMMARY.md
+ nav_file: SUMMARY.txt
- coverage
- section-index
- mkdocstrings:
@@ -95,5 +97,7 @@ extra:
social:
- icon: fontawesome/brands/github
link: https://github.com/pawamoy
+ - icon: fontawesome/brands/mastodon
+ link: https://fosstodon.org/@pawamoy
- icon: fontawesome/brands/twitter
link: https://twitter.com/pawamoy
diff --git a/pyproject.toml b/pyproject.toml
index 914bd58b..673d3c8b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -29,7 +29,7 @@ classifiers = [
"Typing :: Typed",
]
dependencies = [
- "mkdocstrings>=0.19",
+ "mkdocstrings>=0.20",
"griffe>=0.24",
]
@@ -52,7 +52,7 @@ includes = ["src/mkdocstrings_handlers"]
editable-backend = "editables"
[tool.pdm.dev-dependencies]
-duty = ["duty>=0.7"]
+duty = ["duty>=0.8"]
docs = [
"mkdocs>=1.3",
"mkdocs-coverage>=0.2",
@@ -64,32 +64,13 @@ docs = [
"markdown-exec>=0.5",
"toml>=0.10",
]
-format = [
- "autoflake>=1.4",
- "black>=21.10b0",
- "isort>=5.10",
-]
maintain = [
- "git-changelog>=0.4",
+ "black>=23.1",
+ "blacken-docs>=1.13",
+ "git-changelog>=1.0",
]
quality = [
- # TODO: remove once importlib-metadata version conflict is resolved
- "importlib-metadata<5; python_version < '3.8'",
- "flake8>=4; python_version >= '3.8'",
-
- "darglint>=1.8",
- "flake8-bandit>=2.1",
- "flake8-black>=0.2",
- "flake8-bugbear>=21.9",
- "flake8-builtins>=1.5",
- "flake8-comprehensions>=3.7",
- "flake8-docstrings>=1.6",
- "flake8-pytest-style>=1.5",
- "flake8-string-format>=0.3",
- "flake8-tidy-imports>=4.5",
- "flake8-variables-names>=0.0",
- "pep8-naming>=0.12",
- "wps-light>=0.15",
+ "ruff>=0.0.246",
]
tests = [
"pytest>=6.2",
@@ -98,22 +79,8 @@ tests = [
"pytest-xdist>=2.4",
]
typing = [
- "mypy>=0.910",
+ "mypy>=0.911",
"types-markdown>=3.3",
"types-toml>=0.10",
]
security = ["safety>=2"]
-
-[tool.black]
-line-length = 120
-exclude = "tests/fixtures"
-
-[tool.isort]
-line_length = 120
-not_skip = "__init__.py"
-multi_line_output = 3
-force_single_line = false
-balanced_wrapping = true
-default_section = "THIRDPARTY"
-known_first_party = "mkdocstrings_handlers"
-include_trailing_comma = true
diff --git a/scripts/gen_credits.py b/scripts/gen_credits.py
index 10f5647d..7f59f8f9 100644
--- a/scripts/gen_credits.py
+++ b/scripts/gen_credits.py
@@ -1,16 +1,22 @@
+"""Script to generate the project's credits."""
+
+from __future__ import annotations
+
import re
+import sys
from itertools import chain
from pathlib import Path
from textwrap import dedent
+from typing import Mapping, cast
import toml
from jinja2 import StrictUndefined
from jinja2.sandbox import SandboxedEnvironment
-try:
- from importlib.metadata import metadata, PackageNotFoundError
-except ImportError:
- from importlib_metadata import metadata, PackageNotFoundError
+if sys.version_info < (3, 8):
+ from importlib_metadata import PackageNotFoundError, metadata
+else:
+ from importlib.metadata import PackageNotFoundError, metadata
project_dir = Path(".")
pyproject = toml.load(project_dir / "pyproject.toml")
@@ -21,31 +27,33 @@
project_name = project["name"]
regex = re.compile(r"(?P[\w.-]+)(?P.*)$")
-def get_license(pkg_name):
+
+def _get_license(pkg_name: str) -> str:
try:
data = metadata(pkg_name)
except PackageNotFoundError:
return "?"
- license = data.get("License", "").strip()
- multiple_lines = bool(license.count("\n"))
+ license_name = cast(dict, data).get("License", "").strip()
+ multiple_lines = bool(license_name.count("\n"))
# TODO: remove author logic once all my packages licenses are fixed
author = ""
- if multiple_lines or not license or license == "UNKNOWN":
- for header, value in data.items():
+ if multiple_lines or not license_name or license_name == "UNKNOWN":
+ for header, value in cast(dict, data).items():
if header == "Classifier" and value.startswith("License ::"):
- license = value.rsplit("::", 1)[1].strip()
+ license_name = value.rsplit("::", 1)[1].strip()
elif header == "Author-email":
author = value
- if license == "Other/Proprietary License" and "pawamoy" in author:
- license = "ISC"
- return license or "?"
+ if license_name == "Other/Proprietary License" and "pawamoy" in author:
+ license_name = "ISC"
+ return license_name or "?"
-def get_deps(base_deps):
+
+def _get_deps(base_deps: Mapping[str, Mapping[str, str]]) -> dict[str, dict[str, str]]:
deps = {}
for dep in base_deps:
- parsed = regex.match(dep).groupdict()
+ parsed = regex.match(dep).groupdict() # type: ignore[union-attr]
dep_name = parsed["dist"].lower()
- deps[dep_name] = {"license": get_license(dep_name), **parsed, **lock_pkgs[dep_name]}
+ deps[dep_name] = {"license": _get_license(dep_name), **parsed, **lock_pkgs[dep_name]}
again = True
while again:
@@ -53,58 +61,63 @@ def get_deps(base_deps):
for pkg_name in lock_pkgs:
if pkg_name in deps:
for pkg_dependency in lock_pkgs[pkg_name].get("dependencies", []):
- parsed = regex.match(pkg_dependency).groupdict()
+ parsed = regex.match(pkg_dependency).groupdict() # type: ignore[union-attr]
dep_name = parsed["dist"].lower()
- if dep_name not in deps:
- deps[dep_name] = {"license": get_license(dep_name), **parsed, **lock_pkgs[dep_name]}
+ if dep_name not in deps and dep_name != project["name"]:
+ deps[dep_name] = {"license": _get_license(dep_name), **parsed, **lock_pkgs[dep_name]}
again = True
return deps
-dev_dependencies = get_deps(chain(*pdm.get("dev-dependencies", {}).values()))
-prod_dependencies = get_deps(
- chain(
- project.get("dependencies", []),
- chain(*project.get("optional-dependencies", {}).values()),
+
+def _render_credits() -> str:
+ dev_dependencies = _get_deps(chain(*pdm.get("dev-dependencies", {}).values())) # type: ignore[arg-type]
+ prod_dependencies = _get_deps(
+ chain( # type: ignore[arg-type]
+ project.get("dependencies", []),
+ chain(*project.get("optional-dependencies", {}).values()),
+ ),
)
-)
-
-template_data = {
- "project_name": project_name,
- "prod_dependencies": sorted(prod_dependencies.values(), key=lambda dep: dep["name"]),
- "dev_dependencies": sorted(dev_dependencies.values(), key=lambda dep: dep["name"]),
- "more_credits": "http://pawamoy.github.io/credits/",
-}
-template_text = dedent(
- """
- These projects were used to build `{{ project_name }}`. **Thank you!**
-
- [`python`](https://www.python.org/) |
- [`pdm`](https://pdm.fming.dev/) |
- [`copier-pdm`](https://github.com/pawamoy/copier-pdm)
-
- {% macro dep_line(dep) -%}
- [`{{ dep.name }}`](https://pypi.org/project/{{ dep.name }}/) | {{ dep.summary }} | {{ ("`" ~ dep.spec ~ "`") if dep.spec else "" }} | `{{ dep.version }}` | {{ dep.license }}
- {%- endmacro %}
-
- ### Runtime dependencies
-
- Project | Summary | Version (accepted) | Version (last resolved) | License
- ------- | ------- | ------------------ | ----------------------- | -------
- {% for dep in prod_dependencies -%}
- {{ dep_line(dep) }}
- {% endfor %}
-
- ### Development dependencies
-
- Project | Summary | Version (accepted) | Version (last resolved) | License
- ------- | ------- | ------------------ | ----------------------- | -------
- {% for dep in dev_dependencies -%}
- {{ dep_line(dep) }}
- {% endfor %}
-
- {% if more_credits %}**[More credits from the author]({{ more_credits }})**{% endif %}
- """
-)
-jinja_env = SandboxedEnvironment(undefined=StrictUndefined)
-print(jinja_env.from_string(template_text).render(**template_data))
+
+ template_data = {
+ "project_name": project_name,
+ "prod_dependencies": sorted(prod_dependencies.values(), key=lambda dep: dep["name"]),
+ "dev_dependencies": sorted(dev_dependencies.values(), key=lambda dep: dep["name"]),
+ "more_credits": "http://pawamoy.github.io/credits/",
+ }
+ template_text = dedent(
+ """
+ These projects were used to build `{{ project_name }}`. **Thank you!**
+
+ [`python`](https://www.python.org/) |
+ [`pdm`](https://pdm.fming.dev/) |
+ [`copier-pdm`](https://github.com/pawamoy/copier-pdm)
+
+ {% macro dep_line(dep) -%}
+ [`{{ dep.name }}`](https://pypi.org/project/{{ dep.name }}/) | {{ dep.summary }} | {{ ("`" ~ dep.spec ~ "`") if dep.spec else "" }} | `{{ dep.version }}` | {{ dep.license }}
+ {%- endmacro %}
+
+ ### Runtime dependencies
+
+ Project | Summary | Version (accepted) | Version (last resolved) | License
+ ------- | ------- | ------------------ | ----------------------- | -------
+ {% for dep in prod_dependencies -%}
+ {{ dep_line(dep) }}
+ {% endfor %}
+
+ ### Development dependencies
+
+ Project | Summary | Version (accepted) | Version (last resolved) | License
+ ------- | ------- | ------------------ | ----------------------- | -------
+ {% for dep in dev_dependencies -%}
+ {{ dep_line(dep) }}
+ {% endfor %}
+
+ {% if more_credits %}**[More credits from the author]({{ more_credits }})**{% endif %}
+ """,
+ )
+ jinja_env = SandboxedEnvironment(undefined=StrictUndefined)
+ return jinja_env.from_string(template_text).render(**template_data)
+
+
+print(_render_credits())
diff --git a/docs/gen_ref_nav.py b/scripts/gen_ref_nav.py
old mode 100755
new mode 100644
similarity index 88%
rename from docs/gen_ref_nav.py
rename to scripts/gen_ref_nav.py
index 14f0f4ad..97d8b5a7
--- a/docs/gen_ref_nav.py
+++ b/scripts/gen_ref_nav.py
@@ -17,7 +17,7 @@
parts = parts[:-1]
doc_path = doc_path.with_name("index.md")
full_doc_path = full_doc_path.with_name("index.md")
- elif parts[-1] == "__main__":
+ elif parts[-1].startswith("_"):
continue
nav[parts] = doc_path.as_posix()
@@ -28,5 +28,5 @@
mkdocs_gen_files.set_edit_path(full_doc_path, ".." / path)
-with mkdocs_gen_files.open("reference/SUMMARY.md", "w") as nav_file:
+with mkdocs_gen_files.open("reference/SUMMARY.txt", "w") as nav_file:
nav_file.writelines(nav.build_literate_nav())
diff --git a/scripts/multirun.sh b/scripts/multirun.sh
deleted file mode 100755
index a55d1746..00000000
--- a/scripts/multirun.sh
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/env bash
-set -e
-
-PYTHON_VERSIONS="${PYTHON_VERSIONS-3.7 3.8 3.9 3.10 3.11}"
-
-restore_previous_python_version() {
- if pdm use -f "$1" &>/dev/null; then
- echo "> Restored previous Python version: ${1##*/}"
- fi
-}
-
-if [ -n "${PYTHON_VERSIONS}" ]; then
- old_python_version="$(pdm config python.path)"
- echo "> Currently selected Python version: ${old_python_version##*/}"
- trap "restore_previous_python_version ${old_python_version}" EXIT
- for python_version in ${PYTHON_VERSIONS}; do
- if pdm use -f "python${python_version}" &>/dev/null; then
- echo "> pdm run $@ (python${python_version})"
- pdm run "$@"
- else
- echo "> pdm use -f python${python_version}: Python interpreter not available?" >&2
- fi
- done
-else
- pdm run "$@"
-fi
diff --git a/scripts/setup.sh b/scripts/setup.sh
index 188eaebc..9e7ab1ff 100755
--- a/scripts/setup.sh
+++ b/scripts/setup.sh
@@ -3,36 +3,18 @@ set -e
PYTHON_VERSIONS="${PYTHON_VERSIONS-3.7 3.8 3.9 3.10 3.11}"
-install_with_pipx() {
- if ! command -v "$1" &>/dev/null; then
- if ! command -v pipx &>/dev/null; then
- python3 -m pip install --user pipx
- fi
- pipx install "$1"
+if ! command -v pdm &>/dev/null; then
+ if ! command -v pipx &>/dev/null; then
+ python3 -m pip install --user pipx
fi
-}
-
-install_with_pipx pdm
-
-restore_previous_python_version() {
- if pdm use -f "$1" &>/dev/null; then
- echo "> Restored previous Python version: ${1##*/}"
- fi
-}
+ pipx install pdm
+fi
+if ! pdm self list 2>/dev/null | grep -q pdm-multirun; then
+ pipx inject pdm pdm-multirun
+fi
if [ -n "${PYTHON_VERSIONS}" ]; then
- if old_python_version="$(pdm config python.path 2>/dev/null)"; then
- echo "> Currently selected Python version: ${old_python_version##*/}"
- trap "restore_previous_python_version ${old_python_version}" EXIT
- fi
- for python_version in ${PYTHON_VERSIONS}; do
- if pdm use -f "python${python_version}" &>/dev/null; then
- echo "> Using Python ${python_version} interpreter"
- pdm install
- else
- echo "> pdm use -f python${python_version}: Python interpreter not available?" >&2
- fi
- done
+ pdm multirun -vi ${PYTHON_VERSIONS// /,} pdm install
else
pdm install
fi
diff --git a/src/mkdocstrings_handlers/python/__init__.py b/src/mkdocstrings_handlers/python/__init__.py
index 706d85ee..f93ab20e 100644
--- a/src/mkdocstrings_handlers/python/__init__.py
+++ b/src/mkdocstrings_handlers/python/__init__.py
@@ -2,7 +2,7 @@
from mkdocstrings_handlers.python.handler import get_handler
-__all__ = ["get_handler"] # noqa: WPS410
+__all__ = ["get_handler"]
# TODO: CSS classes everywhere in templates
# TODO: name normalization (filenames, Jinja2 variables, HTML tags, CSS classes)
diff --git a/src/mkdocstrings_handlers/python/handler.py b/src/mkdocstrings_handlers/python/handler.py
index 940f2731..ffb645ea 100644
--- a/src/mkdocstrings_handlers/python/handler.py
+++ b/src/mkdocstrings_handlers/python/handler.py
@@ -2,6 +2,7 @@
from __future__ import annotations
+import copy
import glob
import os
import posixpath
@@ -9,15 +10,13 @@
import sys
from collections import ChainMap
from contextlib import suppress
-from typing import Any, BinaryIO, Iterator, Optional, Tuple
+from typing import TYPE_CHECKING, Any, BinaryIO, Iterator, Mapping
-from griffe.agents.extensions import load_extensions
from griffe.collections import LinesCollection, ModulesCollection
from griffe.docstrings.parsers import Parser
from griffe.exceptions import AliasResolutionError
from griffe.loader import GriffeLoader
from griffe.logger import patch_loggers
-from markdown import Markdown
from mkdocstrings.extension import PluginError
from mkdocstrings.handlers.base import BaseHandler, CollectionError, CollectorItem
from mkdocstrings.inventory import Inventory
@@ -25,14 +24,24 @@
from mkdocstrings_handlers.python import rendering
+try:
+ from griffe.extensions import load_extensions
+except ImportError:
+ # TODO: remove once support for Griffe 0.25 is dropped
+ from griffe.agents.extensions import load_extensions # type: ignore[no-redef]
+
+if TYPE_CHECKING:
+ from markdown import Markdown
+
+
if sys.version_info >= (3, 11):
from contextlib import chdir
else:
# TODO: remove once support for Python 3.10 is dropped
from contextlib import contextmanager
- @contextmanager # noqa: WPS440
- def chdir(path: str): # noqa: D103,WPS440
+ @contextmanager
+ def chdir(path: str) -> Iterator[None]: # noqa: D103
old_wd = os.getcwd()
os.chdir(path)
try:
@@ -78,6 +87,16 @@ class PythonHandler(BaseHandler):
"separate_signature": False,
"line_length": 60,
"merge_init_into_class": False,
+ "show_docstring_attributes": True,
+ "show_docstring_description": True,
+ "show_docstring_examples": True,
+ "show_docstring_other_parameters": True,
+ "show_docstring_parameters": True,
+ "show_docstring_raises": True,
+ "show_docstring_receives": True,
+ "show_docstring_returns": True,
+ "show_docstring_warns": True,
+ "show_docstring_yields": True,
"show_source": True,
"show_bases": True,
"show_submodules": False,
@@ -88,6 +107,8 @@ class PythonHandler(BaseHandler):
"members": None,
"filters": ["!^_[^_]"],
"annotations_path": "brief",
+ "preload_modules": None,
+ "load_external_modules": False,
}
"""
Attributes: Headings options:
@@ -118,6 +139,16 @@ class PythonHandler(BaseHandler):
line_length (int): Maximum line length when formatting code/signatures. Default: `60`.
merge_init_into_class (bool): Whether to merge the `__init__` method into the class' signature and docstring. Default: `False`.
show_if_no_docstring (bool): Show the object heading even if it has no docstring or children with docstrings. Default: `False`.
+ show_docstring_attributes (bool): Whether to display the "Attributes" section in the object's docstring. Default: `True`.
+ show_docstring_description (bool): Whether to display the textual block (including admonitions) in the object's docstring. Default: `True`.
+ show_docstring_examples (bool): Whether to display the "Examples" section in the object's docstring. Default: `True`.
+ show_docstring_other_parameters (bool): Whether to display the "Other Parameters" section in the object's docstring. Default: `True`.
+ show_docstring_parameters (bool): Whether to display the "Parameters" section in the object's docstring. Default: `True`.
+ show_docstring_raises (bool): Whether to display the "Raises" section in the object's docstring. Default: `True`.
+ show_docstring_receives (bool): Whether to display the "Receives" section in the object's docstring. Default: `True`.
+ show_docstring_returns (bool): Whether to display the "Returns" section in the object's docstring. Default: `True`.
+ show_docstring_warns (bool): Whether to display the "Warns" section in the object's docstring. Default: `True`.
+ show_docstring_yields (bool): Whether to display the "Yields" section in the object's docstring. Default: `True`.
Attributes: Signatures/annotations options:
annotations_path (str): The verbosity for annotations path: `brief` (recommended), or `source` (as written in the source). Default: `"brief"`.
@@ -129,10 +160,24 @@ class PythonHandler(BaseHandler):
Attributes: Additional options:
show_bases (bool): Show the base classes of a class. Default: `True`.
show_source (bool): Show the source code of this object. Default: `True`.
- """ # noqa: E501
+ preload_modules (list[str] | None): Pre-load modules that are
+ not specified directly in autodoc instructions (`::: identifier`).
+ It is useful when you want to render documentation for a particular member of an object,
+ and this member is imported from another package than its parent.
+
+ For an imported member to be rendered, you need to add it to the `__all__` attribute
+ of the importing module.
+
+ The modules must be listed as an array of strings. Default: `None`.
+
+ """
def __init__(
- self, *args: Any, config_file_path: str | None = None, paths: list[str] | None = None, **kwargs: Any
+ self,
+ *args: Any,
+ config_file_path: str | None = None,
+ paths: list[str] | None = None,
+ **kwargs: Any,
) -> None:
"""Initialize the handler.
@@ -153,9 +198,8 @@ def __init__(
paths.append(os.path.dirname(config_file_path))
search_paths = [path for path in sys.path if path] # eliminate empty path
for path in reversed(paths):
- if not os.path.isabs(path):
- if config_file_path:
- path = os.path.abspath(os.path.join(os.path.dirname(config_file_path), path))
+ if not os.path.isabs(path) and config_file_path:
+ path = os.path.abspath(os.path.join(os.path.dirname(config_file_path), path)) # noqa: PLW2901
if path not in search_paths:
search_paths.insert(0, path)
self._paths = search_paths
@@ -167,9 +211,10 @@ def load_inventory(
cls,
in_file: BinaryIO,
url: str,
- base_url: Optional[str] = None,
- **kwargs: Any,
- ) -> Iterator[Tuple[str, str]]:
+ base_url: str | None = None,
+ domains: list[str] | None = None,
+ **kwargs: Any, # noqa: ARG003
+ ) -> Iterator[tuple[str, str]]:
"""Yield items and their URLs from an inventory file streamed from `in_file`.
This implements mkdocstrings' `load_inventory` "protocol" (see [`mkdocstrings.plugin`][mkdocstrings.plugin]).
@@ -178,24 +223,28 @@ def load_inventory(
in_file: The binary file-like object to read the inventory from.
url: The URL that this file is being streamed from (used to guess `base_url`).
base_url: The URL that this inventory's sub-paths are relative to.
+ domains: A list of domain strings to filter the inventory by, when not passed, "py" will be used.
**kwargs: Ignore additional arguments passed from the config.
Yields:
Tuples of (item identifier, item URL).
"""
+ domains = domains or ["py"]
if base_url is None:
base_url = posixpath.dirname(url)
- for item in Inventory.parse_sphinx(in_file, domain_filter=("py",)).values(): # noqa: WPS526
+ for item in Inventory.parse_sphinx(in_file, domain_filter=domains).values():
yield item.name, posixpath.join(base_url, item.uri)
- def collect(self, identifier: str, config: dict) -> CollectorItem: # noqa: D102,WPS231
+ def collect(self, identifier: str, config: Mapping[str, Any]) -> CollectorItem: # noqa: D102
module_name = identifier.split(".", 1)[0]
unknown_module = module_name not in self._modules_collection
if config.get("fallback", False) and unknown_module:
raise CollectionError("Not loading additional modules during fallback")
- final_config = ChainMap(config, self.default_config)
+ # See: https://github.com/python/typeshed/issues/8430
+ mutable_config = dict(copy.deepcopy(config))
+ final_config = ChainMap(mutable_config, self.default_config)
parser_name = final_config["docstring_style"]
parser_options = final_config["docstring_options"]
parser = parser_name and Parser(parser_name)
@@ -210,19 +259,26 @@ def collect(self, identifier: str, config: dict) -> CollectorItem: # noqa: D102
lines_collection=self._lines_collection,
)
try:
+ for pre_loaded_module in final_config.get("preload_modules") or []:
+ if pre_loaded_module not in self._modules_collection:
+ loader.load_module(pre_loaded_module)
loader.load_module(module_name)
except ImportError as error:
raise CollectionError(str(error)) from error
-
- unresolved, iterations = loader.resolve_aliases(implicit=False, external=False)
+ unresolved, iterations = loader.resolve_aliases(
+ implicit=False,
+ external=final_config["load_external_modules"],
+ )
if unresolved:
logger.debug(f"{len(unresolved)} aliases were still unresolved after {iterations} iterations")
logger.debug(f"Unresolved aliases: {', '.join(sorted(unresolved))}")
try:
doc_object = self._modules_collection[identifier]
- except KeyError as error: # noqa: WPS440
+ except KeyError as error:
raise CollectionError(f"{identifier} could not be found") from error
+ except AliasResolutionError as error:
+ raise CollectionError(str(error)) from error
if not unknown_module:
with suppress(AliasResolutionError):
@@ -232,8 +288,10 @@ def collect(self, identifier: str, config: dict) -> CollectorItem: # noqa: D102
return doc_object
- def render(self, data: CollectorItem, config: dict) -> str: # noqa: D102 (ignore missing docstring)
- final_config = ChainMap(config, self.default_config)
+ def render(self, data: CollectorItem, config: Mapping[str, Any]) -> str: # noqa: D102 (ignore missing docstring)
+ # See https://github.com/python/typeshed/issues/8430
+ mutabled_config = dict(copy.deepcopy(config))
+ final_config = ChainMap(mutabled_config, self.default_config)
template = self.env.get_template(f"{data.kind.value}.html")
@@ -243,9 +301,11 @@ def render(self, data: CollectorItem, config: dict) -> str: # noqa: D102 (ignor
heading_level = final_config["heading_level"]
try:
final_config["members_order"] = rendering.Order(final_config["members_order"])
- except ValueError:
+ except ValueError as error:
choices = "', '".join(item.value for item in rendering.Order)
- raise PluginError(f"Unknown members_order '{final_config['members_order']}', choose between '{choices}'.")
+ raise PluginError(
+ f"Unknown members_order '{final_config['members_order']}', choose between '{choices}'.",
+ ) from error
if final_config["filters"]:
final_config["filters"] = [
@@ -276,11 +336,11 @@ def get_anchors(self, data: CollectorItem) -> list[str]: # noqa: D102 (ignore m
def get_handler(
- theme: str, # noqa: W0613 (unused argument config)
- custom_templates: Optional[str] = None,
+ theme: str,
+ custom_templates: str | None = None,
config_file_path: str | None = None,
paths: list[str] | None = None,
- **config: Any,
+ **config: Any, # noqa: ARG001
) -> PythonHandler:
"""Simply return an instance of `PythonHandler`.
diff --git a/src/mkdocstrings_handlers/python/rendering.py b/src/mkdocstrings_handlers/python/rendering.py
index 8e5f7d85..9ee91769 100644
--- a/src/mkdocstrings_handlers/python/rendering.py
+++ b/src/mkdocstrings_handlers/python/rendering.py
@@ -6,13 +6,15 @@
import re
import sys
from functools import lru_cache
-from typing import Any, Pattern, Sequence
+from typing import TYPE_CHECKING, Any, Callable, Match, Pattern, Sequence
-from griffe.dataclasses import Alias, Object
from markupsafe import Markup
-from mkdocstrings.handlers.base import CollectorItem
from mkdocstrings.loggers import get_logger
+if TYPE_CHECKING:
+ from griffe.dataclasses import Alias, Object
+ from mkdocstrings.handlers.base import CollectorItem
+
logger = get_logger(__name__)
@@ -102,7 +104,7 @@ def do_order_members(
return sorted(members, key=order_map[order])
-def do_crossref(path: str, brief: bool = True) -> Markup:
+def do_crossref(path: str, *, brief: bool = True) -> Markup:
"""Filter to create cross-references.
Parameters:
@@ -118,7 +120,7 @@ def do_crossref(path: str, brief: bool = True) -> Markup:
return Markup("{path}").format(full_path=full_path, path=path)
-def do_multi_crossref(text: str, code: bool = True) -> Markup:
+def do_multi_crossref(text: str, *, code: bool = True) -> Markup:
"""Filter to create cross-references.
Parameters:
@@ -131,8 +133,8 @@ def do_multi_crossref(text: str, code: bool = True) -> Markup:
group_number = 0
variables = {}
- def repl(match): # noqa: WPS430
- nonlocal group_number # noqa: WPS420
+ def repl(match: Match) -> str:
+ nonlocal group_number
group_number += 1
path = match.group()
path_var = f"path{group_number}"
@@ -145,7 +147,7 @@ def repl(match): # noqa: WPS430
return Markup(text).format(**variables)
-def _keep_object(name, filters):
+def _keep_object(name: str, filters: Sequence[tuple[Pattern, bool]]) -> bool:
keep = None
rules = set()
for regex, exclude in filters:
@@ -153,7 +155,7 @@ def _keep_object(name, filters):
if regex.search(name):
keep = not exclude
if keep is None:
- if rules == {False}: # noqa: WPS531
+ if rules == {False}:
# only included stuff, no match = reject
return False
# only excluded stuff, or included and excluded stuff, no match = keep
@@ -163,7 +165,8 @@ def _keep_object(name, filters):
def do_filter_objects(
objects_dictionary: dict[str, Object | Alias],
- filters: list[tuple[bool, Pattern]] | None = None,
+ *,
+ filters: Sequence[tuple[Pattern, bool]] | None = None,
members_list: list[str] | None = None,
keep_no_docstrings: bool = True,
) -> list[Object | Alias]:
@@ -195,14 +198,14 @@ def do_filter_objects(
@lru_cache(maxsize=1)
-def _get_black_formatter():
+def _get_black_formatter() -> Callable[[str, int], str]:
try:
from black import Mode, format_str
except ModuleNotFoundError:
logger.warning("Formatting signatures requires Black to be installed.")
return lambda text, _: text
- def formatter(code, line_length): # noqa: WPS430
+ def formatter(code: str, line_length: int) -> str:
mode = Mode(line_length=line_length)
return format_str(code, mode=mode)
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/children.html b/src/mkdocstrings_handlers/python/templates/material/_base/children.html
index 71755ea7..9e27ed0f 100644
--- a/src/mkdocstrings_handlers/python/templates/material/_base/children.html
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/children.html
@@ -19,7 +19,7 @@
{% set extra_level = 0 %}
{% endif %}
- {% with attributes = obj.attributes|filter_objects(config.filters, members_list, config.show_if_no_docstring) %}
+ {% with attributes = obj.attributes|filter_objects(filters=config.filters, members_list=members_list, keep_no_docstrings=config.show_if_no_docstring) %}
{% if attributes %}
{% if config.show_category_heading %}
{% filter heading(heading_level, id=html_id ~ "-attributes") %}Attributes{% endfilter %}
@@ -34,7 +34,7 @@
{% endif %}
{% endwith %}
- {% with classes = obj.classes|filter_objects(config.filters, members_list, config.show_if_no_docstring) %}
+ {% with classes = obj.classes|filter_objects(filters=config.filters, members_list=members_list, keep_no_docstrings=config.show_if_no_docstring) %}
{% if classes %}
{% if config.show_category_heading %}
{% filter heading(heading_level, id=html_id ~ "-classes") %}Classes{% endfilter %}
@@ -49,7 +49,7 @@
{% endif %}
{% endwith %}
- {% with functions = obj.functions|filter_objects(config.filters, members_list, config.show_if_no_docstring) %}
+ {% with functions = obj.functions|filter_objects(filters=config.filters, members_list=members_list, keep_no_docstrings=config.show_if_no_docstring) %}
{% if functions %}
{% if config.show_category_heading %}
{% filter heading(heading_level, id=html_id ~ "-functions") %}Functions{% endfilter %}
@@ -67,7 +67,7 @@
{% endwith %}
{% if config.show_submodules %}
- {% with modules = obj.modules|filter_objects(config.filters, members_list, config.show_if_no_docstring) %}
+ {% with modules = obj.modules|filter_objects(filters=config.filters, members_list=members_list, keep_no_docstrings=config.show_if_no_docstring) %}
{% if modules %}
{% if config.show_category_heading %}
{% filter heading(heading_level, id=html_id ~ "-modules") %}Modules{% endfilter %}
@@ -88,7 +88,7 @@
{% else %}
{% for child in obj.members|
- filter_objects(config.filters, members_list, config.show_if_no_docstring)|
+ filter_objects(filters=config.filters, members_list=members_list, keep_no_docstrings=config.show_if_no_docstring)|
order_members(config.members_order, members_list) %}
{% if not (obj.kind.value == "class" and child.name == "__init__" and config.merge_init_into_class) %}
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/docstring.html b/src/mkdocstrings_handlers/python/templates/material/_base/docstring.html
index bd1b6963..1f840771 100644
--- a/src/mkdocstrings_handlers/python/templates/material/_base/docstring.html
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/docstring.html
@@ -1,27 +1,27 @@
{% if docstring_sections %}
{{ log.debug("Rendering docstring") }}
{% for section in docstring_sections %}
- {% if section.kind.value == "text" %}
+ {% if config.show_docstring_description and section.kind.value == "text" %}
{{ section.value|convert_markdown(heading_level, html_id) }}
- {% elif section.kind.value == "attributes" %}
+ {% elif config.show_docstring_attributes and section.kind.value == "attributes" %}
{% include "docstring/attributes.html" with context %}
- {% elif section.kind.value == "parameters" %}
+ {% elif config.show_docstring_parameters and section.kind.value == "parameters" %}
{% include "docstring/parameters.html" with context %}
- {% elif section.kind.value == "other parameters" %}
+ {% elif config.show_docstring_other_parameters and section.kind.value == "other parameters" %}
{% include "docstring/other_parameters.html" with context %}
- {% elif section.kind.value == "raises" %}
+ {% elif config.show_docstring_raises and section.kind.value == "raises" %}
{% include "docstring/raises.html" with context %}
- {% elif section.kind.value == "warns" %}
- {% include "docstring/warns.html" with context %}
- {% elif section.kind.value == "yields" %}
+ {% elif config.show_docstring_warns and section.kind.value == "warns" %}
+ {% include "docstring/warns.html" with context %}
+ {% elif config.show_docstring_yields and section.kind.value == "yields" %}
{% include "docstring/yields.html" with context %}
- {% elif section.kind.value == "receives" %}
- {% include "docstring/receives.html" with context %}
- {% elif section.kind.value == "returns" %}
+ {% elif config.show_docstring_receives and section.kind.value == "receives" %}
+ {% include "docstring/receives.html" with context %}
+ {% elif config.show_docstring_returns and section.kind.value == "returns" %}
{% include "docstring/returns.html" with context %}
- {% elif section.kind.value == "examples" %}
+ {% elif config.show_docstring_examples and section.kind.value == "examples" %}
{% include "docstring/examples.html" with context %}
- {% elif section.kind.value == "admonition" %}
+ {% elif config.show_docstring_description and section.kind.value == "admonition" %}
{% include "docstring/admonition.html" with context %}
{% endif %}
{% endfor %}
diff --git a/tests/conftest.py b/tests/conftest.py
index ce71a665..5a34bd77 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -3,15 +3,23 @@
from __future__ import annotations
from collections import ChainMap
+from typing import TYPE_CHECKING, Any, Iterator
import pytest
from markdown.core import Markdown
from mkdocs import config
from mkdocs.config.defaults import get_schema
+if TYPE_CHECKING:
+ from pathlib import Path
+
+ from mkdocstrings.plugin import MkdocstringsPlugin
+
+ from mkdocstrings_handlers.python.handler import PythonHandler
+
@pytest.fixture(name="mkdocs_conf")
-def fixture_mkdocs_conf(request, tmp_path):
+def fixture_mkdocs_conf(request: pytest.FixtureRequest, tmp_path: Path) -> Iterator[config.Config]:
"""Yield a MkDocs configuration object.
Parameters:
@@ -21,9 +29,9 @@ def fixture_mkdocs_conf(request, tmp_path):
Yields:
MkDocs config.
"""
- conf = config.Config(schema=get_schema())
- while hasattr(request, "_parent_request") and hasattr(request._parent_request, "_parent_request"): # noqa: WPS437
- request = request._parent_request # noqa: WPS437
+ conf = config.Config(schema=get_schema()) # type: ignore[call-arg]
+ while hasattr(request, "_parent_request") and hasattr(request._parent_request, "_parent_request"):
+ request = request._parent_request
conf_dict = {
"site_name": "foo",
@@ -33,7 +41,7 @@ def fixture_mkdocs_conf(request, tmp_path):
**getattr(request, "param", {}),
}
# Re-create it manually as a workaround for https://github.com/mkdocs/mkdocs/issues/2289
- mdx_configs = dict(ChainMap(*conf_dict.get("markdown_extensions", [])))
+ mdx_configs: dict[str, Any] = dict(ChainMap(*conf_dict.get("markdown_extensions", []))) # type: ignore[arg-type]
conf.load_dict(conf_dict)
assert conf.validate() == ([], [])
@@ -48,7 +56,7 @@ def fixture_mkdocs_conf(request, tmp_path):
@pytest.fixture(name="plugin")
-def fixture_plugin(mkdocs_conf):
+def fixture_plugin(mkdocs_conf: config.Config) -> MkdocstringsPlugin:
"""Return a plugin instance.
Parameters:
@@ -57,26 +65,24 @@ def fixture_plugin(mkdocs_conf):
Returns:
mkdocstrings plugin instance.
"""
- plugin = mkdocs_conf["plugins"]["mkdocstrings"]
- plugin.md = Markdown(extensions=mkdocs_conf["markdown_extensions"], extension_configs=mkdocs_conf["mdx_configs"])
- return plugin
+ return mkdocs_conf["plugins"]["mkdocstrings"]
@pytest.fixture(name="ext_markdown")
-def fixture_ext_markdown(plugin):
+def fixture_ext_markdown(mkdocs_conf: config.Config) -> Markdown:
"""Return a Markdown instance with MkdocstringsExtension.
Parameters:
- plugin: Pytest fixture: [tests.conftest.fixture_plugin][].
+ mkdocs_conf: Pytest fixture: [tests.conftest.fixture_mkdocs_conf][].
Returns:
A Markdown instance.
"""
- return plugin.md
+ return Markdown(extensions=mkdocs_conf["markdown_extensions"], extension_configs=mkdocs_conf["mdx_configs"])
@pytest.fixture(name="handler")
-def fixture_handler(plugin):
+def fixture_handler(plugin: MkdocstringsPlugin, ext_markdown: Markdown) -> PythonHandler:
"""Return a handler instance.
Parameters:
@@ -86,5 +92,5 @@ def fixture_handler(plugin):
A handler instance.
"""
handler = plugin.handlers.get_handler("python")
- handler._update_env(plugin.md, plugin.handlers._config) # noqa: WPS437
- return handler
+ handler._update_env(ext_markdown, plugin.handlers._config)
+ return handler # type: ignore[return-value]
diff --git a/tests/test_handler.py b/tests/test_handler.py
index 93148d5f..fc31942c 100644
--- a/tests/test_handler.py
+++ b/tests/test_handler.py
@@ -1,35 +1,41 @@
"""Tests for the `handler` module."""
+from __future__ import annotations
+
import os
from glob import glob
+from typing import TYPE_CHECKING
import pytest
from griffe.docstrings.dataclasses import DocstringSectionExamples, DocstringSectionKind
from mkdocstrings_handlers.python.handler import CollectionError, PythonHandler, get_handler
+if TYPE_CHECKING:
+ from pathlib import Path
+
-def test_collect_missing_module():
+def test_collect_missing_module() -> None:
"""Assert error is raised for missing modules."""
handler = get_handler(theme="material")
with pytest.raises(CollectionError):
handler.collect("aaaaaaaa", {})
-def test_collect_missing_module_item():
+def test_collect_missing_module_item() -> None:
"""Assert error is raised for missing items within existing modules."""
handler = get_handler(theme="material")
with pytest.raises(CollectionError):
handler.collect("mkdocstrings.aaaaaaaa", {})
-def test_collect_module():
+def test_collect_module() -> None:
"""Assert existing module can be collected."""
handler = get_handler(theme="material")
assert handler.collect("mkdocstrings", {})
-def test_collect_with_null_parser():
+def test_collect_with_null_parser() -> None:
"""Assert we can pass `None` as parser when collecting."""
handler = get_handler(theme="material")
assert handler.collect("mkdocstrings", {"docstring_style": None})
@@ -44,7 +50,7 @@ def test_collect_with_null_parser():
],
indirect=["handler"],
)
-def test_render_docstring_examples_section(handler):
+def test_render_docstring_examples_section(handler: PythonHandler) -> None:
"""Assert docstrings' examples section can be rendered.
Parameters:
@@ -63,7 +69,7 @@ def test_render_docstring_examples_section(handler):
assert "Hello" in rendered
-def test_expand_globs(tmp_path):
+def test_expand_globs(tmp_path: Path) -> None:
"""Assert globs are correctly expanded.
Parameters:
@@ -81,14 +87,14 @@ def test_expand_globs(tmp_path):
handler = PythonHandler(
handler="python",
theme="material",
- config_file_path=tmp_path / "mkdocs.yml",
+ config_file_path=str(tmp_path.joinpath("mkdocs.yml")),
paths=["*exp*"],
)
- for path in globbed_paths: # noqa: WPS440
- assert str(path) in handler._paths # noqa: WPS437
+ for path in globbed_paths:
+ assert str(path) in handler._paths
-def test_expand_globs_without_changing_directory():
+def test_expand_globs_without_changing_directory() -> None:
"""Assert globs are correctly expanded when we are already in the right directory."""
handler = PythonHandler(
handler="python",
@@ -97,4 +103,4 @@ def test_expand_globs_without_changing_directory():
paths=["*.md"],
)
for path in list(glob(os.path.abspath(".") + "/*.md")):
- assert path in handler._paths # noqa: WPS437
+ assert path in handler._paths
diff --git a/tests/test_rendering.py b/tests/test_rendering.py
index 533eaf34..45b6048b 100644
--- a/tests/test_rendering.py
+++ b/tests/test_rendering.py
@@ -1,14 +1,17 @@
"""Tests for the `rendering` module."""
+from __future__ import annotations
+
import re
from dataclasses import dataclass
+from typing import Any
import pytest
from mkdocstrings_handlers.python import rendering
-def test_format_code_and_signature():
+def test_format_code_and_signature() -> None:
"""Assert code and signatures can be Black-formatted."""
assert rendering.do_format_code("print('Hello')", 100)
assert rendering.do_format_code('print("Hello")', 100)
@@ -28,7 +31,7 @@ class _FakeObject:
(["aa", "ab", "ac", "da"], {"members_list": ["aa", "ab"]}, {"aa", "ab"}),
],
)
-def test_filter_objects(names, filter_params, expected_names):
+def test_filter_objects(names: list[str], filter_params: dict[str, Any], expected_names: set[str]) -> None:
"""Assert the objects filter works correctly.
Parameters:
@@ -37,6 +40,6 @@ def test_filter_objects(names, filter_params, expected_names):
expected_names: Names expected to be kept.
"""
objects = {name: _FakeObject(name) for name in names}
- filtered = rendering.do_filter_objects(objects, **filter_params)
+ filtered = rendering.do_filter_objects(objects, **filter_params) # type: ignore[arg-type]
filtered_names = {obj.name for obj in filtered}
assert set(filtered_names) == set(expected_names)
diff --git a/tests/test_themes.py b/tests/test_themes.py
index b1e7d5d5..bedcc806 100644
--- a/tests/test_themes.py
+++ b/tests/test_themes.py
@@ -1,9 +1,16 @@
"""Tests for the different themes we claim to support."""
+from __future__ import annotations
+
import sys
+from typing import TYPE_CHECKING
import pytest
+if TYPE_CHECKING:
+ from markdown import Markdown
+ from mkdocstrings.plugin import MkdocstringsPlugin
+
@pytest.mark.parametrize(
"plugin",
@@ -27,7 +34,7 @@
],
)
@pytest.mark.skipif(sys.version_info < (3, 7), reason="material is not installed on Python 3.6")
-def test_render_themes_templates_python(module, plugin):
+def test_render_themes_templates_python(module: str, plugin: MkdocstringsPlugin, ext_markdown: Markdown) -> None:
"""Test rendering of a given theme's templates.
Parameters:
@@ -35,6 +42,6 @@ def test_render_themes_templates_python(module, plugin):
plugin: Pytest fixture: [tests.conftest.fixture_plugin][].
"""
handler = plugin.handlers.get_handler("python")
- handler._update_env(plugin.md, plugin.handlers._config) # noqa: WPS437
+ handler._update_env(ext_markdown, plugin.handlers._config)
data = handler.collect(module, {})
handler.render(data, {})