From 252b9c6c2dd7ea9818db55ae85cb5d346732bedb Mon Sep 17 00:00:00 2001 From: davfsa Date: Sun, 26 Jun 2022 15:17:34 +0200 Subject: [PATCH 01/13] docs: Fix edit URIs for autogenerated docs PR #443: https://github.com/mkdocstrings/mkdocstrings/pull/443 --- docs/gen_ref_nav.py | 2 +- docs/recipes.md | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/docs/gen_ref_nav.py b/docs/gen_ref_nav.py index d8e80aa4..6b81859d 100644 --- a/docs/gen_ref_nav.py +++ b/docs/gen_ref_nav.py @@ -24,7 +24,7 @@ ident = ".".join(parts) print("::: " + ident, file=fd) - mkdocs_gen_files.set_edit_path(full_doc_path, path) + mkdocs_gen_files.set_edit_path(full_doc_path, Path("../") / path) with mkdocs_gen_files.open("reference/SUMMARY.md", "w") as nav_file: nav_file.writelines(nav.build_literate_nav()) diff --git a/docs/recipes.md b/docs/recipes.md index 5f006057..09c012e3 100644 --- a/docs/recipes.md +++ b/docs/recipes.md @@ -107,6 +107,40 @@ for path in sorted(Path("src").rglob("*.py")): # (1) 8. Actually write to the magic file. 9. We can even set the `edit_uri` on the pages. +> NOTE: +> It is important to look out for correct edit page behaviour when using generated pages. +> For example, if we have `edit_uri` set to `blob/master/docs/` and the following +> file structure: +> +> ``` +> πŸ“ repo +> β”œβ”€ πŸ“„ mkdocs.yml +> β”‚ +> β”œβ”€ πŸ“ docs +> β”‚ β”œβ”€β•΄πŸ“„ index.md +> β”‚ β””β”€β•΄πŸ“„ gen_ref_pages.py +> β”‚ +> β””β”€β•΄πŸ“ src +> β””β”€β•΄πŸ“ project +> β”œβ”€β•΄πŸ“„ lorem.py +> β”œβ”€β•΄πŸ“„ ipsum.py +> β”œβ”€β•΄πŸ“„ dolor.py +> β”œβ”€β•΄πŸ“„ sit.py +> β””β”€β•΄πŸ“„ amet.py +> ``` +> +> Then we will have to change our `set_edit_path` call to: +> +> ```python +> mkdocs_gen_files.set_edit_path(full_doc_path, Path("../") / path) # (1) +> ``` +> 1. Path can be used to traverse the structure in any way you may need, but +> remember to use relative paths! +> +> so that it correctly sets the edit path of (for example) `lorem.py` to +> `/blob/master/src/project/lorem.py` instead of +> `/blob/master/docs/src/project/lorem.py`. + With this script, a `reference` folder is automatically created each time we build our docs. This folder contains a Markdown page for each of our source modules, and each of these pages From 28540bb3c1b935dabdc23bb9e25ad02450615eda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Wed, 17 Aug 2022 21:56:50 +0200 Subject: [PATCH 02/13] docs: Update note about Python collectors --- .github/ISSUE_TEMPLATE/bug_report.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 275afae3..149c6ce0 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,7 +7,8 @@ assignees: '' --- -**Please open an issue on [pytkdocs](https://github.com/pawamoy/pytkdocs/issues) instead +**Please open an issue on [Griffe](https://github.com/mkdocstrings/griffe/issues) (new Python handler) +or [pytkdocs](https://github.com/mkdocstrings/pytkdocs/issues) (legacy Python handler) instead if this is related to Python docstrings parsing or the collection of Python objects!** **Describe the bug** From 03dd7a6e4fefa44889bda9899d9b698bcfd07990 Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Sat, 20 Aug 2022 12:51:53 +0200 Subject: [PATCH 03/13] refactor: Report usage-based warnings as user-facing messages The other warnings are developer-facing and should indeed remain as DeprecationWarning. PR #464: https://github.com/mkdocstrings/mkdocstrings/pull/464 --- src/mkdocstrings/extension.py | 12 +++++++----- src/mkdocstrings/plugin.py | 17 +++++++++++------ tests/test_extension.py | 12 +++++++----- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/mkdocstrings/extension.py b/src/mkdocstrings/extension.py index be0c48bb..02b94fc9 100644 --- a/src/mkdocstrings/extension.py +++ b/src/mkdocstrings/extension.py @@ -21,10 +21,10 @@ option_x: etc ``` """ +import functools import re from collections import ChainMap from typing import Any, MutableSequence, Tuple -from warnings import warn from xml.etree.ElementTree import Element import yaml @@ -182,10 +182,7 @@ def _process_block( options = ChainMap(local_options, deprecated_local_options, global_options, deprecated_global_options) if deprecated_global_options or deprecated_local_options: - warn( - "'selection' and 'rendering' are deprecated and merged into a single 'options' YAML key", - DeprecationWarning, - ) + self._warn_about_options_key() if heading_level: options = ChainMap(options, {"heading_level": heading_level}) # like setdefault @@ -216,6 +213,11 @@ def _process_block( return rendered, handler, data + @classmethod + @functools.lru_cache(maxsize=None) # Warn only once + def _warn_about_options_key(cls): + log.info("DEPRECATION: 'selection' and 'rendering' are deprecated and merged into a single 'options' YAML key") + class _PostProcessor(Treeprocessor): def run(self, root: Element): diff --git a/src/mkdocstrings/plugin.py b/src/mkdocstrings/plugin.py index 34edcc06..7ec0b85d 100644 --- a/src/mkdocstrings/plugin.py +++ b/src/mkdocstrings/plugin.py @@ -19,7 +19,6 @@ from concurrent import futures from typing import Any, BinaryIO, Callable, Iterable, List, Mapping, Optional, Tuple from urllib import request -from warnings import warn from mkdocs.config import Config from mkdocs.config.config_options import Type as MkType @@ -126,11 +125,6 @@ def on_serve(self, server: LiveReloadServer, builder: Callable, **kwargs: Any): **kwargs: Additional arguments passed by MkDocs. """ if self.config["watch"]: - warn( - "mkdocstrings' watch feature is deprecated in favor of MkDocs' watch feature, " - "see https://www.mkdocs.org/user-guide/configuration/#watch.", - DeprecationWarning, - ) for element in self.config["watch"]: log.debug(f"Adding directory '{element}' to watcher") server.watch(element, builder) @@ -205,6 +199,9 @@ def on_config(self, config: Config, **kwargs: Any) -> Config: # noqa: W0613 (un self._inv_futures.append(future) inv_loader.shutdown(wait=False) + if self.config["watch"]: + self._warn_about_watch_option() + return config @property @@ -299,3 +296,11 @@ def _load_inventory(cls, loader: InventoryLoaderType, url: str, **kwargs: Any) - result = dict(loader(content, url=url, **kwargs)) log.debug(f"Loaded inventory from {url!r}: {len(result)} items") return result + + @classmethod + @functools.lru_cache(maxsize=None) # Warn only once + def _warn_about_watch_option(cls): + log.info( + "DEPRECATION: mkdocstrings' watch feature is deprecated in favor of MkDocs' watch feature, " + "see https://www.mkdocs.org/user-guide/configuration/#watch", + ) diff --git a/tests/test_extension.py b/tests/test_extension.py index df388723..3c41932c 100644 --- a/tests/test_extension.py +++ b/tests/test_extension.py @@ -1,4 +1,5 @@ """Tests for the extension module.""" +import logging import re import sys from textwrap import dedent @@ -31,7 +32,7 @@ def test_multiple_footnotes(ext_markdown): def test_markdown_heading_level(ext_markdown): """Assert that Markdown headings' level doesn't exceed heading_level.""" - output = ext_markdown.convert("::: tests.fixtures.headings\n rendering:\n show_root_heading: true") + output = ext_markdown.convert("::: tests.fixtures.headings\n options:\n show_root_heading: true") assert ">Foo" in output assert ">Bar" in output assert ">Baz" in output @@ -83,7 +84,7 @@ def test_no_double_toc(ext_markdown, expect_permalink): # aa ::: tests.fixtures.headings - rendering: + options: show_root_toc_entry: false # bb @@ -137,10 +138,11 @@ def test_dont_register_every_identifier_as_anchor(plugin): assert identifier not in autorefs._abs_url_map # noqa: WPS437 -def test_use_deprecated_yaml_keys(ext_markdown): +def test_use_deprecated_yaml_keys(ext_markdown, caplog): """Check that using the deprecated 'selection' and 'rendering' YAML keys emits a deprecation warning.""" - with pytest.warns(DeprecationWarning, match="single 'options' YAML key"): - assert "h1" not in ext_markdown.convert("::: tests.fixtures.headings\n rendering:\n heading_level: 2") + caplog.set_level(logging.INFO) + assert "h1" not in ext_markdown.convert("::: tests.fixtures.headings\n rendering:\n heading_level: 2") + assert "single 'options' YAML key" in caplog.text def test_use_new_options_yaml_key(ext_markdown): From 9214b74367da1f9c808eacc8ceecc4134d5c9d3c Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Sat, 20 Aug 2022 20:10:06 +0200 Subject: [PATCH 04/13] refactor: Small fixes to type annotations Also for event type annotations - if, hypothetically, MkDocs started to support them. PR #470: https://github.com/mkdocstrings/mkdocstrings/pull/470 --- src/mkdocstrings/extension.py | 23 ++++++++++++----------- src/mkdocstrings/loggers.py | 2 +- src/mkdocstrings/plugin.py | 8 ++++++-- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/mkdocstrings/extension.py b/src/mkdocstrings/extension.py index 02b94fc9..5b8848ab 100644 --- a/src/mkdocstrings/extension.py +++ b/src/mkdocstrings/extension.py @@ -127,17 +127,18 @@ def run(self, parent: Element, blocks: MutableSequence[str]) -> None: el.extend(headings) page = self._autorefs.current_page - for heading in headings: - anchor = heading.attrib["id"] # noqa: WPS440 - self._autorefs.register_anchor(page, anchor) # noqa: WPS441 - - if "data-role" in heading.attrib: - self._handlers.inventory.register( - name=anchor, # noqa: WPS441 - domain=handler.domain, - role=heading.attrib["data-role"], - uri=f"{page}#{anchor}", # noqa: WPS441 - ) + if page: + for heading in headings: + anchor = heading.attrib["id"] # noqa: WPS440 + self._autorefs.register_anchor(page, anchor) # noqa: WPS441 + + if "data-role" in heading.attrib: + self._handlers.inventory.register( + name=anchor, # noqa: WPS441 + domain=handler.domain, + role=heading.attrib["data-role"], + uri=f"{page}#{anchor}", # noqa: WPS441 + ) parent.append(el) diff --git a/src/mkdocstrings/loggers.py b/src/mkdocstrings/loggers.py index d2722616..f75d31d2 100644 --- a/src/mkdocstrings/loggers.py +++ b/src/mkdocstrings/loggers.py @@ -11,7 +11,7 @@ try: from jinja2 import pass_context except ImportError: # TODO: remove once Jinja2 < 3.1 is dropped - from jinja2 import contextfunction as pass_context # noqa: WPS440 + from jinja2 import contextfunction as pass_context # type: ignore # noqa: WPS440 try: import mkdocstrings_handlers diff --git a/src/mkdocstrings/plugin.py b/src/mkdocstrings/plugin.py index 7ec0b85d..2c791774 100644 --- a/src/mkdocstrings/plugin.py +++ b/src/mkdocstrings/plugin.py @@ -111,7 +111,9 @@ def handlers(self) -> Handlers: return self._handlers # TODO: remove once watch feature is removed - def on_serve(self, server: LiveReloadServer, builder: Callable, **kwargs: Any): # noqa: W0613 (unused arguments) + def on_serve( + self, server: LiveReloadServer, config: Config, builder: Callable, *args: Any, **kwargs: Any + ) -> None: # noqa: W0613 (unused arguments) """Watch directories. Hook for the [`on_serve` event](https://www.mkdocs.org/user-guide/plugins/#on_serve). @@ -121,7 +123,9 @@ def on_serve(self, server: LiveReloadServer, builder: Callable, **kwargs: Any): Arguments: server: The `livereload` server instance. + config: The MkDocs config object (unused). builder: The function to build the site. + *args: Additional arguments passed by MkDocs. **kwargs: Additional arguments passed by MkDocs. """ if self.config["watch"]: @@ -216,7 +220,7 @@ def inventory_enabled(self) -> bool: inventory_enabled = any(handler.enable_inventory for handler in self.handlers.seen_handlers) return inventory_enabled - def on_env(self, env, config: Config, **kwargs): + def on_env(self, env, config: Config, *args, **kwargs) -> None: """Extra actions that need to happen after all Markdown rendering and before HTML rendering. Hook for the [`on_env` event](https://www.mkdocs.org/user-guide/plugins/#on_env). From e2fb97b400db0035b5409d71b89060cbf27cab6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 12 Sep 2022 18:54:56 +0200 Subject: [PATCH 05/13] chore: Template upgrade --- .copier-answers.yml | 2 +- .github/FUNDING.yml | 7 +-- CONTRIBUTING.md | 3 +- docs/credits.md | 3 ++ docs/gen_credits.py | 62 ----------------------- docs/gen_ref_nav.py | 10 ++-- duties.py | 62 ++++++----------------- mkdocs.yml | 5 +- pyproject.toml | 15 +++--- scripts/gen_credits.py | 110 +++++++++++++++++++++++++++++++++++++++++ 10 files changed, 150 insertions(+), 129 deletions(-) create mode 100644 docs/credits.md delete mode 100644 docs/gen_credits.py create mode 100644 scripts/gen_credits.py diff --git a/.copier-answers.yml b/.copier-answers.yml index 7dd4a454..ba765919 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: 0.9.6 +_commit: 0.10.2 _src_path: gh:pawamoy/copier-pdm.git author_email: pawamoy@pm.me author_fullname: TimothΓ©e Mazzucotelli diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index c71a8d4e..cf5764f4 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,7 +1,4 @@ github: - - pawamoy -ko_fi: pawamoy -liberapay: pawamoy -patreon: pawamoy +- pawamoy custom: - - https://www.paypal.me/pawamoy +- https://www.paypal.me/pawamoy diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7f80c98a..64f33dc1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,8 @@ cd mkdocstrings make setup ``` -> NOTE: If it fails for some reason, +> NOTE: +> If it fails for some reason, > you'll need to install > [PDM](https://github.com/pdm-project/pdm) > manually. diff --git a/docs/credits.md b/docs/credits.md new file mode 100644 index 00000000..02e1dd81 --- /dev/null +++ b/docs/credits.md @@ -0,0 +1,3 @@ +```python exec="yes" +--8<-- "scripts/gen_credits.py" +``` diff --git a/docs/gen_credits.py b/docs/gen_credits.py deleted file mode 100644 index 370d2e7d..00000000 --- a/docs/gen_credits.py +++ /dev/null @@ -1,62 +0,0 @@ -"""Generate the credits page.""" - -import functools -import re -from itertools import chain -from pathlib import Path -from urllib.request import urlopen - -import mkdocs_gen_files -import toml -from jinja2 import StrictUndefined -from jinja2.sandbox import SandboxedEnvironment - - -def get_credits_data() -> dict: - """Return data used to generate the credits file. - - Returns: - Data required to render the credits template. - """ - project_dir = Path(__file__).parent.parent - metadata = toml.load(project_dir / "pyproject.toml")["project"] - metadata_pdm = toml.load(project_dir / "pyproject.toml")["tool"]["pdm"] - lock_data = toml.load(project_dir / "pdm.lock") - project_name = metadata["name"] - - all_dependencies = chain( - metadata.get("dependencies", []), - chain(*metadata.get("optional-dependencies", {}).values()), - chain(*metadata_pdm.get("dev-dependencies", {}).values()), - ) - direct_dependencies = {re.sub(r"[^\w-].*$", "", dep) for dep in all_dependencies} - direct_dependencies = {dep.lower() for dep in direct_dependencies} - indirect_dependencies = {pkg["name"].lower() for pkg in lock_data["package"]} - indirect_dependencies -= direct_dependencies - - return { - "project_name": project_name, - "direct_dependencies": sorted(direct_dependencies), - "indirect_dependencies": sorted(indirect_dependencies), - "more_credits": "http://pawamoy.github.io/credits/", - } - - -@functools.lru_cache(maxsize=None) -def get_credits(): - """Return credits as Markdown. - - Returns: - The credits page Markdown. - """ - jinja_env = SandboxedEnvironment(undefined=StrictUndefined) - commit = "c78c29caa345b6ace19494a98b1544253cbaf8c1" - template_url = f"https://raw.githubusercontent.com/pawamoy/jinja-templates/{commit}/credits.md" - template_data = get_credits_data() - template_text = urlopen(template_url).read().decode("utf8") # noqa: S310 - return jinja_env.from_string(template_text).render(**template_data) - - -with mkdocs_gen_files.open("credits.md", "w") as fd: - fd.write(get_credits()) -mkdocs_gen_files.set_edit_path("credits.md", "gen_credits.py") diff --git a/docs/gen_ref_nav.py b/docs/gen_ref_nav.py index 6b81859d..a350a732 100644 --- a/docs/gen_ref_nav.py +++ b/docs/gen_ref_nav.py @@ -6,23 +6,25 @@ nav = mkdocs_gen_files.Nav() -for path in sorted(Path("src").glob("**/*.py")): +for path in sorted(Path("src").rglob("*.py")): module_path = path.relative_to("src").with_suffix("") doc_path = path.relative_to("src", "mkdocstrings").with_suffix(".md") full_doc_path = Path("reference", doc_path) - parts = list(module_path.parts) + parts = tuple(module_path.parts) + if parts[-1] == "__init__": parts = parts[:-1] doc_path = doc_path.with_name("index.md") full_doc_path = full_doc_path.with_name("index.md") elif parts[-1] == "__main__": continue - nav[parts] = doc_path + + nav[parts] = doc_path.as_posix() with mkdocs_gen_files.open(full_doc_path, "w") as fd: ident = ".".join(parts) - print("::: " + ident, file=fd) + fd.write(f"::: {ident}") mkdocs_gen_files.set_edit_path(full_doc_path, Path("../") / path) diff --git a/duties.py b/duties.py index 04511dd3..58233386 100644 --- a/duties.py +++ b/duties.py @@ -4,8 +4,6 @@ import os import re import sys -import tempfile -from contextlib import suppress from io import StringIO from pathlib import Path from typing import List, Optional, Pattern @@ -140,7 +138,8 @@ def check_dependencies(ctx): importlib.invalidate_caches() # reload original, unpatched safety - from safety.formatter import report + 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 @@ -154,10 +153,19 @@ def check_dependencies(ctx): # check using safety as a library def safety(): # noqa: WPS430 packages = list(read_requirements(StringIO(requirements))) - vulns = safety_check(packages=packages, ignore_ids="", key="", db_mirror="", cached=False, proxy={}) - output_report = report(vulns=vulns, full=True, checked_packages=len(packages)) + 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") @@ -181,49 +189,7 @@ def check_types(ctx): # noqa: WPS231 Arguments: ctx: The context instance (passed automatically). """ - # NOTE: the following code works around this issue: - # https://github.com/python/mypy/issues/10633 - - # compute packages directory path - py = f"{sys.version_info.major}.{sys.version_info.minor}" - pkgs_dir = Path("__pypackages__", py, "lib").resolve() - - # build the list of available packages - packages = {} - for package in pkgs_dir.glob("*"): - if package.suffix not in {".dist-info", ".pth"} and package.name != "__pycache__": - packages[package.name] = package - - # handle .pth files - for pth in pkgs_dir.glob("*.pth"): - with suppress(OSError): - for package in Path(pth.read_text().splitlines()[0]).glob("*"): # noqa: WPS440 - if package.suffix != ".dist-info": - packages[package.name] = package - - # create a temporary directory to assign to MYPYPATH - with tempfile.TemporaryDirectory() as tmpdir: - - # symlink the stubs - ignore = set() - for stubs in (path for name, path in packages.items() if name.endswith("-stubs")): # noqa: WPS335 - Path(tmpdir, stubs.name).symlink_to(stubs, target_is_directory=True) - # try to symlink the corresponding package - # see https://www.python.org/dev/peps/pep-0561/#stub-only-packages - pkg_name = stubs.name.replace("-stubs", "") - if pkg_name in packages: - ignore.add(pkg_name) - Path(tmpdir, pkg_name).symlink_to(packages[pkg_name], target_is_directory=True) - - # create temporary mypy config to ignore stubbed packages - newconfig = Path("config", "mypy.ini").read_text() - newconfig += "\n" + "\n\n".join(f"[mypy-{pkg}.*]\nignore_errors=true" for pkg in ignore) - tmpconfig = Path(tmpdir, "mypy.ini") - tmpconfig.write_text(newconfig) - - # set MYPYPATH and run mypy - os.environ["MYPYPATH"] = tmpdir - ctx.run(f"mypy --config-file {tmpconfig} {PY_SRC}", title="Type-checking", pty=PTY) + ctx.run(f"mypy --config-file config/mypy.ini {PY_SRC}", title="Type-checking", pty=PTY) @duty(silent=True) diff --git a/mkdocs.yml b/mkdocs.yml index d9c86361..2c1ae40e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -5,7 +5,7 @@ repo_url: "https://github.com/mkdocstrings/mkdocstrings" edit_uri: "blob/master/docs/" repo_name: "mkdocstrings/mkdocstrings" site_dir: "site" -watch: [src/mkdocstrings] +watch: [README.md, CONTRIBUTING.md, CHANGELOG.md, src/mkdocstrings] nav: - Home: @@ -37,6 +37,7 @@ theme: features: - content.code.annotate - navigation.tabs + - navigation.tabs.sticky - navigation.top palette: - media: "(prefers-color-scheme: light)" @@ -76,9 +77,9 @@ markdown_extensions: plugins: - search +- markdown-exec - gen-files: scripts: - - docs/gen_credits.py - docs/gen_ref_nav.py - docs/gen_redirects.py - literate-nav: diff --git a/pyproject.toml b/pyproject.toml index b4cbd1d1..c034d8ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "pdm.pep517.api" name = "mkdocstrings" description = "Automatic documentation from sources, for MkDocs." authors = [{name = "TimothΓ©e Mazzucotelli", email = "pawamoy@pm.me"}] -license = {file = "LICENSE"} +license-expression = "ISC" readme = "README.md" requires-python = ">=3.7" keywords = ["mkdocs", "mkdocs-plugin", "docstrings", "autodoc", "documentation"] @@ -14,7 +14,6 @@ dynamic = ["version"] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", - "License :: OSI Approved :: ISC License (ISCL)", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", @@ -57,7 +56,9 @@ Funding = "https://github.com/sponsors/mkdocstrings" mkdocstrings = "mkdocstrings.plugin:MkdocstringsPlugin" [tool.pdm] -version = {use_scm = true} +version = {source = "scm"} + +[tool.pdm.build] package-dir = "src" includes = ["src/mkdocstrings"] editable-backend = "editables" @@ -65,14 +66,15 @@ editable-backend = "editables" [tool.pdm.dev-dependencies] duty = ["duty>=0.7"] docs = [ - "markdown-callouts>=0.2.0", - "mkdocs>=1.3", # required for the watch feature + "mkdocs>=1.3", "mkdocs-coverage>=0.2", "mkdocs-gen-files>=0.3", "mkdocs-literate-nav>=0.4", "mkdocs-material>=7.3", "mkdocs-section-index>=0.3", "mkdocstrings-python>=0.5.1", + "markdown-callouts>=0.2", + "markdown-exec>=0.5", "toml>=0.10", ] format = [ @@ -85,6 +87,7 @@ maintain = [ ] quality = [ "darglint>=1.8", + "flake8<4", # TODO: remove once importlib-metadata version conflict is resolved "flake8-bandit>=2.1", "flake8-black>=0.2", "flake8-bugbear>=21.9", @@ -114,7 +117,7 @@ typing = [ "types-pyyaml", "types-toml>=0.10", ] -security = ["safety>=1.10"] +security = ["safety>=2"] [tool.black] line-length = 120 diff --git a/scripts/gen_credits.py b/scripts/gen_credits.py new file mode 100644 index 00000000..a21a1e4a --- /dev/null +++ b/scripts/gen_credits.py @@ -0,0 +1,110 @@ +import re +from itertools import chain +from pathlib import Path +from textwrap import dedent + +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 + +project_dir = Path(".") +pyproject = toml.load(project_dir / "pyproject.toml") +project = pyproject["project"] +pdm = pyproject["tool"]["pdm"] +lock_data = toml.load(project_dir / "pdm.lock") +lock_pkgs = {pkg["name"].lower(): pkg for pkg in lock_data["package"]} +project_name = project["name"] +regex = re.compile(r"(?P[\w.-]+)(?P.*)$") + +def get_license(pkg_name): + try: + data = metadata(pkg_name) + except PackageNotFoundError: + return "?" + license = data.get("License", "").strip() + multiple_lines = bool(license.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 header == "Classifier" and value.startswith("License ::"): + license = 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 "?" + +def get_deps(base_deps): + deps = {} + for dep in base_deps: + parsed = regex.match(dep).groupdict() + dep_name = parsed["dist"].lower() + deps[dep_name] = {"license": get_license(dep_name), **parsed, **lock_pkgs[dep_name]} + + again = True + while again: + again = False + 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() + dep_name = parsed["dist"].lower() + if dep_name not in deps: + 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()), + ) +) + +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)) From efa00b28230cf96b7f078f20bac19ceaa7bf6cef Mon Sep 17 00:00:00 2001 From: KSneijders Date: Fri, 23 Sep 2022 20:35:21 +0200 Subject: [PATCH 06/13] docs: Clarify `custom_templates` folder location in options documentation --- docs/usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage.md b/docs/usage.md index 02fc9091..ed08768d 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -108,7 +108,7 @@ The above is equivalent to: - `default_handler`: the handler that is used by default when no handler is specified. - `custom_templates`: the path to a directory containing custom templates. - The path is relative to the docs directory. + The path is relative to the current working directory. See [Theming](theming.md). - `handlers`: the handlers global configuration. - `enable_inventory`: whether to enable inventory file generation. From 995e5dc99cbd247a1017050fc11ea5c0dcc19032 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Fri, 2 Dec 2022 12:56:24 +0100 Subject: [PATCH 07/13] docs: Remove mention of deprecated watch feature from recipes --- docs/recipes.md | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/docs/recipes.md b/docs/recipes.md index 09c012e3..4804efa1 100644 --- a/docs/recipes.md +++ b/docs/recipes.md @@ -53,14 +53,11 @@ plugins: - gen-files: scripts: - docs/gen_ref_pages.py # (2) -- mkdocstrings: - watch: - - src/project # (3) +- mkdocstrings ``` 1. Don't forget to load the `search` plugin when redefining the `plugins` item. 2. The magic happens here, see below how it works. -3. Useful for the live-reload feature of `mkdocs serve`. mkdocs-gen-files is able to run Python scripts at build time. The Python script that we will execute lives in the docs folder, @@ -185,9 +182,7 @@ plugins: - docs/gen_ref_pages.py - literate-nav: nav_file: SUMMARY.md -- mkdocstrings: - watch: - - src/project +- mkdocstrings ``` Then, the previous script is updated like so: @@ -309,9 +304,7 @@ plugins: - literate-nav: nav_file: SUMMARY.md - section-index -- mkdocstrings: - watch: - - src/project +- mkdocstrings ``` With this, `__init__` modules will be documented and bound to the sections From eeeb97b9406ce085727b1766cf8043ad93a16d01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Fri, 2 Dec 2022 13:07:39 +0100 Subject: [PATCH 08/13] chore: Template upgrade --- .copier-answers.yml | 2 +- .github/workflows/ci.yml | 38 +++++--------------------------------- docs/css/mkdocstrings.css | 3 +-- pyproject.toml | 7 +++++-- scripts/gen_credits.py | 2 +- 5 files changed, 13 insertions(+), 39 deletions(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index ba765919..325468be 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: 0.10.2 +_commit: 0.10.7 _src_path: gh:pawamoy/copier-pdm.git author_email: pawamoy@pm.me author_fullname: TimothΓ©e Mazzucotelli diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d9133186..4b086fed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,27 +24,13 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up PDM - uses: pdm-project/setup-pdm@v2.6 + uses: pdm-project/setup-pdm@v3 with: python-version: "3.8" - - name: Set cache variables - id: set_variables - run: | - echo "::set-output name=PIP_CACHE::$(pip cache dir)" - echo "::set-output name=PDM_CACHE::$(pdm config cache_dir)" - - - name: Set up cache - uses: actions/cache@v2 - with: - path: | - ${{ steps.set_variables.outputs.PIP_CACHE }} - ${{ steps.set_variables.outputs.PDM_CACHE }} - key: checks-cache - - name: Resolving dependencies run: pdm lock @@ -76,33 +62,19 @@ jobs: - "3.8" - "3.9" - "3.10" - - "3.11-dev" + - "3.11" runs-on: ${{ matrix.os }} steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up PDM - uses: pdm-project/setup-pdm@v2.6 + uses: pdm-project/setup-pdm@v3 with: python-version: ${{ matrix.python-version }} - - name: Set cache variables - id: set_variables - run: | - echo "::set-output name=PIP_CACHE::$(pip cache dir)" - echo "::set-output name=PDM_CACHE::$(pdm config cache_dir)" - - - name: Set up cache - uses: actions/cache@v2 - with: - path: | - ${{ steps.set_variables.outputs.PIP_CACHE }} - ${{ steps.set_variables.outputs.PDM_CACHE }} - key: tests-cache-${{ runner.os }}-${{ matrix.python-version }} - - name: Install dependencies run: pdm install --no-editable -G duty -G tests -G docs diff --git a/docs/css/mkdocstrings.css b/docs/css/mkdocstrings.css index a83172e5..f269d975 100644 --- a/docs/css/mkdocstrings.css +++ b/docs/css/mkdocstrings.css @@ -1,8 +1,7 @@ /* Indentation. */ div.doc-contents:not(.first) { padding-left: 25px; - border-left: 4px solid rgba(230, 230, 230); - margin-bottom: 80px; + border-left: .05rem solid var(--md-typeset-table-color); } /* Avoid breaking parameters name, etc. in table cells. */ diff --git a/pyproject.toml b/pyproject.toml index c034d8ff..35aea778 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "pdm.pep517.api" name = "mkdocstrings" description = "Automatic documentation from sources, for MkDocs." authors = [{name = "TimothΓ©e Mazzucotelli", email = "pawamoy@pm.me"}] -license-expression = "ISC" +license = "ISC" readme = "README.md" requires-python = ">=3.7" keywords = ["mkdocs", "mkdocs-plugin", "docstrings", "autodoc", "documentation"] @@ -86,8 +86,11 @@ maintain = [ "git-changelog>=0.4", ] 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<4", # TODO: remove once importlib-metadata version conflict is resolved "flake8-bandit>=2.1", "flake8-black>=0.2", "flake8-bugbear>=21.9", diff --git a/scripts/gen_credits.py b/scripts/gen_credits.py index a21a1e4a..10f5647d 100644 --- a/scripts/gen_credits.py +++ b/scripts/gen_credits.py @@ -58,7 +58,7 @@ def get_deps(base_deps): if dep_name not in deps: 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())) From 34a1512254dc78791472543fa378bdeb18d3d276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Fri, 2 Dec 2022 13:30:05 +0100 Subject: [PATCH 09/13] chore: Template upgrade --- .copier-answers.yml | 2 +- docs/credits.md | 5 +++++ scripts/gen_credits.py | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index 325468be..f19d7882 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: 0.10.7 +_commit: 0.10.8 _src_path: gh:pawamoy/copier-pdm.git author_email: pawamoy@pm.me author_fullname: TimothΓ©e Mazzucotelli 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/scripts/gen_credits.py b/scripts/gen_credits.py index 10f5647d..e5bdd7a7 100644 --- a/scripts/gen_credits.py +++ b/scripts/gen_credits.py @@ -55,7 +55,7 @@ def get_deps(base_deps): for pkg_dependency in lock_pkgs[pkg_name].get("dependencies", []): parsed = regex.match(pkg_dependency).groupdict() dep_name = parsed["dist"].lower() - if dep_name not in deps: + 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 From 6c3ef7989f67a40d90647941fcdab723f6464791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Fri, 2 Dec 2022 13:30:28 +0100 Subject: [PATCH 10/13] docs: Small improvement --- docs/recipes.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/recipes.md b/docs/recipes.md index 4804efa1..8a28425f 100644 --- a/docs/recipes.md +++ b/docs/recipes.md @@ -129,12 +129,13 @@ for path in sorted(Path("src").rglob("*.py")): # (1) > Then we will have to change our `set_edit_path` call to: > > ```python -> mkdocs_gen_files.set_edit_path(full_doc_path, Path("../") / path) # (1) +> mkdocs_gen_files.set_edit_path(full_doc_path, Path("../") / path) # (1) > ``` +> > 1. Path can be used to traverse the structure in any way you may need, but > remember to use relative paths! > -> so that it correctly sets the edit path of (for example) `lorem.py` to +> ...so that it correctly sets the edit path of (for example) `lorem.py` to > `/blob/master/src/project/lorem.py` instead of > `/blob/master/docs/src/project/lorem.py`. From a5ed21168c1ccd92a1738f054f1ac2e0f03f030c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sun, 11 Dec 2022 16:21:10 +0100 Subject: [PATCH 11/13] chore: Add JSON schema for plugin's options --- docs/schema.json | 57 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 docs/schema.json diff --git a/docs/schema.json b/docs/schema.json new file mode 100644 index 00000000..a74dabf3 --- /dev/null +++ b/docs/schema.json @@ -0,0 +1,57 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "Automatic documentation from sources, for MkDocs.", + "oneOf": [ + { + "markdownDescription": "https://mkdocstrings.github.io/", + "enum": [ + "mkdocstrings" + ] + }, + { + "type": "object", + "properties": { + "mkdocstrings": { + "markdownDescription": "https://mkdocstrings.github.io/", + "type": "object", + "properties": { + "custom_templates": { + "title": "The path to a directory containing custom templates. The path is relative to the current working directory.", + "markdownDescription": "https://mkdocstrings.github.io/theming/", + "type": "string", + "default": null, + "format": "path" + }, + "default_handler": { + "title": "The handler used by default when no handler is specified in autodoc instructions.", + "markdownDescription": "https://mkdocstrings.github.io/usage/#global-options", + "type": "string", + "default": "python" + }, + "enable_inventory": { + "title": "Whether to enable inventory file generation.", + "markdownDescription": "https://mkdocstrings.github.io/usage/#cross-references-to-other-projects-inventories", + "type": "boolean", + "default": null + }, + "handlers": { + "title": "The handlers global configuration.", + "markdownDescription": "https://mkdocstrings.github.io/handlers/overview/", + "type": "object", + "default": null, + "items": { + "oneOf": [ + { + "$ref": "https://raw.githubusercontent.com/mkdocstrings/python/master/docs/schema.json" + } + ] + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} \ No newline at end of file From 348bdd5e930f3cf7a8e27835189794ec940ae1b7 Mon Sep 17 00:00:00 2001 From: Luis Michaelis Date: Tue, 13 Dec 2022 13:20:31 +0100 Subject: [PATCH 12/13] fix: Fix regular expression for Sphinx inventory parsing Some Sphinx inventories don't match the `sphinx_item_regex` defined in `InventoryItem`. Allowing any number of whitespace characters at the end instead of requiring at least one fixes this issue. Co-authored-by: Luis Michaelis Issue #496: https://github.com/mkdocstrings/mkdocstrings/issues/496 PR #497: https://github.com/mkdocstrings/mkdocstrings/pull/497 --- src/mkdocstrings/inventory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mkdocstrings/inventory.py b/src/mkdocstrings/inventory.py index 6c1b8558..9108d91d 100644 --- a/src/mkdocstrings/inventory.py +++ b/src/mkdocstrings/inventory.py @@ -46,7 +46,7 @@ def format_sphinx(self) -> str: uri = uri[: -len(self.name)] + "$" return f"{self.name} {self.domain}:{self.role} {self.priority} {uri} {dispname}" - sphinx_item_regex = re.compile(r"^(.+?)\s+(\S+):(\S+)\s+(-?\d+)\s+(\S+)\s+(.*)$") + sphinx_item_regex = re.compile(r"^(.+?)\s+(\S+):(\S+)\s+(-?\d+)\s+(\S+)\s*(.*)$") @classmethod def parse_sphinx(cls, line: str) -> "InventoryItem": From d965ccc39249971c897fba6a475b8f974e2de866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Tue, 13 Dec 2022 23:52:45 +0100 Subject: [PATCH 13/13] chore: Prepare release 0.19.1 --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5892de7c..45eca34d 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). +## [0.19.1](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.19.1) - 2022-12-13 + +[Compare with 0.19.0](https://github.com/mkdocstrings/mkdocstrings/compare/0.19.0...0.19.1) + +### Bug Fixes +- Fix regular expression for Sphinx inventory parsing ([348bdd5](https://github.com/mkdocstrings/mkdocstrings/commit/348bdd5e930f3cf7a8e27835189794ec940ae1b7) by Luis Michaelis). [Issue #496](https://github.com/mkdocstrings/mkdocstrings/issues/496), [PR #497](https://github.com/mkdocstrings/mkdocstrings/issues/497) + +### Code Refactoring +- Small fixes to type annotations ([9214b74](https://github.com/mkdocstrings/mkdocstrings/commit/9214b74367da1f9c808eacc8ceecc4134d5c9d3c) by Oleh Prypin). [PR #470](https://github.com/mkdocstrings/mkdocstrings/issues/470) +- Report usage-based warnings as user-facing messages ([03dd7a6](https://github.com/mkdocstrings/mkdocstrings/commit/03dd7a6e4fefa44889bda9899d9b698bcfd07990) by Oleh Prypin). [PR #464](https://github.com/mkdocstrings/mkdocstrings/issues/464) + + ## [0.19.0](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.19.0) - 2022-05-28 [Compare with 0.18.1](https://github.com/mkdocstrings/mkdocstrings/compare/0.18.1...0.19.0)