From 5035e9269fe11664fd25e438ac8f746721b3de0a Mon Sep 17 00:00:00 2001 From: Waylan Limberg Date: Tue, 31 Oct 2023 15:26:07 -0400 Subject: [PATCH 1/6] fix: Make extension paths relative to config file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #112: https://github.com/mkdocstrings/python/pull/112 Co-authored-by: Timothée Mazzucotelli --- src/mkdocstrings_handlers/python/handler.py | 34 ++++++++++++++++-- tests/test_handler.py | 40 +++++++++++++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/src/mkdocstrings_handlers/python/handler.py b/src/mkdocstrings_handlers/python/handler.py index 056429e8..169546fd 100644 --- a/src/mkdocstrings_handlers/python/handler.py +++ b/src/mkdocstrings_handlers/python/handler.py @@ -9,7 +9,7 @@ import sys from collections import ChainMap from contextlib import suppress -from typing import TYPE_CHECKING, Any, BinaryIO, ClassVar, Iterator, Mapping +from typing import TYPE_CHECKING, Any, BinaryIO, ClassVar, Iterator, Mapping, Sequence from griffe.collections import LinesCollection, ModulesCollection from griffe.docstrings.parsers import Parser @@ -265,8 +265,9 @@ def collect(self, identifier: str, config: Mapping[str, Any]) -> CollectorItem: parser = parser_name and Parser(parser_name) if unknown_module: + extensions = self.normalize_extension_paths(final_config.get("extensions", [])) loader = GriffeLoader( - extensions=load_extensions(final_config.get("extensions", [])), + extensions=load_extensions(extensions), search_paths=self._paths, docstring_parser=parser, docstring_options=parser_options, @@ -369,6 +370,35 @@ def get_anchors(self, data: CollectorItem) -> tuple[str, ...]: # noqa: D102 (ig return tuple(anchors) return tuple(anchors) + def normalize_extension_paths(self, extensions: Sequence) -> Sequence: + """Resolve extension paths relative to config file.""" + if self._config_file_path is None: + return extensions + + base_path = os.path.dirname(self._config_file_path) + normalized = [] + + for ext in extensions: + if isinstance(ext, dict): + pth, options = next(iter(ext.items())) + pth = str(pth) + else: + pth = str(ext) + options = None + + if pth.endswith(".py") or ".py:" in pth or "/" in pth or "\\" in pth: # noqa: SIM102 + # This is a sytem path. Normalize it. + if not os.path.isabs(pth): + # Make path absolute relative to config file path. + pth = os.path.normpath(os.path.join(base_path, pth)) + + if options is not None: + normalized.append({pth: options}) + else: + normalized.append(pth) + + return normalized + def get_handler( *, diff --git a/tests/test_handler.py b/tests/test_handler.py index 4971e132..e1d92c18 100644 --- a/tests/test_handler.py +++ b/tests/test_handler.py @@ -105,3 +105,43 @@ def test_expand_globs_without_changing_directory() -> None: ) for path in list(glob(os.path.abspath(".") + "/*.md")): assert path in handler._paths + + +@pytest.mark.parametrize( + ("expect_change", "extension"), + [ + (True, "extension.py"), + (True, "extension.py:SomeExtension"), + (True, "path/to/extension.py"), + (True, "path/to/extension.py:SomeExtension"), + (True, {"extension.py": {"option": "value"}}), + (True, {"extension.py:SomeExtension": {"option": "value"}}), + (True, {"path/to/extension.py": {"option": "value"}}), + (True, {"path/to/extension.py:SomeExtension": {"option": "value"}}), + (False, "/absolute/path/to/extension.py"), + (False, "/absolute/path/to/extension.py:SomeExtension"), + (False, {"/absolute/path/to/extension.py": {"option": "value"}}), + (False, {"/absolute/path/to/extension.py:SomeExtension": {"option": "value"}}), + (False, "dot.notation.path.to.extension"), + (False, "dot.notation.path.to.pyextension"), + (False, {"dot.notation.path.to.extension": {"option": "value"}}), + (False, {"dot.notation.path.to.pyextension": {"option": "value"}}), + ], +) +def test_extension_paths(tmp_path: Path, expect_change: bool, extension: str | dict) -> None: + """Assert extension paths are resolved relative to config file.""" + handler = get_handler( + theme="material", + config_file_path=str(tmp_path.joinpath("mkdocs.yml")), + ) + normalized = handler.normalize_extension_paths([extension])[0] + if expect_change: + if isinstance(normalized, str) and isinstance(extension, str): + assert normalized == str(tmp_path.joinpath(extension)) + elif isinstance(normalized, dict) and isinstance(extension, dict): + pth, options = next(iter(extension.items())) + assert normalized == {str(tmp_path.joinpath(pth)): options} + else: + raise ValueError("Normalization must not change extension items type") + else: + assert normalized == extension From a9078a020e984f7d94e531644e613c08a5fc35ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sun, 12 Nov 2023 18:03:31 +0100 Subject: [PATCH 2/6] tests: Modernize test fixtures --- tests/conftest.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 0801ec0b..1c1a1c54 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,12 +7,12 @@ import pytest from markdown.core import Markdown -from mkdocs import config -from mkdocs.config.defaults import get_schema +from mkdocs.config.defaults import MkDocsConfig if TYPE_CHECKING: from pathlib import Path + from mkdocs import config from mkdocstrings.plugin import MkdocstringsPlugin from mkdocstrings_handlers.python.handler import PythonHandler @@ -29,12 +29,11 @@ def fixture_mkdocs_conf(request: pytest.FixtureRequest, tmp_path: Path) -> Itera Yields: MkDocs config. """ - conf = config.Config(schema=get_schema()) # type: ignore[call-arg] + conf = MkDocsConfig() while hasattr(request, "_parent_request") and hasattr(request._parent_request, "_parent_request"): request = request._parent_request conf_dict = { - "config_file_path": "mkdocs.yml", "site_name": "foo", "site_url": "https://example.org/", "site_dir": str(tmp_path), From 574b234ec4d13dfde5ec906b356c71c3fcd63843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sun, 12 Nov 2023 18:20:12 +0100 Subject: [PATCH 3/6] chore: Template upgrade --- .copier-answers.yml | 4 +- .github/ISSUE_TEMPLATE/bug_report.md | 73 +++++++---- .github/ISSUE_TEMPLATE/config.yml | 5 + .github/ISSUE_TEMPLATE/feature_request.md | 23 ++-- CONTRIBUTING.md | 5 +- Makefile | 3 +- README.md | 2 +- config/git-changelog.toml | 8 ++ config/ruff.toml | 3 + config/vscode/launch.json | 36 ++++++ config/vscode/settings.json | 52 ++++++++ config/vscode/tasks.json | 93 ++++++++++++++ docs/css/insiders.css | 5 +- docs/insiders/index.md | 3 - duties.py | 142 ++++++++++------------ mkdocs.insiders.yml | 4 - mkdocs.yml | 11 +- pyproject.toml | 43 ++++--- scripts/gen_credits.py | 19 ++- scripts/gen_ref_nav.py | 10 +- scripts/insiders.py | 6 +- src/mkdocstrings_handlers/debug.py | 106 ++++++++++++++++ 22 files changed, 496 insertions(+), 160 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 config/git-changelog.toml create mode 100644 config/vscode/launch.json create mode 100644 config/vscode/settings.json create mode 100644 config/vscode/tasks.json delete mode 100644 mkdocs.insiders.yml create mode 100644 src/mkdocstrings_handlers/debug.py diff --git a/.copier-answers.yml b/.copier-answers.yml index da168350..58ab94af 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: 0.16.10 +_commit: 1.1.3 _src_path: gh:pawamoy/copier-pdm author_email: pawamoy@pm.me author_fullname: Timothée Mazzucotelli @@ -9,8 +9,10 @@ copyright_holder: Timothée Mazzucotelli copyright_holder_email: pawamoy@pm.me copyright_license: ISC License insiders: true +insiders_repository_name: mkdocstrings-python project_description: A Python handler for mkdocstrings. project_name: mkdocstrings-python +public_release: true python_package_command_line_name: '' python_package_distribution_name: mkdocstrings-python python_package_import_name: mkdocstrings_handlers diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index ad93416e..ac47315f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,32 +1,61 @@ --- name: Bug report -about: Create a report to help us improve -title: '' +about: Create a bug report to help us improve. +title: "bug: " labels: unconfirmed -assignees: '' - +assignees: [pawamoy] --- -**Describe the bug** -A clear and concise description of what the bug is. +### Description of the bug + + +### To Reproduce + + +``` +WRITE MRE / INSTRUCTIONS HERE +``` + +### Full traceback + + +
Full traceback + +```python +PASTE TRACEBACK HERE +``` + +
-**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Run command '...' -3. Scroll down to '...' -4. See error +### Expected behavior + -**Expected behavior** -A clear and concise description of what you expected to happen. +### Environment information + -**Screenshots** -If applicable, add screenshots to help explain your problem. +```bash +python -m mkdocstrings_handlers.debug # | xclip -selection clipboard +``` -**System (please complete the following information):** -- `mkdocstrings-python` version: [e.g. 0.2.1] -- Python version: [e.g. 3.8] -- OS: [Windows/Linux] +PASTE OUTPUT HERE -**Additional context** -Add any other context about the problem here. +### Additional context + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..9c9765bc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: +- name: I have a question / I need help + url: https://github.com/mkdocstrings/python/discussions/new?category=q-a + about: Ask and answer questions in the Discussions tab. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 4fe86d5e..2df98fbc 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,20 +1,19 @@ --- name: Feature request -about: Suggest an idea for this project -title: '' +about: Suggest an idea for this project. +title: "feature: " labels: feature -assignees: '' - +assignees: pawamoy --- -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] +### Is your feature request related to a problem? Please describe. + -**Describe the solution you'd like** -A clear and concise description of what you want to happen. +### Describe the solution you'd like + -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. +### Describe alternatives you've considered + -**Additional context** -Add any other context or screenshots about the feature request here. +### Additional context + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0dafa847..dfe5a910 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,8 +44,9 @@ on multiple Python versions, you run the task directly with `pdm 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, -[see examples of tasks and run configurations](https://pawamoy.github.io/copier-pdm/work/#vscode-setup). +If you work in VSCode, we provide +[an action to configure VSCode](https://pawamoy.github.io/copier-pdm/work/#vscode-setup) +for the project. ## Development diff --git a/Makefile b/Makefile index 7e8de7cc..437880eb 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,8 @@ BASIC_DUTIES = \ docs \ docs-deploy \ format \ - release + release \ + vscode QUALITY_DUTIES = \ check-quality \ diff --git a/README.md b/README.md index 7535de03..6b3afb5f 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ gitpod - + gitter

diff --git a/config/git-changelog.toml b/config/git-changelog.toml new file mode 100644 index 00000000..44e2b1fb --- /dev/null +++ b/config/git-changelog.toml @@ -0,0 +1,8 @@ +bump = "auto" +convention = "angular" +in-place = true +output = "CHANGELOG.md" +parse-refs = false +parse-trailers = true +sections = ["build", "deps", "feat", "fix", "refactor"] +template = "keepachangelog" diff --git a/config/ruff.toml b/config/ruff.toml index 9925518c..99efa62b 100644 --- a/config/ruff.toml +++ b/config/ruff.toml @@ -77,6 +77,9 @@ ignore = [ "src/*/cli.py" = [ "T201", # Print statement ] +"src/*/debug.py" = [ + "T201", # Print statement +] "scripts/*.py" = [ "INP001", # File is part of an implicit namespace package "T201", # Print statement diff --git a/config/vscode/launch.json b/config/vscode/launch.json new file mode 100644 index 00000000..2e0d651e --- /dev/null +++ b/config/vscode/launch.json @@ -0,0 +1,36 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "python (current file)", + "type": "python", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "justMyCode": false + }, + { + "name": "test", + "type": "python", + "request": "launch", + "module": "pytest", + "justMyCode": false, + "args": [ + "-c=config/pytest.ini", + "-vvv", + "--no-cov", + "--dist=no", + "tests", + "-k=${input:tests_selection}" + ] + } + ], + "inputs": [ + { + "id": "tests_selection", + "type": "promptString", + "description": "Tests selection", + "default": "" + } + ] +} \ No newline at end of file diff --git a/config/vscode/settings.json b/config/vscode/settings.json new file mode 100644 index 00000000..17beee4b --- /dev/null +++ b/config/vscode/settings.json @@ -0,0 +1,52 @@ +{ + "files.watcherExclude": { + "**/__pypackages__/**": true, + "**/.venv*/**": true, + "**/venv*/**": true + }, + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + }, + "python.autoComplete.extraPaths": [ + "__pypackages__/3.8/lib", + "__pypackages__/3.9/lib", + "__pypackages__/3.10/lib", + "__pypackages__/3.11/lib", + "__pypackages__/3.12/lib" + ], + "python.analysis.extraPaths": [ + "__pypackages__/3.8/lib", + "__pypackages__/3.9/lib", + "__pypackages__/3.10/lib", + "__pypackages__/3.11/lib", + "__pypackages__/3.12/lib" + ], + "black-formatter.args": [ + "--config=config/black.toml" + ], + "mypy-type-checker.args": [ + "--config-file=config/mypy.ini" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, + "python.testing.pytestArgs": [ + "--config-file=config/pytest.ini" + ], + "ruff.format.args": [ + "--config=config/ruff.toml" + ], + "ruff.lint.args": [ + "--config=config/ruff.toml" + ], + "yaml.schemas": { + "https://squidfunk.github.io/mkdocs-material/schema.json": "mkdocs.yml" + }, + "yaml.customTags": [ + "!ENV scalar", + "!ENV sequence", + "!relative scalar", + "tag:yaml.org,2002:python/name:materialx.emoji.to_svg", + "tag:yaml.org,2002:python/name:materialx.emoji.twemoji", + "tag:yaml.org,2002:python/name:pymdownx.superfences.fence_code_format" + ] +} \ No newline at end of file diff --git a/config/vscode/tasks.json b/config/vscode/tasks.json new file mode 100644 index 00000000..80cd13d2 --- /dev/null +++ b/config/vscode/tasks.json @@ -0,0 +1,93 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "changelog", + "type": "shell", + "command": "pdm run duty changelog" + }, + { + "label": "check", + "type": "shell", + "command": "pdm run duty check" + }, + { + "label": "check-quality", + "type": "shell", + "command": "pdm run duty check-quality" + }, + { + "label": "check-types", + "type": "shell", + "command": "pdm run duty check-types" + }, + { + "label": "check-docs", + "type": "shell", + "command": "pdm run duty check-docs" + }, + { + "label": "check-dependencies", + "type": "shell", + "command": "pdm run duty check-dependencies" + }, + { + "label": "check-api", + "type": "shell", + "command": "pdm run duty check-api" + }, + { + "label": "clean", + "type": "shell", + "command": "pdm run duty clean" + }, + { + "label": "docs", + "type": "shell", + "command": "pdm run duty docs" + }, + { + "label": "docs-deploy", + "type": "shell", + "command": "pdm run duty docs-deploy" + }, + { + "label": "format", + "type": "shell", + "command": "pdm run duty format" + }, + { + "label": "lock", + "type": "shell", + "command": "pdm lock -G:all" + }, + { + "label": "release", + "type": "shell", + "command": "pdm run duty release ${input:version}" + }, + { + "label": "setup", + "type": "shell", + "command": "bash scripts/setup.sh" + }, + { + "label": "test", + "type": "shell", + "command": "pdm run duty test coverage", + "group": "test" + }, + { + "label": "vscode", + "type": "shell", + "command": "pdm run duty vscode" + } + ], + "inputs": [ + { + "id": "version", + "type": "promptString", + "description": "Version" + } + ] +} \ No newline at end of file diff --git a/docs/css/insiders.css b/docs/css/insiders.css index b5547bd1..e7b9c74f 100644 --- a/docs/css/insiders.css +++ b/docs/css/insiders.css @@ -53,11 +53,10 @@ a.insiders { } .sponsorship-item { - float: left; border-radius: 100%; - display: block; + display: inline-block; height: 1.6rem; - margin: .2rem; + margin: 0.1rem; overflow: hidden; width: 1.6rem; } diff --git a/docs/insiders/index.md b/docs/insiders/index.md index a6df4a4d..7c69b590 100644 --- a/docs/insiders/index.md +++ b/docs/insiders/index.md @@ -123,9 +123,6 @@ You can cancel your sponsorship anytime.[^5]
-
-
- If you sponsor publicly, you're automatically added here with a link to your profile and avatar to show your support for *mkdocstrings-python*. diff --git a/duties.py b/duties.py index 8e3dbf64..cfdc9376 100644 --- a/duties.py +++ b/duties.py @@ -4,12 +4,13 @@ import os import sys +from contextlib import contextmanager from importlib.metadata import version as pkgversion from pathlib import Path -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Iterator from duty import duty -from duty.callables import black, blacken_docs, coverage, lazy, mkdocs, mypy, pytest, ruff, safety +from duty.callables import black, coverage, lazy, mkdocs, mypy, pytest, ruff, safety if TYPE_CHECKING: from duty.context import Context @@ -31,32 +32,16 @@ def pyprefix(title: str) -> str: # noqa: D103 return title -def merge(d1: Any, d2: Any) -> Any: # noqa: D103 - basic_types = (int, float, str, bool, complex) - if isinstance(d1, dict) and isinstance(d2, dict): - for key, value in d2.items(): - if key in d1: - if isinstance(d1[key], basic_types): - d1[key] = value - else: - d1[key] = merge(d1[key], value) - else: - d1[key] = value - return d1 - if isinstance(d1, list) and isinstance(d2, list): - return d1 + d2 - return d2 - - -def mkdocs_config() -> str: # noqa: D103 - import mergedeep - - # force YAML loader to merge arrays - mergedeep.merge = merge - +@contextmanager +def material_insiders() -> Iterator[bool]: # noqa: D103 if "+insiders" in pkgversion("mkdocs-material"): - return "mkdocs.insiders.yml" - return "mkdocs.yml" + os.environ["MATERIAL_INSIDERS"] = "true" + try: + yield True + finally: + os.environ.pop("MATERIAL_INSIDERS") + else: + yield False @duty @@ -66,23 +51,9 @@ def changelog(ctx: Context) -> None: Parameters: ctx: The context instance (passed automatically). """ - from git_changelog.cli import build_and_render + from git_changelog.cli import main as git_changelog - git_changelog = lazy(build_and_render, name="git_changelog") - ctx.run( - 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", - ) + ctx.run(git_changelog, args=[[]], title="Updating changelog") @duty(pre=["check_quality", "check_types", "check_docs", "check_dependencies", "check-api"]) @@ -139,12 +110,12 @@ def check_docs(ctx: Context) -> None: """ Path("htmlcov").mkdir(parents=True, exist_ok=True) Path("htmlcov/index.html").touch(exist_ok=True) - config = mkdocs_config() - ctx.run( - mkdocs.build(strict=True, config_file=config, verbose=True), - title=pyprefix("Building documentation"), - command=f"mkdocs build -vsf {config}", - ) + with material_insiders(): + ctx.run( + mkdocs.build(strict=True, verbose=True), + title=pyprefix("Building documentation"), + command="mkdocs build -vs", + ) @duty @@ -209,11 +180,12 @@ def docs(ctx: Context, host: str = "127.0.0.1", port: int = 8000) -> None: host: The host to serve the docs from. port: The port to serve the docs on. """ - ctx.run( - mkdocs.serve(dev_addr=f"{host}:{port}", config_file=mkdocs_config()), - title="Serving documentation", - capture=False, - ) + with material_insiders(): + ctx.run( + mkdocs.serve(dev_addr=f"{host}:{port}"), + title="Serving documentation", + capture=False, + ) @duty @@ -224,22 +196,22 @@ def docs_deploy(ctx: Context) -> None: ctx: The context instance (passed automatically). """ os.environ["DEPLOY"] = "true" - config_file = mkdocs_config() - if config_file == "mkdocs.yml": - ctx.run(lambda: False, title="Not deploying docs without Material for MkDocs Insiders!") - origin = ctx.run("git config --get remote.origin.url", silent=True) - if "pawamoy-insiders/mkdocstrings-python" in origin: - ctx.run("git remote add upstream git@github.com:mkdocstrings/python", silent=True, nofail=True) - ctx.run( - mkdocs.gh_deploy(config_file=config_file, remote_name="upstream", force=True), - title="Deploying documentation", - ) - else: - ctx.run( - lambda: False, - title="Not deploying docs from public repository (do that from insiders instead!)", - nofail=True, - ) + with material_insiders() as insiders: + if not insiders: + ctx.run(lambda: False, title="Not deploying docs without Material for MkDocs Insiders!") + origin = ctx.run("git config --get remote.origin.url", silent=True) + if "pawamoy-insiders/mkdocstrings-python" in origin: + ctx.run("git remote add upstream git@github.com:mkdocstrings/python", silent=True, nofail=True) + ctx.run( + mkdocs.gh_deploy(remote_name="upstream", force=True), + title="Deploying documentation", + ) + else: + ctx.run( + lambda: False, + title="Not deploying docs from public repository (do that from insiders instead!)", + nofail=True, + ) @duty @@ -254,11 +226,6 @@ def format(ctx: Context) -> None: 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, skip_errors=True), - title="Formatting docs", - nofail=True, - ) @duty(post=["docs-deploy"]) @@ -311,3 +278,28 @@ def test(ctx: Context, match: str = "") -> None: title=pyprefix("Running tests"), command=f"pytest -c config/pytest.ini -n auto -k{match!r} --color=yes tests", ) + + +@duty +def vscode(ctx: Context) -> None: + """Configure VSCode. + + This task will overwrite the following files, + so make sure to back them up: + + - `.vscode/launch.json` + - `.vscode/settings.json` + - `.vscode/tasks.json` + + Parameters: + ctx: The context instance (passed automatically). + """ + + def update_config(filename: str) -> None: + source_file = Path("config", "vscode", filename) + target_file = Path(".vscode", filename) + target_file.parent.mkdir(exist_ok=True) + target_file.write_text(source_file.read_text()) + + for filename in ("launch.json", "settings.json", "tasks.json"): + ctx.run(update_config, args=[filename], title=f"Update .vscode/{filename}") diff --git a/mkdocs.insiders.yml b/mkdocs.insiders.yml deleted file mode 100644 index 9afba9aa..00000000 --- a/mkdocs.insiders.yml +++ /dev/null @@ -1,4 +0,0 @@ -INHERIT: mkdocs.yml - -plugins: -- typeset diff --git a/mkdocs.yml b/mkdocs.yml index 17b92494..6c069795 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -109,11 +109,12 @@ markdown_extensions: kwds: case: lower - pymdownx.emoji: - emoji_index: !!python/name:materialx.emoji.twemoji - emoji_generator: !!python/name:materialx.emoji.to_svg + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg - pymdownx.magiclink - pymdownx.snippets: auto_append: [docs/.glossary.md] + base_path: [!relative $config_dir] check_paths: true - pymdownx.superfences - pymdownx.tabbed: @@ -134,7 +135,7 @@ plugins: scripts: - scripts/gen_ref_nav.py - literate-nav: - nav_file: SUMMARY.txt + nav_file: SUMMARY.md - coverage - mkdocstrings: handlers: @@ -167,6 +168,10 @@ plugins: repository: mkdocstrings/python - minify: minify_html: !ENV [DEPLOY, false] +- group: + enabled: !ENV [MATERIAL_INSIDERS, false] + plugins: + - typeset extra: social: diff --git a/pyproject.toml b/pyproject.toml index d7f58317..29927bde 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,38 +59,37 @@ duty = ["duty>=0.10"] ci-quality = ["mkdocstrings-python[duty,docs,quality,typing,security]"] ci-tests = ["mkdocstrings-python[duty,docs,tests]"] docs = [ - "black>=23.1", - "markdown-callouts>=0.2", - "markdown-exec>=0.5", + "black>=23.9", + "markdown-callouts>=0.3", + "markdown-exec>=1.7", "mkdocs>=1.5", - "mkdocs-coverage>=0.2", - "mkdocs-gen-files>=0.3", - "mkdocs-git-committers-plugin-2>=1.1", - "mkdocs-literate-nav>=0.4", - "mkdocs-material>=7.3", - "mkdocs-minify-plugin>=0.6.4", - "toml>=0.10", + "mkdocs-coverage>=1.0", + "mkdocs-gen-files>=0.5", + "mkdocs-git-committers-plugin-2>=1.2", + "mkdocs-literate-nav>=0.6", + "mkdocs-material>=9.4", + "mkdocs-minify-plugin>=0.7", + "tomli>=2.0; python_version < '3.11'", ] maintain = [ - "black>=23.1", - "blacken-docs>=1.13", - "git-changelog>=1.0", + "black>=23.9", + "blacken-docs>=1.16", + "git-changelog>=2.3", ] quality = [ - "ruff>=0.0.246", + "ruff>=0.0", ] tests = [ - "pytest>=6.2", - "pytest-cov>=3.0", - "pytest-randomly>=3.10", - "pytest-xdist>=2.4", + "pytest>=7.4", + "pytest-cov>=4.1", + "pytest-randomly>=3.15", + "pytest-xdist>=3.3", ] typing = [ - "mypy>=0.911", - "types-markdown>=3.3", + "mypy>=1.5", + "types-markdown>=3.5", "types-pyyaml>=6.0", - "types-toml>=0.10", ] security = [ - "safety>=2", + "safety>=2.3", ] diff --git a/scripts/gen_credits.py b/scripts/gen_credits.py index 459f2939..bf35f0da 100644 --- a/scripts/gen_credits.py +++ b/scripts/gen_credits.py @@ -2,22 +2,31 @@ from __future__ import annotations +import os import re +import sys from importlib.metadata import PackageNotFoundError, metadata 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 -project_dir = Path(".") -pyproject = toml.load(project_dir / "pyproject.toml") +# TODO: Remove once support for Python 3.10 is dropped. +if sys.version_info >= (3, 11): + import tomllib +else: + import tomli as tomllib + +project_dir = Path(os.getenv("MKDOCS_CONFIG_DIR", ".")) +with project_dir.joinpath("pyproject.toml").open("rb") as pyproject_file: + pyproject = tomllib.load(pyproject_file) project = pyproject["project"] pdm = pyproject["tool"]["pdm"] -lock_data = toml.load(project_dir / "pdm.lock") +with project_dir.joinpath("pdm.lock").open("rb") as lock_file: + lock_data = tomllib.load(lock_file) lock_pkgs = {pkg["name"].lower(): pkg for pkg in lock_data["package"]} project_name = project["name"] regex = re.compile(r"(?P[\w.-]+)(?P.*)$") @@ -30,7 +39,7 @@ def _get_license(pkg_name: str) -> str: return "?" 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 + # TODO: Remove author logic once all my packages licenses are fixed. author = "" if multiple_lines or not license_name or license_name == "UNKNOWN": for header, value in cast(dict, data).items(): diff --git a/scripts/gen_ref_nav.py b/scripts/gen_ref_nav.py index 713522be..7285ac1c 100644 --- a/scripts/gen_ref_nav.py +++ b/scripts/gen_ref_nav.py @@ -7,9 +7,11 @@ nav = mkdocs_gen_files.Nav() mod_symbol = '' -for path in sorted(Path("src").rglob("*.py")): - module_path = path.relative_to("src").with_suffix("") - doc_path = path.relative_to("src").with_suffix(".md") +src = Path(__file__).parent.parent / "src" + +for path in sorted(src.rglob("*.py")): + module_path = path.relative_to(src).with_suffix("") + doc_path = path.relative_to(src).with_suffix(".md") full_doc_path = Path("reference", doc_path) parts = tuple(module_path.parts) @@ -30,5 +32,5 @@ mkdocs_gen_files.set_edit_path(full_doc_path, ".." / path) -with mkdocs_gen_files.open("reference/SUMMARY.txt", "w") as nav_file: +with mkdocs_gen_files.open("reference/SUMMARY.md", "w") as nav_file: nav_file.writelines(nav.build_literate_nav()) diff --git a/scripts/insiders.py b/scripts/insiders.py index 28ca1c87..8f5e215e 100644 --- a/scripts/insiders.py +++ b/scripts/insiders.py @@ -4,6 +4,7 @@ import json import logging +import os import posixpath from dataclasses import dataclass from datetime import date, datetime, timedelta @@ -115,8 +116,9 @@ def load_goals(data: str, funding: int = 0, project: Project | None = None) -> d def _load_goals_from_disk(path: str, funding: int = 0) -> dict[int, Goal]: + project_dir = os.getenv("MKDOCS_CONFIG_DIR", ".") try: - data = Path(path).read_text() + 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) @@ -159,7 +161,7 @@ def funding_goals(source: str | list[str | tuple[str, str, str]], funding: int = goals[amount] = goal else: goals[amount].features.extend(goal.features) - return goals + return {amount: goals[amount] for amount in sorted(goals)} def feature_list(goals: Iterable[Goal]) -> list[Feature]: diff --git a/src/mkdocstrings_handlers/debug.py b/src/mkdocstrings_handlers/debug.py new file mode 100644 index 00000000..ffebc12e --- /dev/null +++ b/src/mkdocstrings_handlers/debug.py @@ -0,0 +1,106 @@ +"""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.""" + 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-python") -> 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-python"] + variables = ["PYTHONPATH", *[var for var in os.environ if var.startswith("MKDOCSTRINGS_PYTHON")]] + return Environment( + interpreter_name=py_name, + interpreter_version=py_version, + 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}") + 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() From b5bb8a982e7a2ec97c73335e453d0033bf4987b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sun, 12 Nov 2023 18:28:45 +0100 Subject: [PATCH 4/6] refactor: Prepare for Griffe 0.37 --- pyproject.toml | 2 +- src/mkdocstrings_handlers/python/handler.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 29927bde..853cdf9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ classifiers = [ ] dependencies = [ "mkdocstrings>=0.20", - "griffe>=0.35", + "griffe>=0.37", ] [project.urls] diff --git a/src/mkdocstrings_handlers/python/handler.py b/src/mkdocstrings_handlers/python/handler.py index 169546fd..6fc2804f 100644 --- a/src/mkdocstrings_handlers/python/handler.py +++ b/src/mkdocstrings_handlers/python/handler.py @@ -278,8 +278,8 @@ def collect(self, identifier: str, config: Mapping[str, Any]) -> CollectorItem: 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) + loader.load(pre_loaded_module) + loader.load(module_name) except ImportError as error: raise CollectionError(str(error)) from error unresolved, iterations = loader.resolve_aliases( From 3a100406445c2e431cee9683f845fd9d8d2e6736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sun, 12 Nov 2023 18:36:13 +0100 Subject: [PATCH 5/6] chore: Fix debug module location, add packages to debug info --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- src/mkdocstrings_handlers/{ => python}/debug.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/mkdocstrings_handlers/{ => python}/debug.py (97%) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index ac47315f..ca545c26 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -50,7 +50,7 @@ PASTE TRACEBACK HERE redacting sensitive information. --> ```bash -python -m mkdocstrings_handlers.debug # | xclip -selection clipboard +python -m mkdocstrings_handlers.python.debug # | xclip -selection clipboard ``` PASTE OUTPUT HERE diff --git a/src/mkdocstrings_handlers/debug.py b/src/mkdocstrings_handlers/python/debug.py similarity index 97% rename from src/mkdocstrings_handlers/debug.py rename to src/mkdocstrings_handlers/python/debug.py index ffebc12e..7a4e8791 100644 --- a/src/mkdocstrings_handlers/debug.py +++ b/src/mkdocstrings_handlers/python/debug.py @@ -78,7 +78,7 @@ def get_debug_info() -> Environment: Environment information. """ py_name, py_version = _interpreter_name_version() - packages = ["mkdocstrings-python"] + packages = ["mkdocs", "mkdocstrings", "mkdocstrings-python", "griffe"] variables = ["PYTHONPATH", *[var for var in os.environ if var.startswith("MKDOCSTRINGS_PYTHON")]] return Environment( interpreter_name=py_name, From dde658e77ef79e92ed380431f35c478ec0b27d24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sun, 12 Nov 2023 18:52:36 +0100 Subject: [PATCH 6/6] chore: Prepare release 1.7.4 --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab570436..db7fefc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ 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). +## [1.7.4](https://github.com/mkdocstrings/python/releases/tag/1.7.4) - 2023-11-12 + +[Compare with 1.7.3](https://github.com/mkdocstrings/python/compare/1.7.3...1.7.4) + +### Bug Fixes + +- Make extension paths relative to config file ([5035e92](https://github.com/mkdocstrings/python/commit/5035e9269fe11664fd25e438ac8f746721b3de0a) by Waylan Limberg). [PR #112](https://github.com/mkdocstrings/python/pull/112), Co-authored-by: Timothée Mazzucotelli + +### Code Refactoring + +- Prepare for Griffe 0.37 ([b5bb8a9](https://github.com/mkdocstrings/python/commit/b5bb8a982e7a2ec97c73335e453d0033bf4987b6) by Timothée Mazzucotelli). + ## [1.7.3](https://github.com/mkdocstrings/python/releases/tag/1.7.3) - 2023-10-09 [Compare with 1.7.2](https://github.com/mkdocstrings/python/compare/1.7.2...1.7.3)