From 438be895b5edfe92121b2bd3a0654959ca343d4f Mon Sep 17 00:00:00 2001 From: Mark Shui Hu Date: Sun, 28 Sep 2025 18:00:21 +0200 Subject: [PATCH 01/12] docs: Add links to GitHub Actions handler PR-800: https://github.com/mkdocstrings/mkdocstrings/pull/800 --- README.md | 1 + docs/usage/handlers.md | 1 + mkdocs.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/README.md b/README.md index cb6887e6..23d3246c 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Come have a chat or ask questions on our [Gitter channel](https://gitter.im/mkdo We currently have [handlers](https://mkdocstrings.github.io/handlers/overview/) for the [C](https://mkdocstrings.github.io/c/), [Crystal](https://mkdocstrings.github.io/crystal/), + [GitHub Actions](https://watermarkhu.nl/mkdocstrings-github/), [Python](https://mkdocstrings.github.io/python/), [MATLAB](https://watermarkhu.nl/mkdocstrings-matlab/), [TypeScript](https://mkdocstrings.github.io/typescript/), and diff --git a/docs/usage/handlers.md b/docs/usage/handlers.md index c4bbda57..efdaccd1 100644 --- a/docs/usage/handlers.md +++ b/docs/usage/handlers.md @@ -6,6 +6,7 @@ A handler is what makes it possible to collect and render documentation for a pa - [C](https://mkdocstrings.github.io/c/){ .external } - [Crystal](https://mkdocstrings.github.io/crystal/){ .external } +- [GitHub Actions](https://watermarkhu.nl/mkdocstrings-github/){ .external } - [Python](https://mkdocstrings.github.io/python/){ .external } - [Python (Legacy)](https://mkdocstrings.github.io/python-legacy/){ .external } - [MATLAB](https://watermarkhu.nl/mkdocstrings-matlab/){ .external } diff --git a/mkdocs.yml b/mkdocs.yml index 9163e91d..52721b3e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -26,6 +26,7 @@ nav: - All handlers: - C: https://mkdocstrings.github.io/c/ - Crystal: https://mkdocstrings.github.io/crystal/ + - GitHub Actions: https://watermarkhu.nl/mkdocstrings-github/ - Python: https://mkdocstrings.github.io/python/ - Python (Legacy): https://mkdocstrings.github.io/python-legacy/ - MATLAB: https://watermarkhu.nl/mkdocstrings-matlab/ From 75ff96877a7602a6ae02839b00ebc03e5b8fc91d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Thu, 9 Oct 2025 13:15:07 +0200 Subject: [PATCH 02/12] chore: Template upgrade --- .copier-answers.yml | 2 +- .github/pull_request_template.md | 15 ++++++++ .github/workflows/ci.yml | 29 ++++++++++++-- Makefile | 7 ++++ README.md | 2 +- config/pytest.ini | 4 ++ config/pytest_39.ini | 19 ++++++++++ docs/js/insiders.js | 13 ++++--- duties.py | 23 +++++++---- scripts/get_version.py | 7 +++- scripts/make.py | 59 +++++++++++++++++++++++------ src/mkdocstrings/_internal/debug.py | 2 +- tests/test_api.py | 6 ++- 13 files changed, 154 insertions(+), 34 deletions(-) create mode 100644 .github/pull_request_template.md create mode 100644 config/pytest_39.ini diff --git a/.copier-answers.yml b/.copier-answers.yml index fa6c4f9c..e200114f 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier. -_commit: 1.8.4 +_commit: 1.10.1 _src_path: gh:pawamoy/copier-uv author_email: dev@pawamoy.fr author_fullname: Timothée Mazzucotelli diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..6f0f2faf --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,15 @@ +### For reviewers + + +- [ ] I did not use AI +- [ ] I used AI and thoroughly reviewed every code/docs change + +### Description of the change + + +### Relevant resources + + +- +- +- diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2dd6da41..b721b7a5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,10 +2,16 @@ name: ci on: push: + - main + - test-me-* pull_request: branches: - main +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + defaults: run: shell: bash @@ -15,13 +21,30 @@ env: LC_ALL: en_US.utf-8 PYTHONIOENCODING: UTF-8 PYTHONPATH: docs + PYTHONWARNDEFAULTENCODING: "1" PYTHON_VERSIONS: "" jobs: quality: + strategy: + matrix: + os: + - ubuntu-latest + - macos-latest + - windows-latest + python-version: + - "3.9" + - "3.13" + include: + - os: ubuntu-latest + python-version: "3.10" + - os: ubuntu-latest + python-version: "3.11" + - os: ubuntu-latest + python-version: "3.12" - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} steps: - name: Checkout @@ -33,7 +56,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v5 with: - python-version: "3.12" + python-version: ${{ matrix.python-version }} - name: Setup uv uses: astral-sh/setup-uv@v5 @@ -109,7 +132,7 @@ jobs: - lowest-direct exclude: ${{ fromJSON(needs.exclude-test-jobs.outputs.jobs) }} runs-on: ${{ matrix.os }} - continue-on-error: ${{ matrix.python-version == '3.14' }} + continue-on-error: true steps: - name: Checkout diff --git a/Makefile b/Makefile index 5e88121d..1b3391da 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,13 @@ # the `make` command will point at the `scripts/make` shell script. # This Makefile is just here to allow auto-completion in the terminal. +default: help + @echo + @echo 'Enable direnv in your shell to use the `make` command: `direnv allow`' + @echo 'Or use `python scripts/make ARGS` to run the commands/tasks directly.' + +.DEFAULT_GOAL: default + actions = \ allrun \ changelog \ diff --git a/README.md b/README.md index 23d3246c..af264b77 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![documentation](https://img.shields.io/badge/docs-mkdocs-708FCC.svg?style=flat)](https://mkdocstrings.github.io/) [![pypi version](https://img.shields.io/pypi/v/mkdocstrings.svg)](https://pypi.org/project/mkdocstrings/) [![conda version](https://img.shields.io/conda/vn/conda-forge/mkdocstrings)](https://anaconda.org/conda-forge/mkdocstrings) -[![gitter](https://badges.gitter.im/join%20chat.svg)](https://app.gitter.im/#/room/#mkdocstrings:gitter.im) +[![gitter](https://img.shields.io/badge/matrix-chat-4DB798.svg?style=flat)](https://app.gitter.im/#/room/#mkdocstrings:gitter.im) Automatic documentation from sources, for [MkDocs](https://www.mkdocs.org/). Come have a chat or ask questions on our [Gitter channel](https://gitter.im/mkdocstrings/community). diff --git a/config/pytest.ini b/config/pytest.ini index 288b6cff..f65bd620 100644 --- a/config/pytest.ini +++ b/config/pytest.ini @@ -10,5 +10,9 @@ testpaths = # action:message_regex:warning_class:module_regex:line filterwarnings = error + default::EncodingWarning + error::EncodingWarning:mkdocstrings ignore:.*`get_anchors` method:DeprecationWarning:mkdocstrings ignore:.*Importing from:DeprecationWarning:mkdocstrings_handlers + # TODO: Remove once pytest-xdist 4 is released. + ignore:.*rsyncdir:DeprecationWarning:xdist diff --git a/config/pytest_39.ini b/config/pytest_39.ini new file mode 100644 index 00000000..27f45425 --- /dev/null +++ b/config/pytest_39.ini @@ -0,0 +1,19 @@ +# YORE: EOL 3.9: Remove file. +# This file is used on 3.9 due to forward compatibility issue with filterwarnings. +# See https://github.com/pytest-dev/pytest/issues/11101. +[pytest] +python_files = + test_*.py +addopts = + --cov + --cov-config config/coverage.ini +testpaths = + tests + +# action:message_regex:warning_class:module_regex:line +filterwarnings = + error + ignore:.*`get_anchors` method:DeprecationWarning:mkdocstrings + ignore:.*Importing from:DeprecationWarning:mkdocstrings_handlers + # TODO: Remove once pytest-xdist 4 is released. + ignore:.*rsyncdir:DeprecationWarning:xdist diff --git a/docs/js/insiders.js b/docs/js/insiders.js index 8bb68485..a86a0918 100644 --- a/docs/js/insiders.js +++ b/docs/js/insiders.js @@ -29,11 +29,14 @@ function updatePremiumSponsors(dataURL, rank) { let html = ''; html += `${capRank} sponsors

` sponsors.forEach(function (sponsor) { - html += ` - - ${sponsor.name} - - ` + html += `` + if (sponsor.image) { + html += `${sponsor.name}` + } else if (sponsor.imageLight && sponsor.imageDark) { + html += `${sponsor.name}` + html += `${sponsor.name}` + } + html += ''; }); html += '

' sponsorsDiv.innerHTML = html; diff --git a/duties.py b/duties.py index b75d8b55..ee20b497 100644 --- a/duties.py +++ b/duties.py @@ -26,6 +26,8 @@ WINDOWS = os.name == "nt" PTY = not WINDOWS and not CI MULTIRUN = os.environ.get("MULTIRUN", "0") == "1" +PY_VERSION = f"{sys.version_info.major}{sys.version_info.minor}" +PY_DEV = "314" def pyprefix(title: str) -> str: @@ -84,7 +86,7 @@ def check(ctx: Context) -> None: """Check it all!""" -@duty +@duty(nofail=PY_VERSION == PY_DEV) def check_quality(ctx: Context) -> None: """Check the code quality.""" ctx.run( @@ -93,7 +95,7 @@ def check_quality(ctx: Context) -> None: ) -@duty +@duty(nofail=PY_VERSION == PY_DEV) def check_docs(ctx: Context) -> None: """Check if the documentation builds correctly.""" Path("htmlcov").mkdir(parents=True, exist_ok=True) @@ -105,7 +107,7 @@ def check_docs(ctx: Context) -> None: ) -@duty +@duty(nofail=PY_VERSION == PY_DEV) def check_types(ctx: Context) -> None: """Check that the code is correctly typed.""" os.environ["MYPYPATH"] = "src" @@ -116,7 +118,7 @@ def check_types(ctx: Context) -> None: ) -@duty +@duty(nofail=PY_VERSION == PY_DEV) def check_api(ctx: Context, *cli_args: str) -> None: """Check for API breaking changes.""" ctx.run( @@ -237,19 +239,24 @@ def coverage(ctx: Context) -> None: ctx.run(tools.coverage.html(rcfile="config/coverage.ini")) -@duty +@duty(nofail=PY_VERSION == PY_DEV) def test(ctx: Context, *cli_args: str, match: str = "") -> None: # noqa: PT028 """Run the test suite. Parameters: 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}" + os.environ["COVERAGE_FILE"] = f".coverage.{PY_VERSION}" + os.environ["PYTHONWARNDEFAULTENCODING"] = "1" + config_file = "config/pytest.ini" + # YORE: EOL 3.9: Remove block. + if sys.version_info[:2] < (3, 10): + config_file = "config/pytest_39.ini" + ctx.run( tools.pytest( "tests", - config_file="config/pytest.ini", + config_file=config_file, select=match, color="yes", ).add_args("-n", "auto", *cli_args), diff --git a/scripts/get_version.py b/scripts/get_version.py index 6734e5b6..3c425a73 100644 --- a/scripts/get_version.py +++ b/scripts/get_version.py @@ -4,7 +4,12 @@ from contextlib import suppress from pathlib import Path -from pdm.backend.hooks.version import SCMVersion, Version, default_version_formatter, get_version_from_scm +from pdm.backend.hooks.version import ( # ty: ignore[unresolved-import] + SCMVersion, + Version, + default_version_formatter, + get_version_from_scm, +) _root = Path(__file__).parent.parent _changelog = _root / "CHANGELOG.md" diff --git a/scripts/make.py b/scripts/make.py index 55679baa..1e697bcc 100755 --- a/scripts/make.py +++ b/scripts/make.py @@ -15,6 +15,7 @@ PYTHON_VERSIONS = os.getenv("PYTHON_VERSIONS", "3.9 3.10 3.11 3.12 3.13 3.14").split() +PYTHON_DEV = "3.14" def shell(cmd: str, *, capture_output: bool = False, **kwargs: Any) -> str | None: @@ -67,16 +68,31 @@ def setup() -> None: uv_install(venv_path) +class _RunError(subprocess.CalledProcessError): + def __init__(self, *args: Any, python_version: str, **kwargs: Any): + super().__init__(*args, **kwargs) + self.python_version = python_version + + def run(version: str, cmd: str, *args: str, **kwargs: Any) -> None: """Run a command in a virtual environment.""" kwargs = {"check": True, **kwargs} uv_run = ["uv", "run", "--no-sync"] - if version == "default": - with environ(UV_PROJECT_ENVIRONMENT=".venv"): - subprocess.run([*uv_run, cmd, *args], **kwargs) # noqa: S603, PLW1510 - else: - with environ(UV_PROJECT_ENVIRONMENT=f".venvs/{version}", MULTIRUN="1"): - subprocess.run([*uv_run, cmd, *args], **kwargs) # noqa: S603, PLW1510 + try: + if version == "default": + with environ(UV_PROJECT_ENVIRONMENT=".venv"): + subprocess.run([*uv_run, cmd, *args], **kwargs) # noqa: S603, PLW1510 + else: + with environ(UV_PROJECT_ENVIRONMENT=f".venvs/{version}", MULTIRUN="1"): + subprocess.run([*uv_run, cmd, *args], **kwargs) # noqa: S603, PLW1510 + except subprocess.CalledProcessError as process: + raise _RunError( + returncode=process.returncode, + python_version=version, + cmd=process.cmd, + output=process.output, + stderr=process.stderr, + ) from process def multirun(cmd: str, *args: str, **kwargs: Any) -> None: @@ -144,19 +160,31 @@ def main() -> int: cmd = args.pop(0) if cmd == "run": - run("default", *args) + if not args: + print("make: run: missing command", file=sys.stderr) + return 1 + run("default", *args) # ty: ignore[missing-argument] return 0 if cmd == "multirun": - multirun(*args) + if not args: + print("make: run: missing command", file=sys.stderr) + return 1 + multirun(*args) # ty: ignore[missing-argument] return 0 if cmd == "allrun": - allrun(*args) + if not args: + print("make: run: missing command", file=sys.stderr) + return 1 + allrun(*args) # ty: ignore[missing-argument] return 0 if cmd.startswith("3."): - run(cmd, *args) + if not args: + print("make: run: missing command", file=sys.stderr) + return 1 + run(cmd, *args) # ty: ignore[missing-argument] return 0 opts = [] @@ -183,7 +211,14 @@ def main() -> int: if __name__ == "__main__": try: sys.exit(main()) - except subprocess.CalledProcessError as process: + except _RunError as process: if process.output: print(process.output, file=sys.stderr) - sys.exit(process.returncode) + if (code := process.returncode) == 139: # noqa: PLR2004 + print( + f"✗ (python{process.python_version}) '{' '.join(process.cmd)}' failed with return code {code} (segfault)", + file=sys.stderr, + ) + if process.python_version == PYTHON_DEV: + code = 0 + sys.exit(code) diff --git a/src/mkdocstrings/_internal/debug.py b/src/mkdocstrings/_internal/debug.py index 7b56409b..f6b11600 100644 --- a/src/mkdocstrings/_internal/debug.py +++ b/src/mkdocstrings/_internal/debug.py @@ -85,7 +85,7 @@ def _get_debug_info() -> _Environment: interpreter_version=py_version, interpreter_path=sys.executable, platform=platform.platform(), - variables=[_Variable(var, val) for var in variables if (val := os.getenv(var))], + variables=[_Variable(var, val) for var in variables if (val := os.getenv(var))], # ty: ignore[invalid-argument-type] packages=[_Package(pkg, _get_version(pkg)) for pkg in packages], ) diff --git a/tests/test_api.py b/tests/test_api.py index 57f0ce20..ea672073 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -91,7 +91,7 @@ def _fixture_public_objects(public_api: griffe.Module) -> list[griffe.Object | g def _fixture_inventory() -> Inventory: inventory_file = Path(__file__).parent.parent / "site" / "objects.inv" if not inventory_file.exists(): - raise pytest.skip("The objects inventory is not available.") + pytest.skip("The objects inventory is not available.") # ty: ignore[call-non-callable] with inventory_file.open("rb") as file: return Inventory.parse_sphinx(file) @@ -137,7 +137,9 @@ def test_api_matches_inventory(inventory: Inventory, public_objects: list[griffe """All public objects are added to the inventory.""" ignore_names = {"__getattr__", "__init__", "__repr__", "__str__", "__post_init__"} not_in_inventory = [ - obj.path for obj in public_objects if obj.name not in ignore_names and obj.path not in inventory + f"{obj.relative_filepath}:{obj.lineno}: {obj.path}" + for obj in public_objects + if obj.name not in ignore_names and obj.path not in inventory ] msg = "Objects not in the inventory (try running `make run mkdocs build`):\n{paths}" assert not not_in_inventory, msg.format(paths="\n".join(sorted(not_in_inventory))) From 5698963317af8e5d790a558fd945371f6ef3811a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Thu, 9 Oct 2025 17:52:31 +0200 Subject: [PATCH 03/12] chore: Template upgrade --- .copier-answers.yml | 2 +- .github/workflows/ci.yml | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index e200114f..f459d439 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier. -_commit: 1.10.1 +_commit: 1.10.2 _src_path: gh:pawamoy/copier-uv author_email: dev@pawamoy.fr author_fullname: Timothée Mazzucotelli diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b721b7a5..253c08f9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,8 +2,9 @@ name: ci on: push: - - main - - test-me-* + branches: + - main + - test-me-* pull_request: branches: - main @@ -36,13 +37,13 @@ jobs: python-version: - "3.9" - "3.13" - include: - - os: ubuntu-latest - python-version: "3.10" - - os: ubuntu-latest - python-version: "3.11" - - os: ubuntu-latest - python-version: "3.12" + include: + - os: ubuntu-latest + python-version: "3.10" + - os: ubuntu-latest + python-version: "3.11" + - os: ubuntu-latest + python-version: "3.12" runs-on: ${{ matrix.os }} From b8f35c14f1b93408096cd2289782159beb0cdf03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 10 Nov 2025 12:01:43 +0100 Subject: [PATCH 04/12] chore: Template upgrade --- .copier-answers.yml | 6 +- .github/workflows/ci.yml | 46 +++------ .github/workflows/release.yml | 21 +--- .github/workflows/sponsors.yml | 26 +++++ README.md | 5 + config/pytest_39.ini | 19 ---- docs/.overrides/main.html | 12 +-- docs/css/insiders.css | 124 ----------------------- docs/insiders/changelog.md | 3 - docs/insiders/goals.yml | 13 --- docs/insiders/index.md | 166 ------------------------------- docs/insiders/installation.md | 67 ------------- docs/js/insiders.js | 77 --------------- duties.py | 70 ++----------- mkdocs.yml | 8 +- pyproject.toml | 3 +- scripts/insiders.py | 173 --------------------------------- scripts/make.py | 4 +- 18 files changed, 65 insertions(+), 778 deletions(-) create mode 100644 .github/workflows/sponsors.yml delete mode 100644 config/pytest_39.ini delete mode 100644 docs/css/insiders.css delete mode 100644 docs/insiders/changelog.md delete mode 100644 docs/insiders/goals.yml delete mode 100644 docs/insiders/index.md delete mode 100644 docs/insiders/installation.md delete mode 100644 docs/js/insiders.js delete mode 100644 scripts/insiders.py diff --git a/.copier-answers.yml b/.copier-answers.yml index f459d439..6c5d650e 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier. -_commit: 1.10.2 +_commit: 1.11.1 _src_path: gh:pawamoy/copier-uv author_email: dev@pawamoy.fr author_fullname: Timothée Mazzucotelli @@ -8,12 +8,8 @@ copyright_date: '2019' copyright_holder: Timothée Mazzucotelli copyright_holder_email: dev@pawamoy.fr copyright_license: ISC -insiders: true -insiders_email: insiders@pawamoy.fr -insiders_repository_name: mkdocstrings project_description: Automatic documentation from sources, for MkDocs. project_name: mkdocstrings -public_release: true python_package_command_line_name: '' python_package_distribution_name: mkdocstrings python_package_import_name: mkdocstrings diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 253c08f9..54dd741c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,15 +35,15 @@ jobs: - macos-latest - windows-latest python-version: - - "3.9" - - "3.13" + - "3.10" + - "3.14" include: - - os: ubuntu-latest - python-version: "3.10" - os: ubuntu-latest python-version: "3.11" - os: ubuntu-latest python-version: "3.12" + - os: ubuntu-latest + python-version: "3.13" runs-on: ${{ matrix.os }} @@ -55,7 +55,7 @@ jobs: fetch-tags: true - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} @@ -82,39 +82,15 @@ jobs: - name: Store objects inventory for tests uses: actions/upload-artifact@v4 + if: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13' }} with: name: objects.inv path: site/objects.inv - exclude-test-jobs: - runs-on: ubuntu-latest - outputs: - jobs: ${{ steps.exclude-jobs.outputs.jobs }} - steps: - - id: exclude-jobs - run: | - if ${{ github.repository_owner == 'pawamoy-insiders' }}; then - echo 'jobs=[ - {"os": "macos-latest"}, - {"os": "windows-latest"}, - {"python-version": "3.10"}, - {"python-version": "3.11"}, - {"python-version": "3.12"}, - {"python-version": "3.13"}, - {"python-version": "3.14"} - ]' | tr -d '[:space:]' >> $GITHUB_OUTPUT - else - echo 'jobs=[ - {"os": "macos-latest", "resolution": "lowest-direct"}, - {"os": "windows-latest", "resolution": "lowest-direct"} - ]' | tr -d '[:space:]' >> $GITHUB_OUTPUT - fi - tests: needs: - quality - - exclude-test-jobs strategy: matrix: os: @@ -122,16 +98,20 @@ jobs: - macos-latest - windows-latest python-version: - - "3.9" - "3.10" - "3.11" - "3.12" - "3.13" - "3.14" + - "3.15" resolution: - highest - lowest-direct - exclude: ${{ fromJSON(needs.exclude-test-jobs.outputs.jobs) }} + exclude: + - os: macos-latest + resolution: lowest-direct + - os: windows-latest + resolution: lowest-direct runs-on: ${{ matrix.os }} continue-on-error: true @@ -143,7 +123,7 @@ jobs: fetch-tags: true - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} allow-prereleases: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 73347dad..1c7cda36 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,30 +15,15 @@ jobs: fetch-depth: 0 fetch-tags: true - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: - python-version: "3.12" + python-version: "3.13" - name: Setup uv uses: astral-sh/setup-uv@v5 - - name: Build dists - if: github.repository_owner == 'pawamoy-insiders' - run: uv tool run --from build pyproject-build - - name: Upload dists artifact - uses: actions/upload-artifact@v4 - if: github.repository_owner == 'pawamoy-insiders' - with: - name: mkdocstrings-insiders - path: ./dist/* - name: Prepare release notes - if: github.repository_owner != 'pawamoy-insiders' run: uv tool run git-changelog --release-notes > release-notes.md - - name: Create release with assets - uses: softprops/action-gh-release@v2 - if: github.repository_owner == 'pawamoy-insiders' - with: - files: ./dist/* - name: Create release uses: softprops/action-gh-release@v2 - if: github.repository_owner != 'pawamoy-insiders' with: body_path: release-notes.md + diff --git a/.github/workflows/sponsors.yml b/.github/workflows/sponsors.yml new file mode 100644 index 00000000..8dd9150f --- /dev/null +++ b/.github/workflows/sponsors.yml @@ -0,0 +1,26 @@ +name: Update sponsors + +on: + schedule: + - cron: '0 0 * * *' + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + update-readme: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Update README and create PR + uses: pawamoy/readme-insert@main + with: + markup-url: https://pawamoy.github.io/sponsors.txt + start-marker: '' + end-marker: '' + commit-message: 'chore: Update sponsors section in README' + pr-title: 'chore: Update sponsors section in README' + pr-body: 'This PR updates the sponsors section in the README file.' diff --git a/README.md b/README.md index af264b77..20f6fd39 100644 --- a/README.md +++ b/README.md @@ -133,3 +133,8 @@ In one of your markdown files: ``` See the [Usage](https://mkdocstrings.github.io/usage) section of the docs for more examples! + +## Sponsors + + + diff --git a/config/pytest_39.ini b/config/pytest_39.ini deleted file mode 100644 index 27f45425..00000000 --- a/config/pytest_39.ini +++ /dev/null @@ -1,19 +0,0 @@ -# YORE: EOL 3.9: Remove file. -# This file is used on 3.9 due to forward compatibility issue with filterwarnings. -# See https://github.com/pytest-dev/pytest/issues/11101. -[pytest] -python_files = - test_*.py -addopts = - --cov - --cov-config config/coverage.ini -testpaths = - tests - -# action:message_regex:warning_class:module_regex:line -filterwarnings = - error - ignore:.*`get_anchors` method:DeprecationWarning:mkdocstrings - ignore:.*Importing from:DeprecationWarning:mkdocstrings_handlers - # TODO: Remove once pytest-xdist 4 is released. - ignore:.*rsyncdir:DeprecationWarning:xdist diff --git a/docs/.overrides/main.html b/docs/.overrides/main.html index 1e956857..c702362f 100644 --- a/docs/.overrides/main.html +++ b/docs/.overrides/main.html @@ -1,13 +1,11 @@ {% extends "base.html" %} {% block announce %} - - Fund this project through - sponsorship - - {% include ".icons/octicons/heart-fill-16.svg" %} - — - + Fund this project through + sponsorship + + {% include ".icons/octicons/heart-fill-16.svg" %} + — Follow @pawamoy on diff --git a/docs/css/insiders.css b/docs/css/insiders.css deleted file mode 100644 index e7b9c74f..00000000 --- a/docs/css/insiders.css +++ /dev/null @@ -1,124 +0,0 @@ -@keyframes heart { - - 0%, - 40%, - 80%, - 100% { - transform: scale(1); - } - - 20%, - 60% { - transform: scale(1.15); - } -} - -@keyframes vibrate { - 0%, 2%, 4%, 6%, 8%, 10%, 12%, 14%, 16%, 18% { - -webkit-transform: translate3d(-2px, 0, 0); - transform: translate3d(-2px, 0, 0); - } - 1%, 3%, 5%, 7%, 9%, 11%, 13%, 15%, 17%, 19% { - -webkit-transform: translate3d(2px, 0, 0); - transform: translate3d(2px, 0, 0); - } - 20%, 100% { - -webkit-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); - } -} - -.heart { - color: #e91e63; -} - -.pulse { - animation: heart 1000ms infinite; -} - -.vibrate { - animation: vibrate 2000ms infinite; -} - -.new-feature svg { - fill: var(--md-accent-fg-color) !important; -} - -a.insiders { - color: #e91e63; -} - -.sponsorship-list { - width: 100%; -} - -.sponsorship-item { - border-radius: 100%; - display: inline-block; - height: 1.6rem; - margin: 0.1rem; - overflow: hidden; - width: 1.6rem; -} - -.sponsorship-item:focus, .sponsorship-item:hover { - transform: scale(1.1); -} - -.sponsorship-item img { - filter: grayscale(100%) opacity(75%); - height: auto; - width: 100%; -} - -.sponsorship-item:focus img, .sponsorship-item:hover img { - filter: grayscale(0); -} - -.sponsorship-item.private { - background: var(--md-default-fg-color--lightest); - color: var(--md-default-fg-color); - font-size: .6rem; - font-weight: 700; - line-height: 1.6rem; - text-align: center; -} - -.mastodon { - color: #897ff8; - border-radius: 100%; - box-shadow: inset 0 0 0 .05rem currentcolor; - display: inline-block; - height: 1.2rem !important; - padding: .25rem; - transition: all .25s; - vertical-align: bottom !important; - width: 1.2rem; -} - -.premium-sponsors { - text-align: center; -} - -#silver-sponsors img { - height: 140px; -} - -#bronze-sponsors img { - height: 140px; -} - -#bronze-sponsors p { - display: flex; - flex-wrap: wrap; - justify-content: center; -} - -#bronze-sponsors a { - display: block; - flex-shrink: 0; -} - -.sponsors-total { - font-weight: bold; -} \ No newline at end of file diff --git a/docs/insiders/changelog.md b/docs/insiders/changelog.md deleted file mode 100644 index 0f438566..00000000 --- a/docs/insiders/changelog.md +++ /dev/null @@ -1,3 +0,0 @@ -# Changelog - -## mkdocstrings Insiders diff --git a/docs/insiders/goals.yml b/docs/insiders/goals.yml deleted file mode 100644 index 0e27b997..00000000 --- a/docs/insiders/goals.yml +++ /dev/null @@ -1,13 +0,0 @@ -goals: - 500: - name: PlasmaVac User Guide - features: [] - 1000: - name: GraviFridge Fluid Renewal - features: [] - 1500: - name: HyperLamp Navigation Tips - features: [] - 2000: - name: FusionDrive Ejection Configuration - features: [] diff --git a/docs/insiders/index.md b/docs/insiders/index.md deleted file mode 100644 index ce59f6bb..00000000 --- a/docs/insiders/index.md +++ /dev/null @@ -1,166 +0,0 @@ ---- -title: Insiders ---- - -# Insiders - -*mkdocstrings* follows the **sponsorware** release strategy, which means that new features are first exclusively released to sponsors as part of [Insiders][]. Read on to learn [what sponsorships achieve][sponsorship], [how to become a sponsor][sponsors] to get access to Insiders, and [what's in it for you][features]! - -## What is Insiders? - -*mkdocstrings Insiders* is a private fork of *mkdocstrings*, hosted as a private GitHub repository. Almost[^1] [all new features][features] are developed as part of this fork, which means that they are immediately available to all eligible sponsors, as they are granted access to this private repository. - -[^1]: In general, every new feature is first exclusively released to sponsors, but sometimes upstream dependencies enhance existing features that must be supported by *mkdocstrings*. - -Every feature is tied to a [funding goal][funding] in monthly subscriptions. When a funding goal is hit, the features that are tied to it are merged back into *mkdocstrings* and released for general availability, making them available to all users. Bugfixes are always released in tandem. - -Sponsorships start as low as [**$10 a month**][sponsors].[^2] - -[^2]: Note that $10 a month is the minimum amount to become eligible for Insiders. While GitHub Sponsors also allows to sponsor lower amounts or one-time amounts, those can't be granted access to Insiders due to technical reasons. Such contributions are still very much welcome as they help ensuring the project's sustainability. - -## What sponsorships achieve - -Sponsorships make this project sustainable, as they buy the maintainers of this project time – a very scarce resource – which is spent on the development of new features, bug fixing, stability improvement, issue triage and general support. The biggest bottleneck in Open Source is time.[^3] - -[^3]: Making an Open Source project sustainable is exceptionally hard: maintainers burn out, projects are abandoned. That's not great and very unpredictable. The sponsorware model ensures that if you decide to use *mkdocstrings*, you can be sure that bugs are fixed quickly and new features are added regularly. - -If you're unsure if you should sponsor this project, check out the list of [completed funding goals][goals completed] to learn whether you're already using features that were developed with the help of sponsorships. You're most likely using at least a handful of them, [thanks to our awesome sponsors][sponsors]! - -## What's in it for me? - -```python exec="1" session="insiders" -data_source = [ - "docs/insiders/goals.yml", - ("griffe-pydantic", "https://mkdocstrings.github.io/griffe-pydantic/", "insiders/goals.yml"), - ("griffe-typedoc", "https://mkdocstrings.github.io/griffe-typedoc/", "insiders/goals.yml"), - ("griffe-warnings-deprecated", "https://mkdocstrings.github.io/griffe-warnings-deprecated/", "insiders/goals.yml"), - ("mkdocstrings-c", "https://mkdocstrings.github.io/c/", "insiders/goals.yml"), - ("mkdocstrings-python", "https://mkdocstrings.github.io/python/", "insiders/goals.yml"), - ("mkdocstrings-shell", "https://mkdocstrings.github.io/shell/", "insiders/goals.yml"), - ("mkdocstrings-typescript", "https://mkdocstrings.github.io/typescript/", "insiders/goals.yml"), -] -``` - - -```python exec="1" session="insiders" idprefix="" ---8<-- "scripts/insiders.py" - -if unreleased_features: - print( - "The moment you [become a sponsor](#how-to-become-a-sponsor), you'll get **immediate " - f"access to {len(unreleased_features)} additional features** that you can start using right away, and " - "which are currently exclusively available to sponsors:\n" - ) - - for feature in unreleased_features: - feature.render(badge=True) - - print( - "\n\nThese are just the features related to this project. " - "[See the complete feature list on the author's main Insiders page](https://pawamoy.github.io/insiders/#whats-in-it-for-me)." - ) -else: - print( - "The moment you [become a sponsor](#how-to-become-a-sponsor), you'll get immediate " - "access to all released features that you can start using right away, and " - "which are exclusively available to sponsors. At this moment, there are no " - "Insiders features for this project, but checkout the [next funding goals](#goals) " - "to see what's coming, as well as **[the feature list for all Insiders projects](https://pawamoy.github.io/insiders/#whats-in-it-for-me).**" - ) -``` - - -Additionally, your sponsorship will give more weight to your upvotes on issues, helping us prioritize work items in our backlog. For more information on how we prioritize work, see this page: [Backlog management][backlog]. - -## How to become a sponsor - -Thanks for your interest in sponsoring! In order to become an eligible sponsor with your GitHub account, visit [pawamoy's sponsor profile][github sponsor profile], and complete a sponsorship of **$10 a month or more**. You can use your individual or organization GitHub account for sponsoring. - -Sponsorships lower than $10 a month are also very much appreciated, and useful. They won't grant you access to Insiders, but they will be counted towards reaching sponsorship goals. Every sponsorship helps us implementing new features and releasing them to the public. - -**Important:** By default, when you're sponsoring **[@pawamoy][github sponsor profile]** through a GitHub organization, all the publicly visible members of the organization will be invited to join our private repositories. If you wish to only grant access to a subset of users, please send a short email to insiders@pawamoy.fr with the name of your organization and the GitHub accounts of the users that should be granted access. - -**Tip:** to ensure that access is not tied to a particular individual GitHub account, you can create a bot account (i.e. a GitHub account that is not tied to a specific individual), and use this account for the sponsoring. After being granted access to our private repositories, the bot account can create private forks of our private repositories into your own organization, which all members of your organization will have access to. - -You can cancel your sponsorship anytime.[^5] - -[^5]: If you cancel your sponsorship, GitHub schedules a cancellation request which will become effective at the end of the billing cycle. This means that even though you cancel your sponsorship, you will keep your access to Insiders as long as your cancellation isn't effective. All charges are processed by GitHub through Stripe. As we don't receive any information regarding your payment, and GitHub doesn't offer refunds, sponsorships are non-refundable. - - -[:octicons-heart-fill-24:{ .pulse }   Join our awesome sponsors][github sponsor profile]{ .md-button .md-button--primary } - -
-
-
-
-
-
-
- -
- - - If you sponsor publicly, you're automatically added here with a link to your profile and avatar to show your support for *mkdocstrings*. Alternatively, if you wish to keep your sponsorship private, you'll be a silent +1. You can select visibility during checkout and change it afterwards. - - -## Funding - -### Goals - -The following section lists all funding goals. Each goal contains a list of features prefixed with a checkmark symbol, denoting whether a feature is :octicons-check-circle-fill-24:{ style="color: #00e676" } already available or :octicons-check-circle-fill-24:{ style="color: var(--md-default-fg-color--lightest)" } planned, but not yet implemented. When the funding goal is hit, the features are released for general availability. - -```python exec="1" session="insiders" idprefix="" -for goal in goals.values(): - if not goal.complete: - goal.render() -``` - -### Goals completed - -This section lists all funding goals that were previously completed, which means that those features were part of Insiders, but are now generally available and can be used by all users. - -```python exec="1" session="insiders" idprefix="" -for goal in goals.values(): - if goal.complete: - goal.render() -``` - -## Frequently asked questions - -### Compatibility - -> We're building an open source project and want to allow outside collaborators to use *mkdocstrings* locally without having access to Insiders. Is this still possible? - -Yes. Insiders is compatible with *mkdocstrings*. Almost all new features and configuration options are either backward-compatible or implemented behind feature flags. Most Insiders features enhance the overall experience, though while these features add value for the users of your project, they shouldn't be necessary for previewing when making changes to content. - -### Payment - -> We don't want to pay for sponsorship every month. Are there any other options? - -Yes. You can sponsor on a yearly basis by [switching your GitHub account to a yearly billing cycle][billing cycle]. If for some reason you cannot do that, you could also create a dedicated GitHub account with a yearly billing cycle, which you only use for sponsoring (some sponsors already do that). - -If you have any problems or further questions, please reach out to insiders@pawamoy.fr. - -### Terms - -> Are we allowed to use Insiders under the same terms and conditions as *mkdocstrings*? - -Yes. Whether you're an individual or a company, you may use *mkdocstrings Insiders* precisely under the same terms as *mkdocstrings*, which are given by the [ISC license][license]. However, we kindly ask you to respect our **fair use policy**: - -- Please **don't distribute the source code** of Insiders. You may freely use it for public, private or commercial projects, privately fork or mirror it, but please don't make the source code public, as it would counteract the sponsorware strategy. -- If you cancel your subscription, your access to the private repository is revoked, and you will miss out on all future updates of Insiders. However, you may **use the latest version** that's available to you **as long as you like**. Just remember that [GitHub deletes private forks][private forks]. - -[backlog]: https://pawamoy.github.io/backlog/ -[insiders]: #what-is-insiders -[sponsorship]: #what-sponsorships-achieve -[sponsors]: #how-to-become-a-sponsor -[features]: #whats-in-it-for-me -[funding]: #funding -[goals completed]: #goals-completed -[github sponsor profile]: https://github.com/sponsors/pawamoy -[billing cycle]: https://docs.github.com/en/github/setting-up-and-managing-billing-and-payments-on-github/changing-the-duration-of-your-billing-cycle -[license]: ../license.md -[private forks]: https://docs.github.com/en/github/setting-up-and-managing-your-github-user-account/removing-a-collaborator-from-a-personal-repository - - - diff --git a/docs/insiders/installation.md b/docs/insiders/installation.md deleted file mode 100644 index 1df4608b..00000000 --- a/docs/insiders/installation.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: Getting started with Insiders ---- - -# Getting started with Insiders - -*mkdocstrings Insiders* is a compatible drop-in replacement for *mkdocstrings*, and can be installed similarly using `pip` or `git`. Note that in order to access the Insiders repository, you need to [become an eligible sponsor][] of @pawamoy on GitHub. - -## Installation - -### with the `insiders` tool - -[`insiders`][insiders-tool] is a tool that helps you keep up-to-date versions of Insiders projects in the PyPI index of your choice (self-hosted, Google registry, Artifactory, etc.). - -**We kindly ask that you do not upload the distributions to public registries, as it is against our [Terms of use][].** - -### with pip (ssh/https) - -*mkdocstrings Insiders* can be installed with `pip` [using SSH][install-pip-ssh]: - -```bash -pip install git+ssh://git@github.com/pawamoy-insiders/mkdocstrings.git -``` - -Or using HTTPS: - -```bash -pip install git+https://${GH_TOKEN}@github.com/pawamoy-insiders/mkdocstrings.git -``` - ->? NOTE: **How to get a GitHub personal access token?** The `GH_TOKEN` environment variable is a GitHub token. It can be obtained by creating a [personal access token][github-pat] for your GitHub account. It will give you access to the Insiders repository, programmatically, from the command line or GitHub Actions workflows: -> -> 1. Go to https://github.com/settings/tokens -> 2. Click on [Generate a new token][github-pat-new] -> 3. Enter a name and select the [`repo`][scopes] scope -> 4. Generate the token and store it in a safe place -> -> Note that the personal access token must be kept secret at all times, as it allows the owner to access your private repositories. - -### with Git - -Of course, you can use *mkdocstrings Insiders* directly using Git: - -``` -git clone git@github.com:pawamoy-insiders/mkdocstrings -``` - -When cloning with Git, the package must be installed: - -``` -pip install -e mkdocstrings -``` - -## Upgrading - -When upgrading Insiders, you should always check the version of *mkdocstrings* which makes up the first part of the version qualifier. For example, a version like `8.x.x.4.x.x` means that Insiders `4.x.x` is currently based on `8.x.x`. - -If the major version increased, it's a good idea to consult the [changelog][] and go through the steps to ensure your configuration is up to date and all necessary changes have been made. - -[become an eligible sponsor]: ./index.md#how-to-become-a-sponsor -[changelog]: ./changelog.md -[github-pat]: https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token -[github-pat-new]: https://github.com/settings/tokens/new -[insiders-tool]: https://pawamoy.github.io/insiders-project/ -[install-pip-ssh]: https://docs.github.com/en/authentication/connecting-to-github-with-ssh -[scopes]: https://docs.github.com/en/developers/apps/scopes-for-oauth-apps#available-scopes -[terms of use]: ./index.md#terms diff --git a/docs/js/insiders.js b/docs/js/insiders.js deleted file mode 100644 index a86a0918..00000000 --- a/docs/js/insiders.js +++ /dev/null @@ -1,77 +0,0 @@ -function humanReadableAmount(amount) { - const strAmount = String(amount); - if (strAmount.length >= 4) { - return `${strAmount.slice(0, strAmount.length - 3)},${strAmount.slice(-3)}`; - } - return strAmount; -} - -function getJSON(url, callback) { - var xhr = new XMLHttpRequest(); - xhr.open('GET', url, true); - xhr.responseType = 'json'; - xhr.onload = function () { - var status = xhr.status; - if (status === 200) { - callback(null, xhr.response); - } else { - callback(status, xhr.response); - } - }; - xhr.send(); -} - -function updatePremiumSponsors(dataURL, rank) { - let capRank = rank.charAt(0).toUpperCase() + rank.slice(1); - getJSON(dataURL + `/sponsors${capRank}.json`, function (err, sponsors) { - const sponsorsDiv = document.getElementById(`${rank}-sponsors`); - if (sponsors.length > 0) { - let html = ''; - html += `${capRank} sponsors

` - sponsors.forEach(function (sponsor) { - html += `` - if (sponsor.image) { - html += `${sponsor.name}` - } else if (sponsor.imageLight && sponsor.imageDark) { - html += `${sponsor.name}` - html += `${sponsor.name}` - } - html += ''; - }); - html += '

' - sponsorsDiv.innerHTML = html; - } - }); -} - -function updateInsidersPage(author_username) { - const sponsorURL = `https://github.com/sponsors/${author_username}` - const dataURL = `https://raw.githubusercontent.com/${author_username}/sponsors/main`; - getJSON(dataURL + '/numbers.json', function (err, numbers) { - document.getElementById('sponsors-count').innerHTML = numbers.count; - Array.from(document.getElementsByClassName('sponsors-total')).forEach(function (element) { - element.innerHTML = '$ ' + humanReadableAmount(numbers.total); - }); - getJSON(dataURL + '/sponsors.json', function (err, sponsors) { - const sponsorsElem = document.getElementById('sponsors'); - const privateSponsors = numbers.count - sponsors.length; - sponsors.forEach(function (sponsor) { - sponsorsElem.innerHTML += ` - - - - `; - }); - if (privateSponsors > 0) { - sponsorsElem.innerHTML += ` - - +${privateSponsors} - - `; - } - }); - }); - updatePremiumSponsors(dataURL, "gold"); - updatePremiumSponsors(dataURL, "silver"); - updatePremiumSponsors(dataURL, "bronze"); -} diff --git a/duties.py b/duties.py index ee20b497..04357dbb 100644 --- a/duties.py +++ b/duties.py @@ -6,10 +6,9 @@ import re import sys from contextlib import contextmanager -from functools import wraps from importlib.metadata import version as pkgversion from pathlib import Path -from typing import TYPE_CHECKING, Any, Callable +from typing import TYPE_CHECKING from duty import duty, tools @@ -37,21 +36,6 @@ def pyprefix(title: str) -> str: return title -def not_from_insiders(func: Callable) -> Callable: - @wraps(func) - def wrapper(ctx: Context, *args: Any, **kwargs: Any) -> None: - origin = ctx.run("git config --get remote.origin.url", silent=True) - if "pawamoy-insiders/griffe" in origin: - ctx.run( - lambda: False, - title="Not running this task from insiders repository (do that from public repo instead!)", - ) - return - func(ctx, *args, **kwargs) - - return wrapper - - @contextmanager def material_insiders() -> Iterator[bool]: if "+insiders" in pkgversion("mkdocs-material"): @@ -145,39 +129,13 @@ def docs(ctx: Context, *cli_args: str, host: str = "127.0.0.1", port: int = 8000 @duty -def docs_deploy(ctx: Context, *, force: bool = False) -> None: - """Deploy the documentation to GitHub pages. - - Parameters: - force: Whether to force deployment, even from non-Insiders version. - """ +def docs_deploy(ctx: Context) -> None: + """Deploy the documentation to GitHub pages.""" os.environ["DEPLOY"] = "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, allow_overrides=False) - if "pawamoy-insiders/mkdocstrings" in origin: - ctx.run( - "git remote add org-pages git@github.com:mkdocstrings/mkdocstrings.github.io", - silent=True, - nofail=True, - allow_overrides=False, - ) - ctx.run( - tools.mkdocs.gh_deploy(remote_name="org-pages", force=True), - title="Deploying documentation", - ) - elif force: - ctx.run( - tools.mkdocs.gh_deploy(remote_name="org-pages", force=True), - title="Deploying documentation", - ) - else: - ctx.run( - lambda: False, - title="Not deploying docs from public repository (do that from insiders instead!)", - nofail=True, - ) + ctx.run(tools.mkdocs.gh_deploy(remote_name="org-pages", force=True), title="Deploying documentation") @duty @@ -201,7 +159,6 @@ def build(ctx: Context) -> None: @duty -@not_from_insiders def publish(ctx: Context) -> None: """Publish source and wheel distributions to PyPI.""" if not Path("dist").exists(): @@ -215,7 +172,6 @@ def publish(ctx: Context) -> None: @duty(post=["build", "publish", "docs-deploy"]) -@not_from_insiders def release(ctx: Context, version: str = "") -> None: """Release a new Python package. @@ -226,7 +182,7 @@ def release(ctx: Context, version: str = "") -> None: ctx.run("false", title="A version must be provided") ctx.run("git add pyproject.toml CHANGELOG.md", title="Staging files", pty=PTY) ctx.run(["git", "commit", "-m", f"chore: Prepare release {version}"], title="Committing changes", pty=PTY) - ctx.run(f"git tag {version}", title="Tagging commit", pty=PTY) + ctx.run(f"git tag -m '' -a {version}", title="Tagging commit", pty=PTY) ctx.run("git push", title="Pushing commits", pty=False) ctx.run("git push --tags", title="Pushing tags", pty=False) @@ -240,24 +196,14 @@ def coverage(ctx: Context) -> None: @duty(nofail=PY_VERSION == PY_DEV) -def test(ctx: Context, *cli_args: str, match: str = "") -> None: # noqa: PT028 - """Run the test suite. - - Parameters: - match: A pytest expression to filter selected tests. - """ +def test(ctx: Context, *cli_args: str) -> None: + """Run the test suite.""" os.environ["COVERAGE_FILE"] = f".coverage.{PY_VERSION}" os.environ["PYTHONWARNDEFAULTENCODING"] = "1" - config_file = "config/pytest.ini" - # YORE: EOL 3.9: Remove block. - if sys.version_info[:2] < (3, 10): - config_file = "config/pytest_39.ini" - ctx.run( tools.pytest( "tests", - config_file=config_file, - select=match, + config_file="config/pytest.ini", color="yes", ).add_args("-n", "auto", *cli_args), title=pyprefix("Running tests"), diff --git a/mkdocs.yml b/mkdocs.yml index 52721b3e..cdfb5173 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -41,11 +41,6 @@ nav: - Contributing: contributing.md - Code of Conduct: code_of_conduct.md - Coverage report: coverage.md -- Insiders: - - insiders/index.md - - Getting started: - - Installation: insiders/installation.md - - Changelog: insiders/changelog.md - Author's website: https://pawamoy.github.io/ theme: @@ -93,7 +88,6 @@ extra_css: - css/style.css - css/material.css - css/mkdocstrings.css -- css/insiders.css extra_javascript: - js/feedback.js @@ -158,7 +152,7 @@ plugins: show_root_heading: true show_root_full_path: false show_signature_annotations: true - show_source: false + show_source: true show_symbol_type_heading: true show_symbol_type_toc: true signature_crossrefs: true diff --git a/pyproject.toml b/pyproject.toml index 4db21462..14ba163b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ authors = [{name = "Timothée Mazzucotelli", email = "dev@pawamoy.fr"}] license = "ISC" license-files = ["LICENSE"] readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.10" keywords = ["mkdocs", "mkdocs-plugin", "docstrings", "autodoc", "documentation"] dynamic = ["version"] classifiers = [ @@ -18,7 +18,6 @@ classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", diff --git a/scripts/insiders.py b/scripts/insiders.py deleted file mode 100644 index 4cd438d4..00000000 --- a/scripts/insiders.py +++ /dev/null @@ -1,173 +0,0 @@ -# Functions related to Insiders funding goals. - -from __future__ import annotations - -import json -import logging -import os -import posixpath -from dataclasses import dataclass -from datetime import date, datetime, timedelta -from itertools import chain -from pathlib import Path -from typing import TYPE_CHECKING, cast -from urllib.error import HTTPError -from urllib.parse import urljoin -from urllib.request import urlopen - -import yaml - -if TYPE_CHECKING: - from collections.abc import Iterable - -logger = logging.getLogger(f"mkdocs.logs.{__name__}") - - -def human_readable_amount(amount: int) -> str: - str_amount = str(amount) - if len(str_amount) >= 4: # noqa: PLR2004 - return f"{str_amount[: len(str_amount) - 3]},{str_amount[-3:]}" - return str_amount - - -@dataclass -class Project: - name: str - url: str - - -@dataclass -class Feature: - name: str - ref: str | None - since: date | None - project: Project | None - - def url(self, rel_base: str = "..") -> str | None: # noqa: D102 - if not self.ref: - return None - if self.project: - rel_base = self.project.url - return posixpath.join(rel_base, self.ref.lstrip("/")) - - def render(self, rel_base: str = "..", *, badge: bool = False) -> None: # noqa: D102 - new = "" - if badge: - recent = self.since and date.today() - self.since <= timedelta(days=60) # noqa: DTZ011 - if recent: - ft_date = self.since.strftime("%B %d, %Y") # type: ignore[union-attr] - new = f' :material-alert-decagram:{{ .new-feature .vibrate title="Added on {ft_date}" }}' - project = f"[{self.project.name}]({self.project.url}) — " if self.project else "" - feature = f"[{self.name}]({self.url(rel_base)})" if self.ref else self.name - print(f"- [{'x' if self.since else ' '}] {project}{feature}{new}") - - -@dataclass -class Goal: - name: str - amount: int - features: list[Feature] - complete: bool = False - - @property - def human_readable_amount(self) -> str: # noqa: D102 - return human_readable_amount(self.amount) - - def render(self, rel_base: str = "..") -> None: # noqa: D102 - print(f"#### $ {self.human_readable_amount} — {self.name}\n") - if self.features: - for feature in self.features: - feature.render(rel_base) - print("") - else: - print("There are no features in this goal for this project. ") - print( - "[See the features in this goal **for all Insiders projects.**]" - f"(https://pawamoy.github.io/insiders/#{self.amount}-{self.name.lower().replace(' ', '-')})", - ) - - -def load_goals(data: str, funding: int = 0, project: Project | None = None) -> dict[int, Goal]: - goals_data = yaml.safe_load(data)["goals"] - return { - amount: Goal( - name=goal_data["name"], - amount=amount, - complete=funding >= amount, - features=[ - Feature( - name=feature_data["name"], - ref=feature_data.get("ref"), - since=feature_data.get("since") and datetime.strptime(feature_data["since"], "%Y/%m/%d").date(), # noqa: DTZ007 - project=project, - ) - for feature_data in goal_data["features"] - ], - ) - for amount, goal_data in goals_data.items() - } - - -def _load_goals_from_disk(path: str, funding: int = 0) -> dict[int, Goal]: - project_dir = os.getenv("MKDOCS_CONFIG_DIR", ".") - try: - data = Path(project_dir, path).read_text() - except OSError as error: - raise RuntimeError(f"Could not load data from disk: {path}") from error - return load_goals(data, funding) - - -def _load_goals_from_url(source_data: tuple[str, str, str], funding: int = 0) -> dict[int, Goal]: - project_name, project_url, data_fragment = source_data - data_url = urljoin(project_url, data_fragment) - try: - with urlopen(data_url) as response: # noqa: S310 - data = response.read() - except HTTPError as error: - raise RuntimeError(f"Could not load data from network: {data_url}") from error - return load_goals(data, funding, project=Project(name=project_name, url=project_url)) - - -def _load_goals(source: str | tuple[str, str, str], funding: int = 0) -> dict[int, Goal]: - if isinstance(source, str): - return _load_goals_from_disk(source, funding) - return _load_goals_from_url(source, funding) - - -def funding_goals(source: str | list[str | tuple[str, str, str]], funding: int = 0) -> dict[int, Goal]: - if isinstance(source, str): - return _load_goals_from_disk(source, funding) - goals = {} - for src in source: - source_goals = _load_goals(src, funding) - for amount, goal in source_goals.items(): - if amount not in goals: - goals[amount] = goal - else: - goals[amount].features.extend(goal.features) - return {amount: goals[amount] for amount in sorted(goals)} - - -def feature_list(goals: Iterable[Goal]) -> list[Feature]: - return list(chain.from_iterable(goal.features for goal in goals)) - - -def load_json(url: str) -> str | list | dict: - with urlopen(url) as response: # noqa: S310 - return json.loads(response.read().decode()) - - -data_source = globals()["data_source"] -sponsor_url = "https://github.com/sponsors/pawamoy" -data_url = "https://raw.githubusercontent.com/pawamoy/sponsors/main" -numbers: dict[str, int] = load_json(f"{data_url}/numbers.json") # type: ignore[assignment] -sponsors: list[dict] = load_json(f"{data_url}/sponsors.json") # type: ignore[assignment] -current_funding = numbers["total"] -sponsors_count = numbers["count"] -goals = funding_goals(data_source, funding=current_funding) -ongoing_goals = [goal for goal in goals.values() if not goal.complete] -unreleased_features = sorted( - (ft for ft in feature_list(ongoing_goals) if ft.since), - key=lambda ft: cast("date", ft.since), - reverse=True, -) diff --git a/scripts/make.py b/scripts/make.py index 1e697bcc..b741a366 100755 --- a/scripts/make.py +++ b/scripts/make.py @@ -14,8 +14,8 @@ from collections.abc import Iterator -PYTHON_VERSIONS = os.getenv("PYTHON_VERSIONS", "3.9 3.10 3.11 3.12 3.13 3.14").split() -PYTHON_DEV = "3.14" +PYTHON_VERSIONS = os.getenv("PYTHON_VERSIONS", "3.10 3.11 3.12 3.13 3.14 3.15").split() +PYTHON_DEV = "3.15" def shell(cmd: str, *, capture_output: bool = False, **kwargs: Any) -> str | None: From 39fbea1c2dd030f017ae6fd4a8653959f310a693 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 11 Nov 2025 00:58:50 +0000 Subject: [PATCH 05/12] chore: Update sponsors section in README --- README.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/README.md b/README.md index 20f6fd39..a486ed40 100644 --- a/README.md +++ b/README.md @@ -137,4 +137,62 @@ See the [Usage](https://mkdocstrings.github.io/usage) section of the docs for mo ## Sponsors + +
+ +
Silver sponsors

+Material for MkDocs
+FastAPI
+Pydantic
+

+ +
Bronze sponsors

+Nixtla
+

+
+ +--- + +

+ofek +samuelcolvin +tlambert03 +ssbarnea +femtomc +cmarqu +kolenaIO +ramnes +machow +BenHammersley +trevorWieland +laenan8466 +MarcoGorelli +analog-cbarber +OdinManiac +rstudio-sponsorship +schlich +SuperCowPowers +butterlyn +livingbio +NemetschekAllplan +EricJayHartman +15r10nk +cdwilson +activeloopai +roboflow +wrath-codes +leodevian +cmclaughlin +blaisep +RapidataAI +rodolphebarbanneau +theSymbolSyndicate +blakeNaccarato +ChargeStorm +Alphadelta14 +

+ + +*And 8 more private sponsor(s).* + From 3076375ec7eb4c9c8b739e414a919a1be29df07a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Wed, 19 Nov 2025 18:39:13 +0100 Subject: [PATCH 06/12] chore: Specify encoding when reading file in tests --- tests/test_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_plugin.py b/tests/test_plugin.py index acb9556c..833de692 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -41,7 +41,7 @@ def test_disabling_plugin(tmp_path: Path) -> None: mkdocs_config["plugins"].run_event("shutdown") # make sure the instruction was not processed - assert "::: mkdocstrings" in site_dir.joinpath("index.html").read_text() + assert "::: mkdocstrings" in site_dir.joinpath("index.html").read_text(encoding="utf8") def test_plugin_default_config(tmp_path: Path) -> None: From 6de266759b79eb72cddd300e6a0a8576085fae40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Wed, 19 Nov 2025 19:57:17 +0100 Subject: [PATCH 07/12] refactor: Expose the Markdown extension, to make mkdocstrings compatible with Zensical --- src/mkdocstrings/__init__.py | 3 +- src/mkdocstrings/_internal/extension.py | 79 ++++++++++++++++++++- src/mkdocstrings/_internal/handlers/base.py | 6 +- 3 files changed, 83 insertions(+), 5 deletions(-) diff --git a/src/mkdocstrings/__init__.py b/src/mkdocstrings/__init__.py index 71720f8a..137811b1 100644 --- a/src/mkdocstrings/__init__.py +++ b/src/mkdocstrings/__init__.py @@ -5,7 +5,7 @@ from __future__ import annotations -from mkdocstrings._internal.extension import AutoDocProcessor, MkdocstringsExtension +from mkdocstrings._internal.extension import AutoDocProcessor, MkdocstringsExtension, makeExtension from mkdocstrings._internal.handlers.base import ( BaseHandler, CollectionError, @@ -62,4 +62,5 @@ "get_template_logger", "get_template_logger_function", "get_template_path", + "makeExtension", ] diff --git a/src/mkdocstrings/_internal/extension.py b/src/mkdocstrings/_internal/extension.py index 2277775c..c06a967b 100644 --- a/src/mkdocstrings/_internal/extension.py +++ b/src/mkdocstrings/_internal/extension.py @@ -35,6 +35,7 @@ from markdown.extensions import Extension from markdown.treeprocessors import Treeprocessor from mkdocs.exceptions import PluginError +from mkdocs_autorefs import AutorefsConfig, AutorefsPlugin from mkdocstrings._internal.handlers.base import BaseHandler, CollectionError, CollectorItem, Handlers from mkdocstrings._internal.loggers import get_logger @@ -43,7 +44,6 @@ from collections.abc import MutableSequence from markdown import Markdown - from mkdocs_autorefs import AutorefsPlugin _logger = get_logger("mkdocstrings") @@ -380,3 +380,80 @@ def extendMarkdown(self, md: Markdown) -> None: # noqa: N802 (casing: parent me "mkdocstrings_post_toc_labels", priority=4, # Right after 'toc'. ) + + +# ----------------------------------------------------------------------------- +# The following is only used by Zensical. The goal is to provide temporary +# compatibility for users migrating from MkDocs (and Material for MkDocs) +# to Zensical. When detecting the use of the mkdocstrings plugin in mkdocs.yml, +# Zensical will add the mkdocstrings extension to its Markdown extensions. + +_default_config: dict[str, Any] = { + "default_handler": "python", + "handlers": {}, + "custom_templates": None, + "locale": "en", + "enable_inventory": True, + "enabled": True, +} + + +def _split_configs(markdown_extensions: list[str | dict]) -> tuple[list[str], dict[str, Any]]: + # Split markdown extensions and their configs from mkdocs.yml + mdx: list[str] = [] + mdx_config: dict[str, Any] = {} + for item in markdown_extensions: + if isinstance(item, str): + mdx.append(item) + elif isinstance(item, dict): + for key, value in item.items(): + mdx.append(key) + mdx_config[key] = value + break # Only one item per dict + return mdx, mdx_config + + +def makeExtension( # noqa: N802 + *args: Any, # noqa: ARG001 + **kwargs: Any, +) -> MkdocstringsExtension: + """Create the extension instance.""" + from zensical.config import _yaml_load # noqa: PLC0415 + + with open("mkdocs.yml", encoding="utf-8") as f: + mkdocs_config = _yaml_load(f) + + mkdocstrings_config = mkdocs_config.get("plugins", None) + if isinstance(mkdocstrings_config, dict): + mkdocstrings_config = mkdocstrings_config.get("mkdocstrings", {}) + elif isinstance(mkdocstrings_config, list): + for plugin in mkdocstrings_config: + if isinstance(plugin, dict) and "mkdocstrings" in plugin: + mkdocstrings_config = plugin["mkdocstrings"] + break + else: + mkdocstrings_config = _default_config + else: + mkdocstrings_config = _default_config + + mdx, mdx_config = _split_configs(mkdocs_config.get("markdown_extensions", [])) + + handlers = Handlers( + theme="material", + default=mkdocstrings_config.get("default_handler", _default_config["default_handler"]), + inventory_project=mkdocs_config.get("site_name", "Project"), + handlers_config=mkdocstrings_config.get("handlers", _default_config["handlers"]), + custom_templates=mkdocstrings_config.get("custom_templates", _default_config["custom_templates"]), + mdx=mdx, + mdx_config=mdx_config, + locale=mkdocstrings_config.get("locale", _default_config["locale"]), + tool_config=mkdocs_config, + ) + + handlers._download_inventories() + + autorefs = AutorefsPlugin() + autorefs.config = AutorefsConfig() + autorefs.scan_toc = False + + return MkdocstringsExtension(handlers=handlers, autorefs=autorefs, **kwargs) diff --git a/src/mkdocstrings/_internal/handlers/base.py b/src/mkdocstrings/_internal/handlers/base.py index c4e9950d..af68a45c 100644 --- a/src/mkdocstrings/_internal/handlers/base.py +++ b/src/mkdocstrings/_internal/handlers/base.py @@ -436,8 +436,7 @@ def do_convert_markdown( treeprocessors[ParagraphStrippingTreeprocessor.name].strip = strip_paragraph # type: ignore[attr-defined] if BacklinksTreeProcessor.name in treeprocessors: treeprocessors[BacklinksTreeProcessor.name].initial_id = html_id # type: ignore[attr-defined] - - if autoref_hook: + if autoref_hook and AutorefsInlineProcessor.name in self.md.inlinePatterns: self.md.inlinePatterns[AutorefsInlineProcessor.name].hook = autoref_hook # type: ignore[attr-defined] try: @@ -448,7 +447,8 @@ def do_convert_markdown( treeprocessors[ParagraphStrippingTreeprocessor.name].strip = False # type: ignore[attr-defined] if BacklinksTreeProcessor.name in treeprocessors: treeprocessors[BacklinksTreeProcessor.name].initial_id = None # type: ignore[attr-defined] - self.md.inlinePatterns[AutorefsInlineProcessor.name].hook = None # type: ignore[attr-defined] + if AutorefsInlineProcessor.name in self.md.inlinePatterns: + self.md.inlinePatterns[AutorefsInlineProcessor.name].hook = None # type: ignore[attr-defined] self.md.reset() _markdown_conversion_layer -= 1 From fc4d588dc73b12c205a933a0a5808742119aa7d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Thu, 27 Nov 2025 14:47:00 +0100 Subject: [PATCH 08/12] docs: Announce maintenance mode --- docs/.overrides/main.html | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/docs/.overrides/main.html b/docs/.overrides/main.html index c702362f..3bfd4775 100644 --- a/docs/.overrides/main.html +++ b/docs/.overrides/main.html @@ -1,18 +1,7 @@ {% extends "base.html" %} {% block announce %} - Fund this project through - sponsorship - - {% include ".icons/octicons/heart-fill-16.svg" %} - — - Follow - @pawamoy on - - - {% include ".icons/fontawesome/brands/mastodon.svg" %} - - Fosstodon - - for updates + ⚠️ mkdocstrings is in maintenance mode! + blog post + {% endblock %} From bebbb88d3f09249b0129b05f98fdbd9f2eaa6818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Thu, 27 Nov 2025 14:47:24 +0100 Subject: [PATCH 09/12] chore: Remove trailing space --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index cdfb5173..8250973b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -26,7 +26,7 @@ nav: - All handlers: - C: https://mkdocstrings.github.io/c/ - Crystal: https://mkdocstrings.github.io/crystal/ - - GitHub Actions: https://watermarkhu.nl/mkdocstrings-github/ + - GitHub Actions: https://watermarkhu.nl/mkdocstrings-github/ - Python: https://mkdocstrings.github.io/python/ - Python (Legacy): https://mkdocstrings.github.io/python-legacy/ - MATLAB: https://watermarkhu.nl/mkdocstrings-matlab/ From 6b73d5a2f455062ab6c68376c85adce6adc037a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Thu, 27 Nov 2025 15:32:23 +0100 Subject: [PATCH 10/12] refactor: Expect Zensical to pass extension configuration instead of loading it again from YAML --- src/mkdocstrings/_internal/extension.py | 60 ++++++++++++------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/mkdocstrings/_internal/extension.py b/src/mkdocstrings/_internal/extension.py index c06a967b..8ccea142 100644 --- a/src/mkdocstrings/_internal/extension.py +++ b/src/mkdocstrings/_internal/extension.py @@ -413,47 +413,47 @@ def _split_configs(markdown_extensions: list[str | dict]) -> tuple[list[str], di return mdx, mdx_config +class _ToolConfig: + def __init__(self, config_file_path: str | None = None) -> None: + self.config_file_path = config_file_path + + def makeExtension( # noqa: N802 - *args: Any, # noqa: ARG001 - **kwargs: Any, + *, + default_handler: str | None = None, + inventory_project: str | None = None, + inventory_version: str | None = None, + handlers: dict[str, dict] | None = None, + custom_templates: str | None = None, + markdown_extensions: list[str | dict] | None = None, + locale: str | None = None, + config_file_path: str | None = None, ) -> MkdocstringsExtension: - """Create the extension instance.""" - from zensical.config import _yaml_load # noqa: PLC0415 - - with open("mkdocs.yml", encoding="utf-8") as f: - mkdocs_config = _yaml_load(f) - - mkdocstrings_config = mkdocs_config.get("plugins", None) - if isinstance(mkdocstrings_config, dict): - mkdocstrings_config = mkdocstrings_config.get("mkdocstrings", {}) - elif isinstance(mkdocstrings_config, list): - for plugin in mkdocstrings_config: - if isinstance(plugin, dict) and "mkdocstrings" in plugin: - mkdocstrings_config = plugin["mkdocstrings"] - break - else: - mkdocstrings_config = _default_config - else: - mkdocstrings_config = _default_config + """Create the extension instance. - mdx, mdx_config = _split_configs(mkdocs_config.get("markdown_extensions", [])) + We only support this function being used by Zensical. + Consider this function private API. + """ + mdx, mdx_config = _split_configs(markdown_extensions or []) + tool_config = _ToolConfig(config_file_path=config_file_path) - handlers = Handlers( + handlers_instance = Handlers( theme="material", - default=mkdocstrings_config.get("default_handler", _default_config["default_handler"]), - inventory_project=mkdocs_config.get("site_name", "Project"), - handlers_config=mkdocstrings_config.get("handlers", _default_config["handlers"]), - custom_templates=mkdocstrings_config.get("custom_templates", _default_config["custom_templates"]), + default=default_handler or _default_config["default_handler"], + inventory_project=inventory_project or "Project", + inventory_version=inventory_version or "0.0.0", + handlers_config=handlers or _default_config["handlers"], + custom_templates=custom_templates or _default_config["custom_templates"], mdx=mdx, mdx_config=mdx_config, - locale=mkdocstrings_config.get("locale", _default_config["locale"]), - tool_config=mkdocs_config, + locale=locale or _default_config["locale"], + tool_config=tool_config, ) - handlers._download_inventories() + handlers_instance._download_inventories() autorefs = AutorefsPlugin() autorefs.config = AutorefsConfig() autorefs.scan_toc = False - return MkdocstringsExtension(handlers=handlers, autorefs=autorefs, **kwargs) + return MkdocstringsExtension(handlers=handlers_instance, autorefs=autorefs) From de34044a02b45250e215af0f969dca581dfb82c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Thu, 27 Nov 2025 15:42:59 +0100 Subject: [PATCH 11/12] refactor: Remove deprecated code before v1 --- pyproject.toml | 5 +- src/mkdocstrings/_internal/extension.py | 33 +--- src/mkdocstrings/_internal/handlers/base.py | 174 ++------------------ src/mkdocstrings/_internal/loggers.py | 6 +- src/mkdocstrings/_internal/plugin.py | 5 - src/mkdocstrings/extension.py | 17 -- src/mkdocstrings/handlers/__init__.py | 3 - src/mkdocstrings/handlers/base.py | 17 -- src/mkdocstrings/handlers/rendering.py | 17 -- src/mkdocstrings/inventory.py | 17 -- src/mkdocstrings/loggers.py | 17 -- src/mkdocstrings/plugin.py | 17 -- tests/test_api.py | 5 - 13 files changed, 22 insertions(+), 311 deletions(-) delete mode 100644 src/mkdocstrings/extension.py delete mode 100644 src/mkdocstrings/handlers/__init__.py delete mode 100644 src/mkdocstrings/handlers/base.py delete mode 100644 src/mkdocstrings/handlers/rendering.py delete mode 100644 src/mkdocstrings/inventory.py delete mode 100644 src/mkdocstrings/loggers.py delete mode 100644 src/mkdocstrings/plugin.py diff --git a/pyproject.toml b/pyproject.toml index 14ba163b..2b3d6c17 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,15 +30,12 @@ classifiers = [ "Typing :: Typed", ] dependencies = [ - # YORE: Bump 1: Replace `2.11.1` with `3.1` within line. - "Jinja2>=2.11.1", + "Jinja2>=3.1", "Markdown>=3.6", "MarkupSafe>=1.1", "mkdocs>=1.6", "mkdocs-autorefs>=1.4", "pymdown-extensions>=6.3", - # YORE: EOL 3.9: Remove line. - "importlib-metadata>=4.6; python_version < '3.10'", ] [project.optional-dependencies] diff --git a/src/mkdocstrings/_internal/extension.py b/src/mkdocstrings/_internal/extension.py index 8ccea142..3a6f4530 100644 --- a/src/mkdocstrings/_internal/extension.py +++ b/src/mkdocstrings/_internal/extension.py @@ -26,7 +26,6 @@ from functools import partial from inspect import signature from typing import TYPE_CHECKING, Any -from warnings import warn from xml.etree.ElementTree import Element import yaml @@ -172,19 +171,7 @@ def _process_block( # Heading level obtained from Markdown (`##`) takes precedence. local_options["heading_level"] = heading_level - # YORE: Bump 1: Replace block with line 2. - if handler.get_options.__func__ is not BaseHandler.get_options: # type: ignore[attr-defined] - options = handler.get_options(local_options) - else: - warn( - "mkdocstrings v1 will start using your handler's `get_options` method to build options " - "instead of merging the global and local options (dictionaries). ", - DeprecationWarning, - stacklevel=1, - ) - handler_config = self._handlers.get_handler_config(handler_name) - global_options = handler_config.get("options", {}) - options = {**global_options, **local_options} + options = handler.get_options(local_options) _logger.debug("Collecting data") try: @@ -266,23 +253,7 @@ def _process_headings(self, handler: BaseHandler, element: Element) -> None: # Register all identifiers for this object # both in the autorefs plugin and in the inventory. aliases: tuple[str, ...] - # YORE: Bump 1: Replace block with line 16. - if hasattr(handler, "get_anchors"): - warn( - "The `get_anchors` method is deprecated. " - "Declare a `get_aliases` method instead, accepting a string (identifier) " - "instead of a collected object.", - DeprecationWarning, - stacklevel=1, - ) - try: - data_object = handler.collect(rendered_id, getattr(handler, "fallback_config", {})) - except CollectionError: - aliases = () - else: - aliases = handler.get_anchors(data_object) - else: - aliases = handler.get_aliases(rendered_id) + aliases = handler.get_aliases(rendered_id) for alias in aliases: if alias != rendered_id: diff --git a/src/mkdocstrings/_internal/handlers/base.py b/src/mkdocstrings/_internal/handlers/base.py index af68a45c..2eb9b3e6 100644 --- a/src/mkdocstrings/_internal/handlers/base.py +++ b/src/mkdocstrings/_internal/handlers/base.py @@ -6,10 +6,9 @@ import datetime import importlib -import inspect import ssl -import sys from concurrent import futures +from importlib.metadata import entry_points from io import BytesIO from pathlib import Path from typing import TYPE_CHECKING, Any, BinaryIO, ClassVar, cast @@ -33,12 +32,6 @@ from mkdocstrings._internal.inventory import Inventory from mkdocstrings._internal.loggers import get_logger, get_template_logger -# YORE: EOL 3.9: Replace block with line 4. -if sys.version_info < (3, 10): - from importlib_metadata import entry_points -else: - from importlib.metadata import entry_points - if TYPE_CHECKING: from collections.abc import Iterable, Iterator, Mapping, Sequence @@ -104,21 +97,15 @@ class BaseHandler: To add custom CSS, add an `extra_css` variable or create an 'style.css' file beside the templates. """ - # YORE: Bump 1: Replace ` = ""` with `` within line. - name: ClassVar[str] = "" + name: ClassVar[str] """The handler's name, for example "python".""" - # YORE: Bump 1: Replace ` = ""` with `` within line. - domain: ClassVar[str] = "" + domain: ClassVar[str] """The handler's domain, used to register objects in the inventory, for example "py".""" enable_inventory: ClassVar[bool] = False """Whether the inventory creation is enabled.""" - # YORE: Bump 1: Remove block. - fallback_config: ClassVar[dict] = {} - """Fallback configuration when searching anchors for identifiers.""" - fallback_theme: ClassVar[str] = "" """Fallback theme to use when a template isn't found in the configured theme.""" @@ -127,16 +114,11 @@ class BaseHandler: def __init__( self, - # YORE: Bump 1: Remove line. - *args: Any, - # YORE: Bump 1: Remove line. - **kwargs: Any, - # YORE: Bump 1: Replace `# ` with `` within block. - # *, - # theme: str, - # custom_templates: str | None, - # mdx: Sequence[str | Extension], - # mdx_config: Mapping[str, Any], + *, + theme: str, + custom_templates: str | None, + mdx: Sequence[str | Extension], + mdx_config: Mapping[str, Any], ) -> None: """Initialize the object. @@ -149,58 +131,6 @@ def __init__( mdx (list[str | Extension]): A list of Markdown extensions to use. mdx_config (Mapping[str, Mapping[str, Any]]): Configuration for the Markdown extensions. """ - # YORE: Bump 1: Remove block. - handler = "" - theme = "" - custom_templates = None - if args: - handler, args = args[0], args[1:] - if args: - theme, args = args[0], args[1:] - warn( - "The `theme` argument must be passed as a keyword argument.", - DeprecationWarning, - stacklevel=2, - ) - if args: - custom_templates, args = args[0], args[1:] - warn( - "The `custom_templates` argument must be passed as a keyword argument.", - DeprecationWarning, - stacklevel=2, - ) - handler = kwargs.pop("handler", handler) - theme = kwargs.pop("theme", theme) - custom_templates = kwargs.pop("custom_templates", custom_templates) - mdx = kwargs.pop("mdx", None) - mdx_config = kwargs.pop("mdx_config", None) - if handler: - if not self.name: - type(self).name = handler - warn( - "The `handler` argument is deprecated. The handler name must be specified as a class attribute.", - DeprecationWarning, - stacklevel=2, - ) - if not self.domain: - warn( - "The `domain` attribute must be specified as a class attribute.", - DeprecationWarning, - stacklevel=2, - ) - if mdx is None: - warn( - "The `mdx` argument must be provided (as a keyword argument).", - DeprecationWarning, - stacklevel=2, - ) - if mdx_config is None: - warn( - "The `mdx_config` argument must be provided (as a keyword argument).", - DeprecationWarning, - stacklevel=2, - ) - self.theme = theme """The selected theme.""" self.custom_templates = custom_templates @@ -533,20 +463,11 @@ def get_headings(self) -> Sequence[Element]: self._headings.clear() return result - # YORE: Bump 1: Replace `*args: Any, **kwargs: Any` with `config: Any`. - def update_env(self, *args: Any, **kwargs: Any) -> None: # noqa: ARG002 + def update_env(self, config: Any) -> None: """Update the Jinja environment.""" - # YORE: Bump 1: Remove line. - warn("No need to call `super().update_env()` anymore.", DeprecationWarning, stacklevel=2) def _update_env(self, md: Markdown, *, config: Any | None = None) -> None: """Update our handler to point to our configured Markdown instance, grabbing some of the config from `md`.""" - # YORE: Bump 1: Remove block. - if self.mdx is None and config is not None: - self.mdx = config.get("mdx", None) or config.get("markdown_extensions", None) or () - if self.mdx_config is None and config is not None: - self.mdx_config = config.get("mdx_config", None) or config.get("mdx_configs", None) or {} - extensions: list[str | Extension] = [*self.mdx, MkdocstringsInnerExtension(self._headings)] new_md = Markdown(extensions=extensions, extension_configs=self.mdx_config) @@ -561,17 +482,7 @@ def _update_env(self, md: Markdown, *, config: Any | None = None) -> None: self.env.filters["highlight"] = Highlighter(new_md).highlight - # YORE: Bump 1: Replace block with `self.update_env(config)`. - parameters = inspect.signature(self.update_env).parameters - if "md" in parameters: - warn( - "The `update_env(md)` parameter is deprecated. Use `self.md` instead.", - DeprecationWarning, - stacklevel=1, - ) - self.update_env(new_md, config) - elif "config" in parameters: - self.update_env(config) + self.update_env(config) class Handlers: @@ -624,35 +535,6 @@ def __init__( self._inv_futures: dict[futures.Future, tuple[BaseHandler, str, Any]] = {} - # YORE: Bump 1: Remove block. - def get_anchors(self, identifier: str) -> tuple[str, ...]: - """Return the canonical HTML anchor for the identifier, if any of the seen handlers can collect it. - - Arguments: - identifier: The identifier (one that [collect][mkdocstrings.BaseHandler.collect] can accept). - - Returns: - A tuple of strings - anchors without '#', or an empty tuple if there isn't any identifier familiar with it. - """ - for handler in self._handlers.values(): - try: - if hasattr(handler, "get_anchors"): - warn( - "The `get_anchors` method is deprecated. " - "Declare a `get_aliases` method instead, accepting a string (identifier) " - "instead of a collected object.", - DeprecationWarning, - stacklevel=1, - ) - aliases = handler.get_anchors(handler.collect(identifier, getattr(handler, "fallback_config", {}))) - else: - aliases = handler.get_aliases(identifier) - except CollectionError: - continue - if aliases: - return aliases - return () - def get_handler_name(self, config: dict) -> str: """Return the handler name defined in an "autodoc" instruction YAML configuration, or the global default handler. @@ -696,34 +578,14 @@ def get_handler(self, name: str, handler_config: dict | None = None) -> BaseHand handler_config = self._handlers_config.get(name, {}) module = importlib.import_module(f"mkdocstrings_handlers.{name}") - # YORE: Bump 1: Remove block. - kwargs = { - "theme": self._theme, - "custom_templates": self._custom_templates, - "mdx": self._mdx, - "mdx_config": self._mdx_config, - "handler_config": handler_config, - "tool_config": self._tool_config, - } - if "config_file_path" in inspect.signature(module.get_handler).parameters: - kwargs["config_file_path"] = self._tool_config.get("config_file_path") - warn( - "The `config_file_path` argument in `get_handler` functions is deprecated. " - "Use `tool_config.get('config_file_path')` instead.", - DeprecationWarning, - stacklevel=1, - ) - self._handlers[name] = module.get_handler(**kwargs) - - # YORE: Bump 1: Replace `# ` with `` within block. - # self._handlers[name] = module.get_handler( - # theme=self._theme, - # custom_templates=self._custom_templates, - # mdx=self._mdx, - # mdx_config=self._mdx_config, - # handler_config=handler_config, - # tool_config=self._tool_config, - # ) + self._handlers[name] = module.get_handler( + theme=self._theme, + custom_templates=self._custom_templates, + mdx=self._mdx, + mdx_config=self._mdx_config, + handler_config=handler_config, + tool_config=self._tool_config, + ) return self._handlers[name] def _download_inventories(self) -> None: diff --git a/src/mkdocstrings/_internal/loggers.py b/src/mkdocstrings/_internal/loggers.py index 6c6304c3..c67a7f4e 100644 --- a/src/mkdocstrings/_internal/loggers.py +++ b/src/mkdocstrings/_internal/loggers.py @@ -7,11 +7,7 @@ from pathlib import Path from typing import TYPE_CHECKING, Any, Callable -# YORE: Bump 1: Replace block with line 2. -try: - from jinja2 import pass_context -except ImportError: - from jinja2 import contextfunction as pass_context # type: ignore[attr-defined,no-redef] +from jinja2 import pass_context if TYPE_CHECKING: from collections.abc import MutableMapping, Sequence diff --git a/src/mkdocstrings/_internal/plugin.py b/src/mkdocstrings/_internal/plugin.py index 2af14a7a..2c27a60a 100644 --- a/src/mkdocstrings/_internal/plugin.py +++ b/src/mkdocstrings/_internal/plugin.py @@ -19,7 +19,6 @@ from inspect import signature from re import Match from typing import TYPE_CHECKING, Any -from warnings import catch_warnings, simplefilter from mkdocs.config import Config from mkdocs.config import config_options as opt @@ -166,10 +165,6 @@ def on_config(self, config: MkDocsConfig) -> MkDocsConfig | None: autorefs.scan_toc = False config.plugins["autorefs"] = autorefs _logger.debug("Added a subdued autorefs instance %r", autorefs) - # YORE: Bump 1: Remove block. - with catch_warnings(): - simplefilter("ignore", category=DeprecationWarning) - autorefs.get_fallback_anchor = handlers.get_anchors mkdocstrings_extension = MkdocstringsExtension(handlers, autorefs) config.markdown_extensions.append(mkdocstrings_extension) # type: ignore[arg-type] diff --git a/src/mkdocstrings/extension.py b/src/mkdocstrings/extension.py deleted file mode 100644 index c7943652..00000000 --- a/src/mkdocstrings/extension.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Deprecated. Import from `mkdocstrings` directly.""" - -# YORE: Bump 1: Remove file. - -import warnings -from typing import Any - -from mkdocstrings._internal import extension - - -def __getattr__(name: str) -> Any: - warnings.warn( - "Importing from `mkdocstrings.extension` is deprecated. Import from `mkdocstrings` directly.", - DeprecationWarning, - stacklevel=2, - ) - return getattr(extension, name) diff --git a/src/mkdocstrings/handlers/__init__.py b/src/mkdocstrings/handlers/__init__.py deleted file mode 100644 index b684324a..00000000 --- a/src/mkdocstrings/handlers/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Deprecated. Import from `mkdocstrings` directly.""" - -# YORE: Bump 1: Remove file. diff --git a/src/mkdocstrings/handlers/base.py b/src/mkdocstrings/handlers/base.py deleted file mode 100644 index c55a50ba..00000000 --- a/src/mkdocstrings/handlers/base.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Deprecated. Import from `mkdocstrings` directly.""" - -# YORE: Bump 1: Remove file. - -import warnings -from typing import Any - -from mkdocstrings._internal.handlers import base - - -def __getattr__(name: str) -> Any: - warnings.warn( - "Importing from `mkdocstrings.handlers.base` is deprecated. Import from `mkdocstrings` directly.", - DeprecationWarning, - stacklevel=2, - ) - return getattr(base, name) diff --git a/src/mkdocstrings/handlers/rendering.py b/src/mkdocstrings/handlers/rendering.py deleted file mode 100644 index f3f04eea..00000000 --- a/src/mkdocstrings/handlers/rendering.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Deprecated. Import from `mkdocstrings` directly.""" - -# YORE: Bump 1: Remove file. - -import warnings -from typing import Any - -from mkdocstrings._internal.handlers import rendering - - -def __getattr__(name: str) -> Any: - warnings.warn( - "Importing from `mkdocstrings.handlers.rendering` is deprecated. Import from `mkdocstrings` directly.", - DeprecationWarning, - stacklevel=2, - ) - return getattr(rendering, name) diff --git a/src/mkdocstrings/inventory.py b/src/mkdocstrings/inventory.py deleted file mode 100644 index 7192acff..00000000 --- a/src/mkdocstrings/inventory.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Deprecated. Import from `mkdocstrings` directly.""" - -# YORE: Bump 1: Remove file. - -import warnings -from typing import Any - -from mkdocstrings._internal import inventory - - -def __getattr__(name: str) -> Any: - warnings.warn( - "Importing from `mkdocstrings.inventory` is deprecated. Import from `mkdocstrings` directly.", - DeprecationWarning, - stacklevel=2, - ) - return getattr(inventory, name) diff --git a/src/mkdocstrings/loggers.py b/src/mkdocstrings/loggers.py deleted file mode 100644 index 25545ca5..00000000 --- a/src/mkdocstrings/loggers.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Deprecated. Import from `mkdocstrings` directly.""" - -# YORE: Bump 1: Remove file. - -import warnings -from typing import Any - -from mkdocstrings._internal import loggers - - -def __getattr__(name: str) -> Any: - warnings.warn( - "Importing from `mkdocstrings.loggers` is deprecated. Import from `mkdocstrings` directly.", - DeprecationWarning, - stacklevel=2, - ) - return getattr(loggers, name) diff --git a/src/mkdocstrings/plugin.py b/src/mkdocstrings/plugin.py deleted file mode 100644 index dbb6abf9..00000000 --- a/src/mkdocstrings/plugin.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Deprecated. Import from `mkdocstrings` directly.""" - -# YORE: Bump 1: Remove file. - -import warnings -from typing import Any - -from mkdocstrings._internal import plugin - - -def __getattr__(name: str) -> Any: - warnings.warn( - "Importing from `mkdocstrings.plugin` is deprecated. Import from `mkdocstrings` directly.", - DeprecationWarning, - stacklevel=2, - ) - return getattr(plugin, name) diff --git a/tests/test_api.py b/tests/test_api.py index ea672073..9821e65c 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -152,8 +152,6 @@ def test_inventory_matches_api( ) -> None: """The inventory doesn't contain any additional Python object.""" not_in_api = [] - # YORE: Bump 1: Remove line. - deprecated_modules = {"extension", "handlers", "inventory", "loggers", "plugin"} public_api_paths = {obj.path for obj in public_objects} public_api_paths.add("mkdocstrings") for item in inventory.values(): @@ -163,9 +161,6 @@ def test_inventory_matches_api( and (item.name == "mkdocstrings" or item.name.startswith("mkdocstrings.")) ): obj = loader.modules_collection[item.name] - # YORE: Bump 1: Remove block. - if any(obj.path.startswith(f"mkdocstrings.{module}") for module in deprecated_modules): - continue if obj.path not in public_api_paths and not any(path in public_api_paths for path in obj.aliases): not_in_api.append(item.name) From 68760a9ec55772c8b330b056c2d0896877324b1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Thu, 27 Nov 2025 16:39:22 +0100 Subject: [PATCH 12/12] chore: Prepare release 1.0.0 --- CHANGELOG.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6346ccd3..9def4ba8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,37 @@ 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.0.0](https://github.com/mkdocstrings/mkdocstrings/releases/tag/1.0.0) - 2025-11-27 + +[Compare with 0.30.1](https://github.com/mkdocstrings/mkdocstrings/compare/0.30.1...1.0.0) + +### Breaking Changes + +- `BaseHandler.name`: *Attribute value was changed*: `''` -> unset +- `BaseHandler.domain`: *Attribute value was changed*: `''` -> unset +- `BaseHandler.fallback_config`: *Public object was removed* +- `BaseHandler.__init__(args)`: *Parameter was removed* +- `BaseHandler.__init__(kwargs)`: *Parameter was removed* +- `BaseHandler.__init__(theme)`: *Parameter was added as required* +- `BaseHandler.__init__(custom_templates)`: *Parameter was added as required* +- `BaseHandler.__init__(mdx)`: *Parameter was added as required* +- `BaseHandler.__init__(mdx_config)`: *Parameter was added as required* +- `BaseHandler.update_env(args)`: *Parameter was removed* +- `BaseHandler.update_env(kwargs)`: *Parameter was removed* +- `BaseHandler.update_env(config)`: *Parameter was added as required* +- `Handlers.get_anchors`: *Public object was removed* (import from `mkdocstrings` directly) +- `mkdocstrings.plugin`: *Public module was removed* (import from `mkdocstrings` directly) +- `mkdocstrings.loggers`: *Public module was removed* (import from `mkdocstrings` directly) +- `mkdocstrings.inventory`: *Public module was removed* (import from `mkdocstrings` directly) +- `mkdocstrings.extension`: *Public module was removed* (import from `mkdocstrings` directly) +- `mkdocstrings.handlers`: *Public module was removed* (import from `mkdocstrings` directly) + +### Code Refactoring + +- Remove deprecated code before v1 ([de34044](https://github.com/mkdocstrings/mkdocstrings/commit/de34044a02b45250e215af0f969dca581dfb82c5) by Timothée Mazzucotelli). +- Expect Zensical to pass extension configuration instead of loading it again from YAML ([6b73d5a](https://github.com/mkdocstrings/mkdocstrings/commit/6b73d5a2f455062ab6c68376c85adce6adc037a3) by Timothée Mazzucotelli). +- Expose the Markdown extension, to make mkdocstrings compatible with Zensical ([6de2667](https://github.com/mkdocstrings/mkdocstrings/commit/6de266759b79eb72cddd300e6a0a8576085fae40) by Timothée Mazzucotelli). + ## [0.30.1](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.30.1) - 2025-09-19 [Compare with 0.30.0](https://github.com/mkdocstrings/mkdocstrings/compare/0.30.0...0.30.1)