From 7c82c3ddee9b9fd7954991fdf89b55be2fde5b95 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Fri, 7 Feb 2025 17:06:41 +0100 Subject: [PATCH 1/4] Rebrand the project (forking) --- .editorconfig | 19 + .github/ISSUE_TEMPLATE/bug_report.yml | 35 + .github/ISSUE_TEMPLATE/feature_request.yml | 34 + .github/workflows/main.yml | 54 +- .github/workflows/mkdocs.yml | 71 ++ .github/workflows/unit-tests.yml | 54 + .github/workflows/validation.yml | 61 + .gitignore | 72 +- .idea/.gitignore | 4 - .idea/garpy.mkdocstrings.iml | 13 - .idea/icon.svg | 1 - .idea/inspectionProfiles/Project_Default.xml | 18 - .idea/modules.xml | 8 - .idea/vcs.xml | 6 - .pre-commit-config.yaml | 57 + CHANGELOG.md | 27 +- CONTRIBUTING.md | 34 - LICENSE.md => LICENSE.txt | 0 MANIFEST.in | 5 - Makefile | 244 ---- NOTICE.md | 20 + README.md | 61 +- docs/LICENSE.md | 54 + docs/changelog.md | 1 - docs/config.md | 63 +- docs/contributing/bugs-and-feature-reqs.md | 185 +++ docs/contributing/making-a-pr.md | 5 + docs/index.md | 152 +-- docs/install.md | 155 ++- docs/license.md | 1 - docs/meta/changelog.md | 7 + docs/meta/fork.md | 48 + docs/meta/license.md | 85 ++ docs/meta/support.md | 10 + docs/meta/versioning.md | 55 + docs/support.md | 4 - docs/usage.md | 159 +++ environment.yml | 26 - github-condarc.yml | 7 - mkdocs.yml | 181 +-- pyproject.toml | 322 +++--- .../python_betterrefs/__init__.py | 48 + .../python_betterrefs/config.py | 58 + .../crossref.py | 74 +- .../python_betterrefs/handler.py | 85 ++ .../py.typed | 0 src/mkdocstrings_handlers/python_xref/VERSION | 1 - .../python_xref/__init__.py | 22 - .../python_xref/handler.py | 95 -- tests/__init__.py | 0 tests/conftest.py | 9 + tests/project/README.md | 4 + tests/project/mkdocs.yml | 73 +- tests/project/src/myproj/__init__.py | 13 - tests/project/src/myproj/bar.py | 21 +- tests/project/src/myproj/foo.py | 22 +- tests/project/src/myproj/pkg/__init__.py | 22 +- tests/project/src/myproj/pkg/baz.py | 17 +- tests/test_crossref.py | 78 +- tests/test_handler.py | 408 +++++-- tests/test_integration.py | 172 +-- uv.lock | 1024 +++++++++++++++++ 62 files changed, 3281 insertions(+), 1383 deletions(-) create mode 100644 .editorconfig create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/workflows/mkdocs.yml create mode 100644 .github/workflows/unit-tests.yml create mode 100644 .github/workflows/validation.yml delete mode 100644 .idea/.gitignore delete mode 100644 .idea/garpy.mkdocstrings.iml delete mode 120000 .idea/icon.svg delete mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml create mode 100644 .pre-commit-config.yaml delete mode 100644 CONTRIBUTING.md rename LICENSE.md => LICENSE.txt (100%) delete mode 100644 MANIFEST.in delete mode 100644 Makefile create mode 100644 NOTICE.md create mode 100644 docs/LICENSE.md delete mode 100644 docs/changelog.md create mode 100644 docs/contributing/bugs-and-feature-reqs.md create mode 100644 docs/contributing/making-a-pr.md delete mode 100644 docs/license.md create mode 100644 docs/meta/changelog.md create mode 100644 docs/meta/fork.md create mode 100644 docs/meta/license.md create mode 100644 docs/meta/support.md create mode 100644 docs/meta/versioning.md delete mode 100644 docs/support.md create mode 100644 docs/usage.md delete mode 100644 environment.yml delete mode 100644 github-condarc.yml create mode 100644 src/mkdocstrings_handlers/python_betterrefs/__init__.py create mode 100644 src/mkdocstrings_handlers/python_betterrefs/config.py rename src/mkdocstrings_handlers/{python_xref => python_betterrefs}/crossref.py (84%) create mode 100644 src/mkdocstrings_handlers/python_betterrefs/handler.py rename src/mkdocstrings_handlers/{python_xref => python_betterrefs}/py.typed (100%) delete mode 100644 src/mkdocstrings_handlers/python_xref/VERSION delete mode 100644 src/mkdocstrings_handlers/python_xref/__init__.py delete mode 100644 src/mkdocstrings_handlers/python_xref/handler.py create mode 100644 tests/__init__.py create mode 100644 tests/conftest.py create mode 100644 tests/project/README.md create mode 100644 uv.lock diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..448b653 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +# Check http://editorconfig.org for more information +# This is the main config file for this project: +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +[*.{py, pyi}] +indent_size = 4 + +[*.md] +indent_size = 4 +trim_trailing_whitespace = false +max_line_length = 120 diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..1f5ed02 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,35 @@ +name: Bug report +description: Found a bug? Let us know so we can fix it! +labels: ["bug"] + +body: + - type: textarea + id: bug-description + attributes: + label: Bug description + description: Describe the bug. What's wrong? + validations: + required: true + + - type: textarea + id: reproduction-steps + attributes: + label: Reproduction + description: Steps to reproduce the bug. You can also include code snippets. Try to keep things as minimal as you can. + + - type: textarea + id: further-info + attributes: + label: Further info + description: Any further info such as images, exception tracebacks, ... + + - type: checkboxes + id: checklist + attributes: + label: Checklist + description: Make sure to tick all the following boxes. + options: + - label: I have searched the issue tracker and have made sure it's not a duplicate. If it is a follow up of another issue, I have specified it. + required: true + - label: I have made sure to remove ANY sensitive information (passwords, credentials, personal details, etc.). + required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..4fec4bc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,34 @@ +name: Feature request +description: Got a cool idea you would like implemented? Let us know! +labels: ["feature"] + +body: + - type: textarea + id: summary + attributes: + label: Summary + description: Quick summary of the feature. + validations: + required: true + + - type: textarea + id: problem + attributes: + label: Why is this needed? + description: Why should this feature be implemented? What problem(s) would it solve? + + - type: textarea + id: ideal-implementation + attributes: + label: Ideal implementation + description: How should this feature be implemented? + value: To be decided. + + - type: checkboxes + id: checklist + attributes: + label: Checklist + description: Make sure to tick all the following boxes. + options: + - label: I have searched the issue tracker and have made sure it's not a duplicate. If it is a follow up of another issue, I have specified it. + required: true diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index aeda8d6..1ff942a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,39 +1,21 @@ +--- name: CI -on: [push] + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +# Cancel already running workflows if new ones are scheduled +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: - build-os-python: - runs-on: ubuntu-latest - strategy: - max-parallel: 5 - fail-fast: true - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.8", "3.10", "3.12"] - steps: - - uses: actions/checkout@v3 - - uses: goanpeca/setup-miniconda@v2.2.0 - with: - miniforge-version: latest - conda-version: ">=23.7.4" - conda-build-version: ">=3.26" - environment-file: environment.yml - activate-environment: mkxref-dev - python-version: ${{ matrix.python-version }} - condarc-file: github-condarc.yml - auto-activate-base: true - use-mamba: true - - name: Dev install package - run: | - conda run -n mkxref-dev pip install -e . --no-deps --no-build-isolation - - name: ruff - run: | - make ruff - - name: mypy - if: success() || failure() - run: | - make mypy - - name: Test with pytest - if: success() || failure() - run: | - make coverage-test + validation: + uses: ./.github/workflows/validation.yml + + unit-tests: + uses: ./.github/workflows/unit-tests.yml diff --git a/.github/workflows/mkdocs.yml b/.github/workflows/mkdocs.yml new file mode 100644 index 0000000..6d20bbc --- /dev/null +++ b/.github/workflows/mkdocs.yml @@ -0,0 +1,71 @@ +--- +name: MkDocs + +on: + push: + branches: + - main + pull_request: + types: # Add closed type + - opened + - reopened + - synchronize + - closed # to delete the PR preview + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: write + pull-requests: write + +env: + PYTHON_VERSION: "3.14" + +jobs: + deploy-docs: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + token: "${{ github.token }}" + # Fetch the entire git history (all branches + tags) + # We do this because the docs use git describe, which requires having all + # the commits up to the latest version tag. + # We also need the gh-pages branch to push the docs to. + fetch-depth: 0 + + - name: Setup uv + uses: astral-sh/setup-uv@v5 + with: + version: "latest" + python-version: ${{ env.PYTHON_VERSION }} + enable-cache: true + cache-suffix: "docs-py${{ env.python-version }}" + + - name: Install dependencies + run: | + uv sync --no-default-groups --group docs + + - name: Build the documentation (mkdocs - PR preview) + if: ${{ github.event_name == 'pull_request' }} + run: mkdocs build + + - name: Deploy docs - PR preview + if: ${{ github.event_name == 'pull_request' }} + uses: rossjrw/pr-preview-action@v1 + with: + source-dir: ./site + preview-branch: gh-pages + umbrella-dir: pr-preview + token: ${{ github.token }} + + - name: Build the documentation (mike) + if: ${{ github.event_name == 'push' }} + run: mike deploy latest + + - name: Deploy docs - latest + if: ${{ github.event_name == 'push' }} + run: git push origin gh-pages diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 0000000..0019ee2 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,54 @@ +--- +name: Unit-Tests + +on: workflow_call + +jobs: + unit-tests: + strategy: + fail-fast: false # Allows for matrix sub-jobs to fail without cancelling the rest + matrix: + platform: [ubuntu-latest, windows-latest] + python-version: ["3.9", "3.14"] + + runs-on: ${{ matrix.platform }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup uv + uses: astral-sh/setup-uv@v5 + with: + version: "latest" + python-version: ${{ matrix.python-version }} + enable-cache: true + cache-suffix: "test-py${{ matrix.python-version }}" + + - name: Install dependencies + run: | + uv sync --no-default-groups --group test + + - name: Run pytest + shell: bash + run: pytest -vv + + # This job is used purely to provide a workflow status, which we can mark as a + # required action in branch protection rules. This is a better option than marking + # the unit-tests jobs manually, since their names change as the supported python + # versions change. This job provides an easy single action that can be marked required. + tests-done: + needs: [unit-tests] + if: always() && !cancelled() + runs-on: ubuntu-latest + + steps: + - name: Set status based on required jobs + env: + RESULTS: ${{ join(needs.*.result, ' ') }} + run: | + for result in $RESULTS; do + if [ "$result" != "success" ]; then + exit 1 + fi + done diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml new file mode 100644 index 0000000..47afc47 --- /dev/null +++ b/.github/workflows/validation.yml @@ -0,0 +1,61 @@ +name: Validation + +on: workflow_call + +env: + PYTHON_VERSION: "3.14" + PRE_COMMIT_HOME: "/home/runner/.cache/pre-commit" + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup uv + uses: astral-sh/setup-uv@v5 + with: + version: "latest" + python-version: ${{ env.PYTHON_VERSION }} + enable-cache: true + cache-suffix: "validation-py${{ env.python-version }}" + + - name: Install dependencies + run: | + # We need the test group here to allow pyright to type-check code in tests + uv sync --no-default-groups --group lint --group test + + - name: Get precommit version + id: precommit_version + run: | + PACKAGE_VERSION=$(pip show pre-commit | grep -i "version:" | awk '{print $2}') + echo "version=$PACKAGE_VERSION" >> $GITHUB_ENV + + - name: Pre-commit Environment Caching + uses: actions/cache@v4 + with: + path: ${{ env.PRE_COMMIT_HOME }} + key: + "precommit-${{ runner.os }}-${{ env.PYTHON_VERSION }}-${{ steps.precommit_version.outputs.version }}-\ + ${{ hashFiles('./.pre-commit-config.yaml') }}" + # Restore keys allows us to perform a cache restore even if the full cache key wasn't matched. + # That way we still end up saving new cache, but we can still make use of the cache from previous + # version. + restore-keys: "precommit-${{ runner.os }}-${{ steps.poetry_setup.outputs-python-version}}-" + + - name: Run pre-commit hooks + run: SKIP=ruff-linter,ruff-formatter,slotscheck,basedpyright pre-commit run --all-files + + - name: Run ruff linter + run: ruff check --output-format=github --show-fixes --exit-non-zero-on-fix . + + - name: Run ruff formatter + run: ruff format --diff . + + - name: Run basedpyright type checker + run: basedpyright --warnings . + + - name: Check UV Lockfile + run: uv lock --check diff --git a/.gitignore b/.gitignore index 7b59b3a..d5a2037 100644 --- a/.gitignore +++ b/.gitignore @@ -1,39 +1,45 @@ -# MacOS -.DS_Store +# Python byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class +.pytest_cache/ +.mypy_cache/ + +# Virtual environments +.venv/ +.tox/ +venv/ +env/ +ENV/ + +# Python packaging files +dist/ -# Make -custom.mk +# Pytest coverage reports +htmlcov/ +.coverage* +coverage.xml -# Python -__pycache__ -*.pyc +# Mkdocs documentation +site/ -# Generated files -*.egg-info -build/ -dist/ -# ignore .eggs directory created by setup.py -.eggs/ -.conda-built -.coverage -/htmlcov/ -/site/ -/pages-tmp/ -/public/ -/tests/.aws/ -conda-meta-data.json -# stray build artifacts -*.whl -*.tar.bz2 -*.conda - -# tox -/.tox/ -/tox-env.yml -/tox-requirements.txt - -# git -*.orig +# Pyenv local version information +.python-version + +# Editor generated files +.idea/ +.vscode/ +.spyproject/ +.spyderproject/ +.replit +# Auto-generated folder attributes for MacOS +.DS_STORE +# Local gitignore (symlinked to .git/info/exclude) +.gitignore_local +# Environmental, backup and personal files +*.env +*.bak +TODO diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 787c8e3..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -*.xml - -!modules.xml -!vcs.xml diff --git a/.idea/garpy.mkdocstrings.iml b/.idea/garpy.mkdocstrings.iml deleted file mode 100644 index 288b948..0000000 --- a/.idea/garpy.mkdocstrings.iml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/icon.svg b/.idea/icon.svg deleted file mode 120000 index 61f0c4e..0000000 --- a/.idea/icon.svg +++ /dev/null @@ -1 +0,0 @@ -../docs/logo.svg \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index e2a5a6f..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 9b275bc..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..6e9a5bc --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,57 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: check-merge-conflict + - id: check-toml # For pyproject.toml + - id: check-yaml # For workflows + # Only parse the files for syntax, don't do full load. + # We need this because of mkdocs.yml, which uses some custom tags to perform dynamic imports from python. + args: ["--unsafe"] + - id: end-of-file-fixer + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + - id: mixed-line-ending + args: [--fix=lf] + - id: sort-simple-yaml + + - repo: local + hooks: + - id: ruff-linter + name: Ruff Linter + description: Run ruff checks on the code + entry: ruff check --force-exclude + language: system + types: [python] + require_serial: true + args: [--fix, --exit-non-zero-on-fix] + + - repo: local + hooks: + - id: ruff-formatter + name: Ruff Formatter + description: Ruf ruff auto-formatter + entry: ruff format + language: system + types: [python] + require_serial: true + + - repo: local + hooks: + - id: basedpyright + name: Based Pyright + description: Run basedpyright type checker + entry: basedpyright --warnings + language: system + types: [python] + pass_filenames: false # pyright runs for the entire project, it can't run for single files + + - repo: local + hooks: + - id: uv-lockfile + name: UV Lockfile + description: Check if the UV lockfile is up to date with pyproject.toml + entry: uv lock --check + language: system + files: '^pyproject\.toml$|^uv\.lock$' + pass_filenames: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 3591c88..6584d34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,20 +1,17 @@ -# mkdocstring-python-xref changes +## [UNRELEASED] Version 0.1.0 (2025-02-09) -## 1.6.2 +This is the initial release following a **rewrite of the project (fork)**. -* Use griffe 1.0 or later +### Breaking changes -## 1.6.1 +- Handler name was renamed to `python_betterrefs` (from `python_xrefs`) +- Config option `relative_crossrefs` was renamed to `better_crossrefs` +- Config option `better_crossrefs` (previously `relative_crossrefs`) is now enabled by default -* Available on conda-forge - -## 1.6.0 - -* Added explicit option to disable cross-reference checking. -* When enabled, check all cross-references, not just relative ones -* If reference begins with '?', don't check cross-reference. - -## 1.5.3 - -First public release +### Other changes +- Rewrite the project documentation +- Move to `basedpyright` type-checker (from mypy) +- Move to `uv` package manager (from condadev) +- Improve CI workflows +- Improve testing diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 5c9afba..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,34 +0,0 @@ -# Contributing to mkdocstrings-python-xref - -## Prerequisites - -* conda must be installed on your machine -* make should be installed in your base conda environment - -## Development install - -To (re)create a conda development environment for this project run: - -``` -make createdev -conda activate mkxref-dev -``` - -After you have created the environment for the first time, you can configure your IDE -to use that for this project. - -To update the environment after pulling or modifying project dependencies, you can use - -``` -make updatedev -``` - -This is just an optimization. If it does not work (e.g. can happen when switching to an old branch), just use `createdev`. - -## Versioning - -The versions will generally track the version of [mkdocstrings_python][] on which it depends. - -[mkdocstrings_python]: https://github.com/mkdocstrings/python - - diff --git a/LICENSE.md b/LICENSE.txt similarity index 100% rename from LICENSE.md rename to LICENSE.txt diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 56ce4fc..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,5 +0,0 @@ -# Including requirements via manifest.in allows: -# - tox to find the same requirements file. -# TODO - removes this if TOX support is dropped or other way -# to tell tox to include this -include runtime-env.yml diff --git a/Makefile b/Makefile deleted file mode 100644 index be20f2c..0000000 --- a/Makefile +++ /dev/null @@ -1,244 +0,0 @@ -CONDA := conda -ECHO := echo -RM := rm -RMDIR := $(RM) -rf -CAT := cat -TOUCH := touch - -# OS specific values -ifndef OS - # maxOs or linux - USERNAME := $(shell whoami) -else - # windows -endif - -COVERAGE_HTML := $(abspath htmlcov) - -# If you want to override default variables in your own source tree, -# define a custom.mk file, for instance to use the name 'garconf-dev' for -# the conda development environment, you could use the entry: -# -# override DEV_ENV := garconf-dev --include custom.mk - -# General env/build/deploy args -PACKAGE := mkdocstrings-python-xref -PACKAGE_VERSION_PATH := src/mkdocstrings_handlers/python_xref/VERSION -VERSION := $(strip $(file < $(PACKAGE_VERSION_PATH))) - -SRC_FILES := $(wildcard src/mkdocstrings_handlers/python_xref/*.py) $(PYTHON_VERSION_PATH) - -# Env names -DEV_ENV := mkxref-dev - -# Whether to run targets in current env or explicitly in $(DEV_ENV) -CURR_ENV_BASENAME := $(shell basename $(CONDA_PREFIX)) -ifeq ($(CURR_ENV_BASENAME), $(DEV_ENV)) - CONDA_RUN := -else - CONDA_RUN := conda run -n $(DEV_ENV) --no-capture-output -endif - -# Testing args -PYTEST_ARGS := -TOX_ARGS := -PYLINT_ARGS := -MYPY_ARGS := -RUFF_ARGS := - -DOC_DIR := docs -MKDOC_CONFIG := mkdocs.yml -MKDOC_FILES := $(MKDOC_CONFIG) $(wildcard $(DOC_DIR)/user/*.md) - -# 'make help' colors -TOP_COLOR :=\033[0;34m -SECTION_COLOR :=\033[0;32m -COLORLESS :=\033[0m - -help: - @$(ECHO) - @$(ECHO) "$(TOP_COLOR)=== Make targets ===$(COLORLESS)" - @$(ECHO) "$(SECTION_COLOR)--- conda environment ---$(COLORLESS)" - @$(ECHO) "create-dev - Create conda development environment named '$(DEV_ENV)'." - @$(ECHO) "update-dev - Update conda development environment '$(DEV_ENV)'." - @$(ECHO) "clean-dev - Remove $(DEV_ENV) conda environment" - @$(ECHO) - @$(ECHO) "$(SECTION_COLOR)--- test ---$(COLORLESS)" - @$(ECHO) "pytest - Run pytest in '$(DEV_ENV)' environment." - @$(ECHO) "test - Run tests and linting in '$(DEV_ENV)' environment." - @$(ECHO) "coverage-test - Runs pytest instrumented for coverage and generates html report" - @$(ECHO) "coverage-show - Open html coverage report in a web browser." - @$(ECHO) - @$(ECHO) "$(SECTION_COLOR)--- lint ---$(COLORLESS)" - @$(ECHO) "lint - Run linting commands in '$(DEV_ENV)' environment." - @$(ECHO) "ruff - Run ruff in '$(DEV_ENV)' environment." - @$(ECHO) "mypy - Run mypy in '$(DEV_ENV)' environment." - @$(ECHO) - @$(ECHO) "$(SECTION_COLOR)--- build ---$(COLORLESS)" - @$(ECHO) "build - Build wheel" - @$(ECHO) "build-wheel - Build wheel." - @$(ECHO) "build-conda - Build conda package (requires whl2conda)" - @$(ECHO) - @$(ECHO) "$(SECTION_COLOR)--- upload ---$(COLORLESS)" - @$(ECHO) "upload - Upload wheel to pypi (requires authorization)" - @$(ECHO) "check-upload - Verify file for upload" - @$(ECHO) - @$(ECHO) "$(SECTION_COLOR)--- documentation ---$(COLORLESS)" - @$(ECHO) "doc - Build HTML documentation in site/ directory." - @$(ECHO) "doc-strict - Build documentation but fail if any warnings" - @$(ECHO) "showdoc - Show HTML documentation using local HTML server." - @$(ECHO) - @$(ECHO) "$(SECTION_COLOR)--- cleanup ---$(COLORLESS)" - @$(ECHO) "clean - Remove generated files." - @$(ECHO) "clean-test - Remove generated test files including tox environments." - @$(ECHO) "clean-doc - Remove generated documentation files." - @$(ECHO) - @$(ECHO) "$(TOP_COLOR)====================$(COLORLESS)" - @$(ECHO) - -dev-install: - $(CONDA_RUN) pip install -e . --no-deps --no-build-isolation - -create-dev: - $(CONDA) env create -f environment.yml --yes - $(MAKE) dev-install - -createdev: create-dev - -update-dev: - $(CONDA) env update -f environment.yml - $(MAKE) dev-install - -updatedev: update-dev - -create-ci-env: create-dev - -clean-dev: - -$(CONDA) env remove -n $(DEV_ENV) - -pytest: - $(CONDA_RUN) pytest -sv -ra $(PYTEST_ARGS) tests - -test: lint pytest - -test-all: test tox - -.coverage: - @$(CONDA_RUN) pytest -ra $(PYTEST_ARGS) --cov --cov-report=html --cov-report=term -- tests - -coverage-test: - @$(MAKE) -B .coverage - -coverage: coverage-test - -coverage-show: - @$(CONDA_RUN) python -m webbrowser file://$(COVERAGE_HTML)/index.html - -pylint: - $(CONDA_RUN) pylint src/mkdocstrings_handlers tests $(PYLINT_ARGS) - -ruff: - $(CONDA_RUN) ruff check src/mkdocstrings_handlers tests $(RUFF_ARGS) - -mypy: - $(CONDA_RUN) mypy $(MYPY_ARGS) - -lint: ruff mypy - -WHEEL_FILE := dist/$(subst -,_,$(PACKAGE))-$(VERSION)-py3-none-any.whl -CONDA_FILE := dist/$(PACKAGE)-$(VERSION)-py_0.conda - -build-sdist: - $(CONDA_RUN) python -m build --sdist --no-isolation --outdir dist - -$(WHEEL_FILE): - $(CONDA_RUN) pip wheel . --no-deps --no-build-isolation -w dist - -build-wheel: $(WHEEL_FILE) - -$(CONDA_FILE): $(WHEEL_FILE) - $(CONDA_RUN) whl2conda convert $(WHEEL_FILE) - -build-conda: $(CONDA_FILE) - -build: build-wheel build-sdist build-conda - -site/index.html: $(MKDOC_FILES) $(SRC_FILES) - $(CONDA_RUN) mkdocs build -f $(MKDOC_CONFIG) - -site/.doc-strict: $(MKDOC_FILES) $(SRC_FILES) - $(CONDA_RUN) mkdocs build -f $(MKDOC_CONFIG) --strict - $(CONDA_RUN) linkchecker -f linkcheckerrc.ini site - $(TOUCH) site/.doc-strict - -doc: site/index.html - -docs: doc - -doc-strict: site/.doc-strict - -showdoc: site/index.html - $(CONDA_RUN) mkdocs serve -f $(MKDOC_CONFIG) - -showdocs: showdoc - -doc-deploy: - $(CONDA_RUN) mike deploy -F $(MKDOC_CONFIG) -u $(VERSION) latest - $(CONDA_RUN) mike set-default -F $(MKDOC_CONFIG) latest - -mike-deploy: doc-deploy -mike-build: doc-deploy - -doc-push: - git push origin gh-pages - -doc-upload: doc-push - -doc-serve-all: - $(CONDA_RUN) mike serve -F $(MKDOC_CONFIG) - -mike-serve: doc-serve-all - -check-upload-wheel: - $(CONDA_RUN) twine check dist/*.whl - -check-upload-sdist: - $(CONDA_RUN) twine check dist/*.tar.gz - -check-upload: check-upload-sdist check-upload-wheel - -upload-wheel: check-upload-wheel - # NOTE: --skip-existing doesn't seem to actually work - $(CONDA_RUN) twine upload --skip-existing $(lastword $(sort $(wildcard dist/*.whl))) - - -upload-sdist: check-upload-sdist - # NOTE: --skip-existing doesn't seem to actually work - $(CONDA_RUN) twine upload --skip-existing $(lastword $(sort $(wildcard dist/*.tar.gz))) - -upload: upload-sdist upload-wheel - -clean-build: - -@$(RMDIR) build - -@$(RMDIR) dist - -clean-doc: - -@$(RMDIR) site - -clean-tox: - -@$(RMDIR) .tox - -@$(RM) tox-env.yml tox-requirements.txt - -clean-test: clean-coverage clean-tox - -@$(RMDIR) .pytest_cache - -@$(RMDIR) .mypy_cache - -clean-coverage: - -@$(RMDIR) $(COVERAGE_HTML) .coverage .coverage.* - -clean-python: - -@python -Bc "import pathlib; [p.unlink() for p in pathlib.Path('.').rglob('*.py[co]')]" - -@python -Bc "import pathlib; [p.rmdir() for p in pathlib.Path('.').rglob('__pycache__')]" - -clean: clean-doc clean-python clean-test clean-build diff --git a/NOTICE.md b/NOTICE.md new file mode 100644 index 0000000..8387ee8 --- /dev/null +++ b/NOTICE.md @@ -0,0 +1,20 @@ +This project forks off of the original project: +[mkdocstrings-python-xref](https://github.com/analog-garage/mkdocstrings-python-xref). This project was created by +Christopher Barber and copyrighted by Analog Devices, Inc. (2022-2023) + +The original project was licensed under the Apache 2.0 License, which this fork also uses. According to the terms of +the Apache 2.0 License, significant modifications made to the project are required to be listed here. Below are the key +changes introduced in this fork: + +- **Documentation:** The documentation has been extensively modified and relicensed under a Creative Commons license + (see the `LICENSE.txt` file in the `docs/` directory for more information). The usage page retains some original + content but has been reorganized for better readability. +- **User Experience:** This fork aims to make migration from the original project as seamless as possible. While the + user interface remains largely the same, the handler has been renamed from mkdocstrings-python-xref to + mkdocstrings-python-betterrefs, some config options were also renamed. +- **Source Code:** Various changes have been made to the source code, with more ongoing. For a detailed list of changes, + please refer to the project's `CHANGELOG.md` file. + +The reason for this fork was mainly personal curiosity and the desire to heavily modify the packaging practices and the +code base, but also to address some issues which the original maintainer took a while to get to. This project is +essentially a direct continuation of the original project, bringing it up-to-date with latest mkdocs-python. diff --git a/README.md b/README.md index 9b89e93..dbf8f18 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,36 @@ -# mkdocstrings-python-xref +# mkdocstrings-python-betterrefs -Python handler for [mkdocstrings] supporting relative cross-references. +![Supported python versions](https://img.shields.io/pypi/pyversions/mkdocstrings-python-betterrefs.svg) +[![Current PyPI version](https://img.shields.io/pypi/v/mkdocstrings-python-betterrefs.svg)](https://pypi.org/project/mkdocstrings-python-betterrefs/) -[![pypi version](https://img.shields.io/pypi/v/mkdocstrings-python-xref.svg)](https://pypi.org/project/mkdocstrings-python-xref/) -[![conda version](https://img.shields.io/conda/vn/conda-forge/mkdocstrings-python-xref)](https://anaconda.org/conda-forge/whl2conda) -[![documentation](https://img.shields.io/badge/docs-mkdocs%20material-blue.svg?style=flat)](https://analog-garage.github.io/mkdocstrings-python-xref/) -![PyPI - Python Version](https://img.shields.io/pypi/pyversions/mkdocstrings-python-xref) -![GitHub](https://img.shields.io/github/license/analog-garage/mkdocstrings-python-xref) -[![CI](https://github.com/analog-garage/mkdocstrings-python-xref/actions/workflows/main.yml/badge.svg)](https://github.com/analog-garage/mkdocstrings-python-xref/actions/workflows/main.yml) -![GitHub issues](https://img.shields.io/github/issues/analog-garage/mkdocstrings-python-xref) +[![Validation](https://github.com/ItsDrike/mkdocstrings-python-betterrefs/actions/workflows/validation.yml/badge.svg)](https://github.com/ItsDrike/mkdocstrings-python-betterrefs/actions/workflows/validation.yml) +[![Unit tests](https://github.com/ItsDrike/mkdocstrings-python-betterrefs/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/ItsDrike/mkdocstrings-python-betterrefs/actions/workflows/unit-tests.yml) -[mkdocstrings] is an awesome plugin for [MkDocs] that can generate Markdown API documentation -from comments in code. The standard [python handler][mkdocstrings-python] allows you to -create cross-reference links using the syntax `[][<path>]` where the path must -either be the fully qualified name of the referent or is empty, in which case the path -is taken from the title. This works well when the names are short, but can be burdensome -in larger codebases with deeply nested package structures. +![License](https://img.shields.io/github/license/ItsDrike/mkdocstrings-python-betterrefs) -This package extends [mkdocstrings-python] to support a relative cross-reference syntax, -that allows you to write doc-strings with cross-references like: +[![Docs](https://github.com/ItsDrike/mkdocstrings-python-betterrefs/actions/workflows/mkdocs.yml/badge.svg)](https://itsdrike.github.io/mkdocstrings-python-betterrefs) + +Python handler for [mkdocstrings] with improved handling for cross-references, including relative ones. + +[mkdocstrings] is an awesome plugin for [MkDocs] that can generate Markdown API documentation from comments in code. The +standard [python handler][mkdocstrings-python] allows you to create cross-reference links using the syntax +`[<title>][<path>]` where the path must either be the fully qualified name of the referent or is empty, in which case +the path is taken from the title. + +[mkdocstrings-python] does already have support for cross-references, however, it is currently only available in the +insiders edition, which is limited to their sponsors. Additionally, this implementation is fairly limited in comparison +to what this project offers. + +> [!TIP] +> For more information on the [mkdocstrings-python] official support of relative cross-references, check out the feature +> request proposing them: [here][official-xrefs-issue], and the docs detailing the configuration option: +> [here][official-xrefs-docs]. +> +> It is expected that relative cross-references will make it into the open-source version once a funding goal of $2,000 +> is reached. You can see the current progress towards this goal [here][official-xrefs-funding-goal]. + +This package extends [mkdocstrings-python] to support an improved cross-reference syntax, that allows you to write +doc-strings with relative cross-references like: ```python class MyClass: @@ -27,19 +39,23 @@ class MyClass: See [other_method][..] from [MyClass][(c)] """ ``` + rather than: ```python class MyClass: def this_method(self): """ - See [other_method][mypkg.mymod.MyClass.other_method] + See [other_method][mypkg.mymod.MyClass.other_method] from [MyClass][mypkg.mymod.Myclass] """ ``` -Another benefit of this extension is that it will report source locations for bad references -so that errors are easier to find and fix. For example: +Relative references are especially useful for larger codebases with deeply nested package structure, where writing out +the absolute paths each time gets very burdensome. + +Another benefit of this extension is that it will report source locations for bad references so that errors are easier +to find and fix. For example: ```bash $ mkdocs build @@ -49,8 +65,11 @@ WARNING - mkdocstrings_handlers: file:///home/jdoe/my-project/src/myproj/bar.py Cannot load reference 'myproj.bar.bad' ``` -For further details, please see the [Documentation](https://analog-garage.github.io/mkdocstrings-python-xref/) +For further details, please see the [Documentation](https://itsdrike.github.io/mkdocstrings-python-betterrefs) [MkDocs]: https://mkdocs.readthedocs.io/ [mkdocstrings]: https://github.com/mkdocstrings/mkdocstrings [mkdocstrings-python]: https://github.com/mkdocstrings/python +[official-xrefs-issue]: https://github.com/mkdocstrings/python/issues/27 +[official-xrefs-docs]: https://mkdocstrings.github.io/python/usage/configuration/docstrings/?h=relative#relative_crossrefs +[official-xrefs-funding-goal]: https://mkdocstrings.github.io/python/insiders/#funding diff --git a/docs/LICENSE.md b/docs/LICENSE.md new file mode 100644 index 0000000..4269472 --- /dev/null +++ b/docs/LICENSE.md @@ -0,0 +1,54 @@ +# Documentation License + +This documentation itself does NOT follow the primary project license! + +Instead, it follows a Creative Commons license: <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a> <img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1" alt=""><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1" alt=""><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/nc.svg?ref=chooser-v1" alt=""><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/sa.svg?ref=chooser-v1" alt=""> + +## Attribution + +If you need a copyright header for proper attribution, you can use: + +<a href="https://itsdrike.github.io/mkdocstrings-python-betterrefs">mkdocstrings-python-betterrefs documentation</a> © 2025 by <a href="mailto:itsdrike@protonmail.com">ItsDrike</a> + +In HTML: + +```html +<a href="https://itsdrike.github.io/mkdocstrings-python-betterrefs">mkdocstrings-python-betterrefs documentation</a> +© 2025 by <a href="mailto:itsdrike@protonmail.com">ItsDrike</a> +``` + +If you also need the license identifier, use the following: + +```html +<a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a> +<img + style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" + src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1" + alt="" +/><img + style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" + src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1" + alt="" +/><img + style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" + src="https://mirrors.creativecommons.org/presskit/icons/nc.svg?ref=chooser-v1" + alt="" +/><img + style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" + src="https://mirrors.creativecommons.org/presskit/icons/sa.svg?ref=chooser-v1" + alt="" +/> +``` + +## Notice + +This documentation is a modified version of the documentation from the original project ([mkdocstrings-python-xref]). +The original documentation was not licensed separately from the project, which means it was following the Apache 2.0 +license. + +This documentation has been heavily modified in various ways, however, the usage page remains fairly similar to the +original. + +See the `NOTICE.txt` file in the root directory of this project for further details. + +[mkdocstrings-python-xref]: https://github.com/analog-garage/mkdocstrings-python-xref diff --git a/docs/changelog.md b/docs/changelog.md deleted file mode 100644 index 786b75d..0000000 --- a/docs/changelog.md +++ /dev/null @@ -1 +0,0 @@ ---8<-- "CHANGELOG.md" diff --git a/docs/config.md b/docs/config.md index ab5d5e2..6947b67 100644 --- a/docs/config.md +++ b/docs/config.md @@ -1,40 +1,41 @@ -Configuration is the same as with [mkdocstrings-python][] except -that the handler name should be `python_xref` instead of `python`. Because -this handler extends the standard [mkdocstrings-python][] handler, the same options are +# Configuration + +Configuration is the same as with [mkdocstrings-python] except that the handler name should be `python_betterrefs` +instead of `python`. Because this handler extends the standard mkdocstrings-python handler, the same options are available. Additional options are added by this extension. Currently, there are two: -* **relative_crossrefs** - if set to true enables use of relative path syntax in - cross-references. - -* **check_crossrefs** - enables early checking of all cross-references. Note that - this option only takes affect if **relative_crossrefs** is also true. This option is - true by default, so this option is used to disable checking. Checking can - also be disabled on a per-case basis by prefixing the reference with '?', e.g. - `[something][?dontcheckme]`. +- **better_crossrefs** - If set to true enables use of better cross-reference syntax provided by this handler + extension (setting this to false would essentially mimic the `python` handler). This is enabled by default, so you + shouldn't need to specify it unless you want to disable this behavior. + +- **check_crossrefs** - Enables early checking of all cross-references. Note that this option only takes affect if + **better_crossrefs** is also true. This option is true by default, so you only need to specify it if you wish to + disable this checking. Checking can also be disabled on a per-case basis by prefixing a reference with '?', e.g. + `[something][?dontcheckme]`. !!! Example "mkdocs.yml plugins specification using this handler" -```yaml -plugins: -- search -- mkdocstrings: - default_handler: python_xref - handlers: - python_xref: - import: - - https://docs.python.org/3/objects.inv - options: - docstring_style: google - docstring_options: - ignore_init_summary: yes - merge_init_into_class: yes - relative_crossrefs: yes - check_crossrefs: no - separate_signature: yes - show_source: no - show_root_full_path: no -``` + ```yaml + plugins: + - search + - mkdocstrings: + default_handler: python_betterrefs + handlers: + python_betterrefs: + options: + docstring_style: google + docstring_options: + ignore_init_summary: true + merge_init_into_class: true + better_crossrefs: true + check_crossrefs: false + separate_signature: true + show_source: true + show_root_full_path: true + inventories: + - https://docs.python.org/3/objects.inv + ``` [mkdocstrings-python]: https://mkdocstrings.github.io/python/ diff --git a/docs/contributing/bugs-and-feature-reqs.md b/docs/contributing/bugs-and-feature-reqs.md new file mode 100644 index 0000000..aaf0362 --- /dev/null +++ b/docs/contributing/bugs-and-feature-reqs.md @@ -0,0 +1,185 @@ +# Bug Reports & Feature Requests + +`mkdocstrings-python-betterrefs` is an actively maintained project, and we welcome contributions in the form of both bug +reports and feature requests. This guide will help you understand how to effectively submit an issue, whether it's +reporting a bug or proposing a new feature. + +## Before creating an issue + +Before opening a new issue with your bug report, please do the following things: + +### Upgrade to the latest version + +Chances are that the bug you discovered was already fixed in a subsequent version. Thus, before reporting an issue, +ensure that you're using the latest version of `mkdocstrings-python-betterrefs`. + +!!! warning "Bug fixes are not backported" + + Please understand that only bugs that occur in the latest version will be addressed. Also, to reduce duplicate + efforts, fixes cannot be backported to earlier versions, except as a hotfix to the latest version, diverging from + the not yet finished features, even if already in the `main` branch. + + Due to the nature of our [versioning], that might mean that if you require an older version of `mkdocstrings`, or + other dependencies of this project, you might be stuck with an older, buggy version of this library. + +### Search for existing issues + +It's possible that the issue you're having was already reported. Please take some time and search the [existing +issues][issue-tracker] in the GitHub repository for your problem. If you do find an existing issue that matches the +problem you're having, simply leave a :thumbsup: reaction instead (avoid commenting "I have this issue too" or similar, +as that ultimately just clutters the discussion in that issue, but if you do think that you have something meaningful to +add, please do). + +!!! note + + Make sure to also check the closed issues. By default, github issue search will start with: `is:issue is:open`, + remove the `is:open` part to search all issues, not just the opened ones. It's possible that we seen this issue + before, but closed the issue as something that we're unable to fix. + +In case you found a relevant issue, however, it has already been closed as implemented (not as declined / not planned), +but the bug / proposed feature is still somehow relevant, don't be worried to drop a comment on this older issue, we +will get notifications for those too. That said, if you think there is sufficient new context now, it might also make +sense to open a new issue instead, but make sure to at least mention the old issue if you choose this route. + +## Creating a new issue + +At this point, when you still haven't found a solution to your problem, we encourage you to create an issue. + +We have some issue-templates ready, to make sure that you include all of the necessary things we need to know: + +- For a **bug report**, you can click [here][open-bug-issue]. +- For a **feature request**, you can instead click [here][open-feature-req-issue]. + +If you prefer, you can also [open a blank issue][open-blank-issue]. This will allow you to avoid having to follow the +issue templates above. This might be useful if your issue doesn't cleanly fit into either of these two, or if you prefer +to use your own categories and structure for the issue. That said, make please still make sure to include all of the +relevant details when you do so. + +## Writing good bug reports + +Generally, the GitHub issue template should guide you towards telling us everything that we need to know. However, for +the best results, keep reading through this section. In here, we'll explain how a well formatted issue should look like +in general and what it should contain. + +### Issue Title + +A good title is short and descriptive. It should be a one-sentence executive summary of the issue, so the impact and +severity of the bug you want to report can be inferred right from the title. + +| <!----> | Example | +| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------ | +| :material-check:{ style="color: #4DB6AC" } **Clear** | Check-crossrefs incorrectly reports stdlib modules as unknown. | +| :material-close:{ style="color: #EF5350" } **Wordy** | When check-crossrefs is enabled, stdlib modules get reported as unknown, causing warnings. | +| :material-close:{ style="color: #EF5350" } **Unclear** | Incorrect warnings | +| :material-close:{ style="color: #EF5350" } **Non-english** | Check-crossrefs informa incorrectamente que los módulos stdlib son desconocidos | +| :material-close:{ style="color: #EF5350" } **Useless** | Help | + +### Bug description + +Now, to the bug you want to report. Provide a clear, focused, specific and concise summary of the bug you encountered. +Explain why you think this is a bug that should be reported to us. Adhere to the following principles: + +1. **Explain the <u>what</u>, not the <u>how</u>** – don't explain [how to reproduce the bug](#reproduction) here, + we're getting there. Focus on articulating the problem and its impact as clearly as possible. +2. **Keep it short and concise** - if the bug can be precisely explained in one or two sentences, perfect. Don't + inflate it - maintainers and future users will be grateful for having to read less. +3. **Don't under-explain** - don't leave out important details just to keep things short. While keeping things short is + important, if something is relevant, mention it. It is more important for us to have enough information to be able + to understand the bug, even if it means slightly longer bug report. +4. **One bug at a time** - if you encounter several unrelated bugs, please create separate issues for them. Don't + report them in the same issue, as this makes it difficult for others when they're searching for existing issues and + also for us, since we can't mark such an issue as complete if only one of the bugs was fixed. + +:material-run-fast: **Stretch goal** – if you found a workaround or a way to fix the bug, you can add it as a comment on +the issue, to help other users temporarily mitigate the problem before we can fix the bug in our code base. + +### Reproduction + +A minimal reproducible example is at the heart of every well-written bug report, as it allows us maintainers to +instantly recreate the necessary conditions to inspect the bug and quickly find its root cause from there. It's a +proven fact that issues with concise and small reproductions can be fixed much faster. + +Focus on keeping your example minimal, ideally, test out the bug in a brand new temporary project, with as little +dependencies as possible (ideally just this library). Use a minimal `mkdocs.yml` configuration, with only what's +necessary to demonstrate the bug. In most cases, it's enough to submit 3 files: `mkdocs.yml`, a single `docs.md` and a +single `source.py`. + +Once you have your reproducible example ready, just give us these files as code snippets. Alternatively, you can also +link us to a github gist, or something similar. + +??? tip "How to include code-snippets (markdown)" + + In case you're not yet familiar with the syntax, GitHub issues use `markdown` format, which means you can use some + nice custom formatting to make the text appear distinct. One of these formatting options is a source-code block / + code snippet. To include one, you will want to use the following syntax: + + ````markdown + ```language + your code + it can be multiline + ``` + ```` + + Note that the symbols used here aren't single quotes (`'`), they're backticks: `` ` ``. + On an english keyboard, you can type these using the key right below escape (also used for tildes: `~`). + + The `language` controls how the code will be highlighted. For python, you can use `python`, for yaml, `yaml`, etc. + +Sometimes, the bug can't be described in terms of code snippets, such as when reporting a mistake in the documentation. +In that case, provide a link to the documentation or whatever other relevant things that will allows us to see the bug +with minimal effort. In certain cases, it might even be fine to leave the reproduction steps section empty. + +## Next steps + +Once the issue is submitted, you have 2 options: + +### Wait for us to address it + +We will try to review your issue as soon as possible. Please be patient though, as this is an open-source project +maintained by volunteers, who work on it simply for the fun of it. This means that we may sometimes have other +priorities in life or we just want to work on some more interesting tasks first. It might therefore take a while for us +to get to your issue, but we try and do our best to respond reasonably quickly, when we can. Even when things are +slower, we kindly ask you to avoid posting comments like "Any progress on this?" as they are not helpful and only create +unnecessary clutter in the discussion. + +When we do address your issue, we might need further information from you. GitHub has a notification system, so once we +respond, you will be notified there. Note that, by default, these notifications might not be forwarded to your email or +elsewhere, so please check GitHub periodically. + +Finally, when we address your issue, we will mark the issue as closed (GitHub will notify you of this too). Once that +happens, your bug should be fixed / feature implemented, but we appreciate it if you take the time to verify that +everything is working correctly. If something is still wrong, you can reopen the issue and let us know. + +!!! warning "Issues are fixed on the main branch" + + Do note that when we close an issue, it means that we have fixed your issue in the `main` branch of the repository. + That doesn't necessarily mean the fix has been released on PyPI yet, so you might still need to wait a while unil + the next release is out. Alternatively, you can also try the [git installation][git-installation] to get the + project right from that latest `main` branch. + +### Attempt to solve it yourself + +!!! quote + + The fastest way to get something done is to avoid waiting on others. + +If you wish to try and tackle the bug yourself, let us know by commenting on the issue with something like "I'd like to +work on this". This helps us avoid duplicate efforts and ensures that we don't work on something you're already +addressing. + +Once a maintainer sees your comment, they will assign the issue to you. Being assigned is a soft approval from us, +giving you the green light to start working. + +Of course, you are welcome to start working on the issue even before being officially assigned. However, please be +aware that sometimes we choose not to fix certain bugs for specific reasons. In such cases, your work might not end up +being used. + +Before starting your work though, make sure to also read our [pull request guide][pr-guide]. + +[versioning]: ./versioning.md +[issue-tracker]: https://github.com/ItsDrike/mkdocstrings-python-betterrefs/issues +[open-bug-issue]: https://github.com/ItsDrike/mkdocstrings-python-betterrefs/issues/new?labels=bug&template=bug-report.yml +[open-feature-req-issue]: https://github.com/ItsDrike/mkdocstrings-python-betterrefs/issues/new?labels=feature&template=feature-request.yml +[open-blank-issue]: https://github.com/ItsDrike/mkdocstrings-python-betterrefs/issues/new?template=Blank+issue +[git-installation]: ../install.md#latest-git-version +[pr-guide]: ./making-a-pr.md diff --git a/docs/contributing/making-a-pr.md b/docs/contributing/making-a-pr.md new file mode 100644 index 0000000..6d36f1d --- /dev/null +++ b/docs/contributing/making-a-pr.md @@ -0,0 +1,5 @@ +# Making a PR + +!!! bug "Work In Progress" + + This page is still being written. diff --git a/docs/index.md b/docs/index.md index eb03413..1704308 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,129 +1,45 @@ -## Relative cross-references +--- +hide: + - navigation +--- -By default, [mkdocstrings] only supports cross-references where the path is -fully qualified or is empty, in which case it is taken from the title. -If you work with long package and class names or with namespace packages, this can result in a lot -of extra typing and harder to read doc-strings. +# mkdocstrings-python-betterrefs -If you enable the `relative_crossrefs` option in the `python_xref` handler options -in your mkdocs.yml file ([see configuration](config.md) for an example), then the handler -will support more compact relative syntax: +Python handler for [mkdocstrings] with improved handling for cross-references, including relative ones. -=== "Absolute" +[mkdocstrings] is an awesome plugin for [MkDocs] that can generate Markdown API documentation from comments in code. The +standard [python handler (mkdomkdocstrings-python)][mkdocstrings-python] allows you to create cross-reference links +using the syntax `[<title>][<path>]` where the path must either be the fully qualified name of the referent or is empty, +in which case the path is taken from the title. - ```python - class MyClass: - def this_method(self): - """ - See [other_method][mypkg.mymod.MyClass.other_method] - from [MyClass][mypkg.mymod.Myclass] - """ - ``` +mkdocstrings-python does already have support for cross-references, however, it is currently only available in the +insiders edition, which is limited to their sponsors. Additionally, this implementation is fairly limited in comparison +to what this project offers. -=== "Relative" +!!! tip - ```python - class MyClass: - def this_method(self): - """ - See [other_method][..] from [MyClass][(c)] - """ - ``` + For more information on the mkdocstrings-python official support of relative cross-references, check out the + feature request proposing them: [here][official-xrefs-issue], and the docs detailing the configuration option: + [here][official-xrefs-docs]. -The relative path specifier has the following form: + Even though the issue proposed the syntax similar to that used by this handler, the official relative crossrefs + support ended up being a very limited version of it. -* If the path ends in `.` then the title text will be appended to the path - (ignoring bold, italic or code markup). + It is expected that relative cross-references will make it into the open-source version once a funding goal of + $2,000 is reached. You can see the current progress towards this goal [here][official-xrefs-funding-goal]. -* If the path begins with a single `.` then it will be expanded relative to the path - of the doc-string in which it occurs. - -* If the path begins with `(c)`, that will be replaced by the path of the - class that contains the doc-string - -* If the path begins with `(m)`, that will be replaced by the path of the - module that contains the doc-string - -* If the path begins with `(p)`, that will be replaced by the path of the - package that contains the doc-string. If there is only one module in the - system it will be treated as a package. - -* If the path begins with one or more `^` characters, then will be replaced - by the path of the parent element. For example, when used in a doc-string - for a method, `^` would get replaced with the class and `^^` would get - replaced with the module. - -* Similarly, if the path begins with two or more `.` characters, then all but - the last `.` will be replaced by the parent element, and if nothing follows - the last `.`, the title text will be appended according to the first rule. - - *NOTE: When using either `^` or `..` we have found that going up more than one - or two levels makes cross-references difficult to read and should be avoided* - -These are demonstrated here: - -=== "Relative" - - ```python - class MyClass: - def this_method(self): - """ - [MyClass][^] - Also [MyClass][(c)] - [`that_method`][^.] - Also [`that_method`][..] - [init method][(c).__init__] - [this module][(m)] - [this package][(p)] - [OtherClass][(m).] - [some_func][^^.] or [some_func][...] - """ - ``` - -=== "Absolute" - - ```python - class MyClass: - def this_method(self): - """ - [MyClass][mypkg.mymod.MyClass] - Also [MyClass][mypkg.mymod.MyClass] - [`that_method`][mypkg.mymod.MyClass.that_method] - Also [`that_method`][mypkg.mymod.MyClass.that_method] - [init method][mypkg.mymod.MyClass.__init__] - [this module][mypkg.mymod] - [this package][mypkg] - [OtherClass][mypkg.mymod.OtherClass] - [some_func][mypkg.mymod.some_func] - """ - ``` - -This has been [proposed as a feature in the standard python handler][relative-crossref-issue] -but has not yet been accepted. - -## Cross-reference checking - -If `relative_crossrefs` and `check_crossrefs` are both enabled (the latter is true by default), -then all cross-reference expressions will be checked to ensure that they exist and failures -will be reported with the source location. Otherwise, missing cross-references will be reported -by mkdocstrings without the source location, in which case it is often difficult to locate the source -of the error. Note that the errors generatoed by this feat[.gitignore](..%2F.gitignore) - - - -ure are in addition to the errors -from mkdocstrings. - -The current implementation of this feature can produce false errors for definitions from the -python standard library. You can disable the check on a case-by-case basis by prefixing the -reference expression with a `?`, for example: - -``` -This function returns a [Path][?pathlib.] instance. -``` - -[mkdocstrings]: https://mkdocstrings.github.io/ -[mkdocstrings_python]: https://mkdocstrings.github.io/python/ -[relative-crossref-issue]: https://github.com/mkdocstrings/python/issues/27 +This package extends [mkdocstrings-python] to support an improved cross-reference syntax, that allows you to write +your doc-strings with these nicer cross-references. The primary goal is making cross-references shorter and less +repetitive. +Do note that this project is a fork of the original [mkdocstrings-python-xref]. For more info, see our [fork +notice][fork-notice] section +[MkDocs]: https://mkdocs.readthedocs.io/ +[mkdocstrings]: https://github.com/mkdocstrings/mkdocstrings +[mkdocstrings-python]: https://github.com/mkdocstrings/python +[official-xrefs-issue]: https://github.com/mkdocstrings/python/issues/27 +[official-xrefs-docs]: https://mkdocstrings.github.io/python/usage/configuration/docstrings/?h=relative#relative_crossrefs +[official-xrefs-funding-goal]: https://mkdocstrings.github.io/python/insiders/#funding +[mkdocstrings-python-xref]: https://github.com/analog-garage/mkdocstrings-python-xref +[fork-notice]: ./meta/fork.md diff --git a/docs/install.md b/docs/install.md index fafd479..a80abc5 100644 --- a/docs/install.md +++ b/docs/install.md @@ -1,10 +1,149 @@ -### Using pip +# Installation -``` -pip install mkdocstrings-python-xref -``` -### Using conda +!!! note "Installing alongside `mkdocstrings-python`" -``` -conda install -c conda-forge mkdocstrings-python-xref -``` + You don't need to explicitly specify [`mkdocstrings-python`][mkdocstrings-python] as a dependency, as this package + already lists it as it's internal dependency, which means installing `mkdocstrings-python-betterrefs` will also + install `mkdocstrings-python` for you. + +## PyPI (stable) version + +`mkdocstrings-python-betterrefs` is available on [PyPI] and can be installed like any other python library with: + +=== ":simple-python: pip" + + ```bash + pip install mkdocstrings-python-betterrefs + ``` + + <div class="result" markdown> + + [pip](https://pip.pypa.io/en/stable/) is the main package installer for Python. + + </div> + +=== ":simple-poetry: poetry" + + ```bash + poetry add mkdocstrings-python-betterrefs + ``` + + <div class="result" markdown> + + [Poetry](https://python-poetry.org/) is an all-in-one solution for Python project management. + + </div> + +=== ":simple-rye: rye" + + ```bash + rye add mkdocstrings-python-betterrefs + ``` + + <div class="result" markdown> + + [Rye](https://rye.astral.sh/) is an all-in-one solution for Python project management, written in Rust. + + </div> + +=== ":simple-ruff: uv" + + ```bash + uv pip install mkdocstrings-python-betterrefs + ``` + + <div class="result" markdown> + + [uv](https://github.com/astral-sh/uv) is an ultra fast dependency resolver and package installer, written in Rust. + + </div> + +=== ":simple-pdm: pdm" + + ```bash + pdm add mkdocstrings-python-betterrefs + ``` + + <div class="result" markdown> + + [PDM](https://pdm-project.org/en/latest/) is an all-in-one solution for Python project management. + + </div> + +## Latest (git) version + +!!! warning "We don't guarantee stability with method of installing" + +If you wish to install the latest available version (the one you currently see in the `main` git branch), you may +instead choose this method of installing. + +This kind of installation should only be done if you wish to test some new unreleased features and it's likely that you +will encounter bugs. + +That said, this library is still in development, and there may be some features that you might wish to try, even +though they're not yet available in the latest release. This method of installation allows you to do just that. + +To install the latest version of `mkdocstrings-python-betterrefs` directly from the `main` git branch, use: + +=== ":simple-python: pip" + + ```bash + pip install 'mkdocstrings-python-betterrefs@git+https://github.com/ItsDrike/mkdocstrings-python-betterrefs@main' + ``` + + <div class="result" markdown> + + [pip](https://pip.pypa.io/en/stable/) is the main package installer for Python. + + </div> + +=== ":simple-poetry: poetry" + + ```bash + poetry add 'git+https://github.com/ItsDrike/mkdocstrings-python-betterrefs#main' + ``` + + <div class="result" markdown> + + [Poetry](https://python-poetry.org/) is an all-in-one solution for Python project management. + + </div> + +=== ":simple-rye: rye" + + ```bash + rye add mkdocstrings-python-betterrefs --git='https://github.com/ItsDrike/mkdocstrings-python-betterrefs' --branch main + ``` + + <div class="result" markdown> + + [Rye](https://rye.astral.sh/) is an all-in-one solution for Python project management, written in Rust. + + </div> + +=== ":simple-ruff: uv" + + ```bash + uv pip install 'mkdocstrings-python-betterrefs@git+https://github.com/ItsDrike/mkdocstrings-python-betterrefs@main' + ``` + + <div class="result" markdown> + + [uv](https://github.com/astral-sh/uv) is an ultra fast dependency resolver and package installer, written in Rust. + + </div> + +=== ":simple-pdm: pdm" + + ```bash + pdm add "git+https://github.com/ItsDrike/mkdocstrings-python-betterrefs@main" + ``` + + <div class="result" markdown> + + [PDM](https://pdm-project.org/en/latest/) is an all-in-one solution for Python project management. + + </div> + +[PyPI]: https://pypi.org/project/mkdocstrings-python-betterrefs +[mkdocstrings-python]: https://github.com/mkdocstrings/python diff --git a/docs/license.md b/docs/license.md deleted file mode 100644 index af216c6..0000000 --- a/docs/license.md +++ /dev/null @@ -1 +0,0 @@ ---8<-- "LICENSE.md" diff --git a/docs/meta/changelog.md b/docs/meta/changelog.md new file mode 100644 index 0000000..af66744 --- /dev/null +++ b/docs/meta/changelog.md @@ -0,0 +1,7 @@ +# Changelog + +!!! danger "" + + Major and minor releases include the changes specified in prior development releases. + +--8<-- "CHANGELOG.md" diff --git a/docs/meta/fork.md b/docs/meta/fork.md new file mode 100644 index 0000000..8b81b93 --- /dev/null +++ b/docs/meta/fork.md @@ -0,0 +1,48 @@ +# Fork notice + +This project is a fork of the excellent [mkdocstrings-python-xref] project. The primary reason for forking was personal +curiosity with how this project worked, though another reason was also that the original project was somewhat slow at +addressing some issues and used fairly uncommon packaging practices when it comes to modern python, which IMO made it +harder to contribute to. + +At its core, this fork retains the original functionality while addressing compatibility issues that arose as its +dependencies (namely: mkdocstrings, mkdocstrings-python, mkdocstrings-autorefs, and griffe) were updated. + +In addition, significant improvements have been made to the codebase, including cleanup and updates to follow modern +packaging practices. For example, this project moved away from Conda in favor of [uv]. + +We’ve also placed a greater emphasis on properly managing project dependencies. Stricter version requirements have been +applied to ensure stability, meaning that new versions of dependencies will only be supported once they’ve been properly +tested. The goal is to automate this process with GitHub workflows that will periodically check for new versions, run +tests, and publish a new PyPI release if all tests pass. This is particularly important given this library's reliance on +internal features of [mkdocstrings-python], which means breakages are common when dependencies are updated. + +It's important to note that this is a "hard fork," meaning future updates to the original mkdocstrings-python-xref will +not necessarily be merged back here. This is mainly because the code-base of this project has become sufficiently +different to make that task pretty hard. However, if a relevant feature from the original project is introduced, we may +consider porting it to this fork. That said, considering this project haven't released a new feature in quite a while +now, this likely won't be a concern. + +!!! note + + Due to technical reasons, this project is not marked as a "fork" on GitHub, even though it is one. Forked repositories + come with limitations, such as disappearing if the original repo is made private, or creating confusion by suggesting + this project is still work-in-progress aimed at contributing back to the original. Using a standalone repository helps + avoid these issues and makes it clear that this is an independent continuation of the original project. + +## Acknowledgements + +This project would not exist without the original mkdocstrings-python-xref project, created by Christopher Barber and +the Analog Devices, Inc. We owe a huge thanks to them for their work, which laid the foundation for this fork and also +suggested the original idea of the improved syntax for cross-references. + +## Legal notice + +The purpose of this page is to acknowledge the original author and clarify the reasons behind this fork, as well as the +changes that have been made since. For the legal information required for derivative works under the Apache 2.0 license, +please refer to the [license page][license-page] instead. + +[mkdocstrings-python-xref]: https://github.com/analog-garage/mkdocstrings-python-xref +[mkdocstrings-python]: https://github.com/mkdocstrings/python +[uv]: https://docs.astral.sh/uv +[license-page]: ./license.md diff --git a/docs/meta/license.md b/docs/meta/license.md new file mode 100644 index 0000000..46f2a70 --- /dev/null +++ b/docs/meta/license.md @@ -0,0 +1,85 @@ +# License + +## Source Code + +This project's source code is licensed under the **Apache 2.0** license. + +This license allows you to use `mkdocstrings-python-betterrefs` in any project, regardless of it's license (including a +proprietary one). You can change the code, distribute it or even use it for commercial purposes. That said, if you wish +to do either of these, you are required to include a copyright notice, the license fulltext and state any significant +changes made to the software; include a `NOTICE` file with the attribution notes. + +!!! tip + + If you want to see a quick glance of what this license allows, prohibits & requires, check it out in [tl;dr + legal][tldr-apache2]. + +??? example "Full LICENSE text" + + ```title="LICENSE.txt" + --8<-- "LICENSE.txt" + ``` + +!!! note + + If you need a copyright header for attribution, you can use: + + === "Rendered" + + <a href="https://github.com/ItsDrike/mkdocstrings-python-betterrefs">mkdocstrings-python-betterrefs</a> + + Copyright © 2022-2023 Analog Devices, Inc.<br> + Copyright © 2025 ItsDrike <itsdrike@protonmail.com> + + === "HTML" + + ```html + <a href="https://github.com/ItsDrike/mkdocstrings-python-betterrefs">mkdocstrings-python-betterrefs</a> + + Copyright © 2022-2023 Analog Devices, Inc.<br> + Copyright © 2025 ItsDrike <itsdrike@protonmail.com> + ``` + +## This documentation + +This documentation itself follows a Creative Commons license: <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a> <img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1" alt=""><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1" alt=""><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/nc.svg?ref=chooser-v1" alt=""><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/sa.svg?ref=chooser-v1" alt=""> + +!!! note + + If you need a copyright header for proper attribution, you can use: + + === "Rendered" + + <a href="https://itsdrike.github.io/mkdocstrings-python-betterrefs">mkdocstrings-python-betterrefs documentation</a> © 2025 by <a href="mailto:itsdrike@protonmail.com">ItsDrike</a> + + If you also need the license identifier, use the following: + + <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a> <img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1" alt=""><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1" alt=""><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/nc.svg?ref=chooser-v1" alt=""><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/sa.svg?ref=chooser-v1" alt=""> + + === "HTML" + + ```html + <a href="https://itsdrike.github.io/mkdocstrings-python-betterrefs">mkdocstrings-python-betterrefs documentation</a> © 2025 by <a href="mailto:itsdrike@protonmail.com">ItsDrike</a> + ``` + + If you also need the license identifier, use the following: + + ```html + <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a> <img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1" alt=""><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1" alt=""><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/nc.svg?ref=chooser-v1" alt=""><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/sa.svg?ref=chooser-v1" alt=""> + ``` + +## Fork NOTICE + +Note that this project itself is a fork of [`mkdocstrings-python-xref`][mkdocstrings-python-xref] and it is distributed +under the same (Apache 2.0) license (fulltext of this license was already shown above). As such, it is required to +include a `NOTICE` file detailing the changes made in this fork. The full text of the `NOTICE` file, which is included +below, lists these modifications. + +??? example "Full NOTICE text" + + ```markdown title="NOTICE.md" + --8<-- "NOTICE.md" + ``` + +[mkdocstrings-python-xref]: https://github.com/analog-garage/mkdocstrings-python-xref +[tldr-apache2]: https://www.tldrlegal.com/license/apache-license-2-0-apache-2-0 diff --git a/docs/meta/support.md b/docs/meta/support.md new file mode 100644 index 0000000..95c3047 --- /dev/null +++ b/docs/meta/support.md @@ -0,0 +1,10 @@ +# Support + +- If you found a bug, or wish to propose a new feature, please follow this [guide][bugs-and-feature-reqs-guide]. +- If you just want to ask a question, feel free to do so on the [project's discussions board][discussions]. +- In case you have a security concern, or some other problem that requires private resolution, you can [send me an + email][email] (`itsdrike@protonmail.com`). + +[bugs-and-feature-reqs-guide]: ../contributing/bugs-and-feature-reqs.md +[discussions]: https://github.com/ItsDrike/mkdocstrings-python-betterrefs/discussions +[email]: mailto:itsdrike@protonmail.com diff --git a/docs/meta/versioning.md b/docs/meta/versioning.md new file mode 100644 index 0000000..8592d41 --- /dev/null +++ b/docs/meta/versioning.md @@ -0,0 +1,55 @@ +# Versioning + +!!! danger "Pre-release phase" + + `mkdocstrings-python-betterrefs` is currently in the pre-release phase (pre v1.0.0). During this phase, these + guarantees will NOT be followed! This means that **breaking changes can occur in minor version bumps**. That said, + micro version bumps are still strictly for bugfixes, and will not include any features or breaking changes. + +This library follows [semantic versioning model][semver], which means the major version is updated every time +there is an incompatible (breaking) change made to the public API. + +In our case, the public API refers to the cross-reference syntax. A major version bump would therefore mean a +potentially breaking update, requiring you to modify the cross references to be compatible with the new version. + +## Examples of Breaking level change (major bump: `vX.0.0`) + +We try to avoid breaking changes as much as we can, but it might sometimes be beneficial, especially if it's to resolve +a problem, that people might technically be relying on. + +Here are some examples of what constitutes a breaking change: + +- Dropping support for the `(m)` syntax, resolving to the current module +- Changing the behavior of a trailing `.` (previously just appending the title text) to now first clean up any markup + from the title (like bold/italic or code markup) +- Using stricter validation of cross-references (breaking, as new warnings will be produced on xrefs that were + considered as valid before and didn't produce warnings) +- Dropping support for `?` prefixing references to avoid cross-ref validation, as it's no longer necessary (validation + now always works, ignoring it should never be needed) +- Removing or renaming a [configuration option][config-option] for the handler (like `check_crossrefs`) +- Changing the default value for a configuration option + +## Examples of a Feature level Change (minor bump: `v1.X.0`) + +- Dropping support of an old `mkdocstrings-python` version +- Introducing support of a new `mkdocstrings-python` version +- Introducing new cross-reference syntax that doesn't interfere with the existing one +- Adding a configuration option for the handler (like `check_crossrefs`), assuming it doesn't affect the default + behavior in a breaking way, at least not by default. +- Adding support for validating more cross-references (not breaking, as people will have these marked as ignored, this + just allows to no longer ignore these xrefs from validation) + +## Examples of a Patch level change (patch bump: `v1.0.X`) + +- Adding unit-tests +- Adding CI workflows +- Changing the documentation +- **Changing the internal code in any way** + +Relying on the handler class, or any other code components from this library is not supported. **All code for this +library is considered a part of the private API** and is subject to any changes without backwards compatibility in mind. + +We usually make these updates with the goal of improving the code readability or efficiency. + +[semver]: https://semver.org +[config-option]: ../config.md diff --git a/docs/support.md b/docs/support.md deleted file mode 100644 index 3d9f5e6..0000000 --- a/docs/support.md +++ /dev/null @@ -1,4 +0,0 @@ -You can file bug reports or feature requests -[on GitHub](https://github.com/analog-garage/mkdocstrings-python-xref/issues) or if you just want to ask -a question on the [project's discussions board](https://github.com/analog-garage/mkdocstrings-python-xref/discussions) - diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 0000000..032110c --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,159 @@ +--- +hide: + - navigation +--- + +# Usage + +## Relative cross-references + +As already mentioned before, `mkdocstrings-python-betterrefs` allows you to use a custom improved syntax when specifying +your cross-references, to make your references shorter and easier to comprehend. The most important feature is the +relative cross-references support. Check the example below: + +=== "Absolute" + + ```python title="mypkg/mymod.py" + class OtherClass: ... + + class MyClass: + def other_method(self): ... + + def this_method(self): + """ + See [other_method][mypkg.mymod.MyClass.other_method] and + the [OtherClass][mypkg.mymod.OtherClass]. + """ + ``` + +=== "Relative" + + ```python title="mypkg/mymod.py" + class OtherClass: ... + + class MyClass: + def other_method(self): ... + + def this_method(self): + """ + See [other_method][..other_method] and + the [OtherClass][(m).OtherClass] + """ + ``` + +The relative path specifier works as follows: + +<!-- prettier-ignore-start --> + +- `(c)`: Replaced by the path of the **class** that contains the docstring. +- `(m)`: Replaced by the path of the **module** that contains the docstring. +- `(p)`: Replaced by the path of the **package** that contains the docstring. (If this is a stand-alone module, this +module will be treated as a package.) + +- **One or more `.` characters:** Expanded to the path of the current docstring (or its parent elements). + + For example, in a method's docstring: + + - `.` is replaced by the method name, + - `..` is replaced by the class name, and + - `...` is replaced by the module name. + +- **One or more `^` characters:** Replaced by the path of the parent element. This is a shorthand for .., which is + commonly used. + + For instance, in a method's docstring: + + - `^` is replaced by the class name, and + - `^^` is replaced by the module name. + +<!-- prettier-ignore-end --> + +!!! note + + When using either `^` or `..` we have found that going up more than one or two levels makes cross-references + difficult to read and should be avoided + +## Avoiding repetition + +In addition to relative reference support, there's special handling to reduce repetitive reference declarations in the +title and the cross-reference target. For instance, instead of writing `[MyClass][..MyClass]`, you can simply write +`[MyClass][..]`, resulting in a much cleaner and more compact syntax. + +This rule applies when the cross-reference path ends with a period (`.`); in such cases, the title text is automatically +appended to the path (ignoring any bold, italic, or code markup). + +## Demonstration + +Quick demonstration: + +=== "mkdocstrings-python-betterrefs" + + ```python title="this_package/this_module.py" + def some_func(): ... + + class MyClass: + def that_method(self): ... + + def this_method(self): + """ + [MyClass][^] + Also [MyClass][(c)] + And [`MyClass`][(m).] yet again + [`that_method`][^.] + Also [`that_method`][..] + [init method][(c).__init__] + [this module][(m)] + [this package][(p)] + [that module][(p).that_module] or [that module][(p).] + [OtherClass][(m).] + [some_func][^^.] or [some_func][...] + """ + ``` + +=== "mkdocstrings-python" + + ```python title="this_package/this_module.py" + def some_func(): ... + + class MyClass: + def that_method(self): ... + + def this_method(self): + """ + [MyClass][this_package.this_module.MyClass] + Also [MyClass][this_package.this_module.MyClass] + And [`MyClass`][this_package.this_module.MyClass] yet again + [`that_method`][this_package.this_module.MyClass.that_method] + Also [`that_method`][this_package.this_module.MyClass.that_method] + [init method][this_package.this_module.MyClass.__init__] + [this module][this_package.this_module] + [this package][this_package] + [that module][this_package.that_module] or [that module][this_package.that_module] + [OtherClass][this_package.this_module.OtherClass] + [some_func][this_package.this_module.some_func] or [some_func][this_package.this_module] + """ + ``` + +## Cross-reference checking + +If `check_crossrefs` is enabled (default), then all cross-reference expressions will be validated to ensure that they +exist. Failures will be reported with the source location information. + +If disabled, missing cross-references will still be reported by `mkdocstrings` directly, but these reports lack source +location details, which can make it challenging to locate the problematic docstring. + +Note that the errors generated by this feature are in addition to the errors from `mkdocstrings`, which will mean you +will see 2 errors for each invalid reference. + +!!! warning + + The current implementation of this feature can produce false errors for definitions from the python standard + library, or external imported libraries. You can disable the check on a case-by-case basis by prefixing the + reference expression with a `?`, for example: + + ```python + def foo() -> pathlib.Path: + """ + This function returns a [Path][?pathlib.] instance. + """ + ``` diff --git a/environment.yml b/environment.yml deleted file mode 100644 index 437c633..0000000 --- a/environment.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: mkxref-dev -channels: - - conda-forge - -dependencies: - # runtime - - python >=3.8,<3.13 - - mkdocstrings-python >=1.6.2,<2.0 - - griffe >=1.0 - # build - - python-build >=1.0.0 - - hatchling >=1.21 - # test - - coverage >=7.4.0 - - pytest >=8.2 - - pytest-cov >=5.0 - - pylint >=3.0.3 - - mypy >=1.10 - - ruff >=0.4.10 - - beautifulsoup4 >=4.12 - # documentation - - black >=23.12 - - mike >=1.1,<2.0 - - mkdocs >=1.5.3,<2.0 - - mkdocs-material >=9.5.4 - - linkchecker >=10.4 diff --git a/github-condarc.yml b/github-condarc.yml deleted file mode 100644 index 586b715..0000000 --- a/github-condarc.yml +++ /dev/null @@ -1,7 +0,0 @@ -auto_stack: 1 -repodata_fns: - - repodata.json -default_channels: - - conda-forge -# solver: libmamba - diff --git a/mkdocs.yml b/mkdocs.yml index e48a5a4..0cb1b2a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,87 +1,128 @@ -site_name: "mkdocstrings-python-xref" -site_description: "A an extended Python handler for mkdocstrings." -repo_url: https://github.com/analog-garage/mkdocstrings-python-xref -site_url: https://github.com/analog-garage/mkdocstrings-python-xref -repo_name: GitHub -site_dir: site -site_author: Christopher Barber -copyright: Copyright © 2022-2023 Analog Devices, Inc. +site_name: "mkdocstrings-python-betterrefs" +site_description: "Extended mkdocstrings-python handler with better cross-references support" +site_url: https://github.com/ItsDrike/mkdocstrings-python-betterrefs + +repo_url: https://github.com/ItsDrike/mkdocstrings-python-betterrefs +copyright: Copyright © 2025 by <a href="mailto:itsdrike@protonmail.com">ItsDrike</a> watch: - src/mkdocstrings_handlers + - LICENSE.txt + - NOTICE.md + - CHANGELOG.md + +exclude_docs: | + LICENSE.md nav: -- User Guide: index.md -- Setup: - - Installation: install.md - - Configuration: config.md -- Support: - - Getting help: support.md - - Changelog: changelog.md - - License: license.md + - Home: index.md + - Installation: + - Installation: install.md + - Configuration: config.md + - Usage: usage.md + - Meta: + - Getting help: meta/support.md + - Versioning: meta/versioning.md + - Changelog: meta/changelog.md + - Fork notice: meta/fork.md + - License: meta/license.md + - Contributing: + - Bugs & Feature requests: contributing/bugs-and-feature-reqs.md + - Making a PR: contributing/making-a-pr.md theme: name: material logo: logo.svg favicon: logo.svg - features: - - navigation.tabs - - navigation.tabs.sticky - - navigation.top palette: - - media: "(prefers-color-scheme: light)" - scheme: default - primary: white - accent: purple - toggle: - icon: material/weather-sunny - name: Switch to dark mode - - media: "(prefers-color-scheme: dark)" - scheme: slate - primary: black - accent: lime - toggle: - icon: material/weather-night - name: Switch to light mode + - media: "(prefers-color-scheme)" + primary: black + accent: black + toggle: + icon: material/brightness-auto + name: Switch to light mode -extra: - generator: false - version: - provider: mike - default: stable + - media: "(prefers-color-scheme: light)" + scheme: default + primary: black + accent: black + toggle: + icon: material/brightness-7 + name: Switch to dark mode + + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: black + accent: black + toggle: + icon: material/brightness-4 + name: Switch to system preference + icon: + repo: fontawesome/brands/github + features: + - content.tabs.link + - content.code.copy + - content.action.edit + - search.highlight + - search.share + - search.suggest + - navigation.footer + - navigation.indexes + - navigation.sections + - navigation.tabs + - navigation.tabs.sticky + - navigation.top + - toc.follow markdown_extensions: -- admonition -- pymdownx.snippets: - check_paths: true -- pymdownx.superfences -- pymdownx.tabbed: - alternate_style: true + - admonition + - attr_list + - md_in_html + - toc: + permalink: true + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.snippets: + check_paths: true + - pymdownx.inlinehilite + - pymdownx.superfences + - pymdownx.details + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + - pymdownx.tabbed: + alternate_style: true plugins: -- search -- mike: - # These fields are all optional; the defaults are as below... - canonical_version: stable - version_selector: true - css_dir: css - javascript_dir: js -- mkdocstrings: - default_handler: python_xref - handlers: - python_xref: - paths: [src] - import: - - https://docs.python.org/3/objects.inv - - https://mkdocstrings.github.io/objects.inv - - https://mkdocstrings.github.io/griffe/objects.inv - options: - docstring_style: google - docstring_options: - ignore_init_summary: yes - merge_init_into_class: yes - relative_crossrefs: yes - separate_signature: yes - show_source: no - show_root_full_path: no + - search + - mike: + canonical_version: "latest" + version_selector: true + - mkdocstrings: + enable_inventory: true + default_handler: python_betterrefs + handlers: + python_betterrefs: + paths: [src] + options: + docstring_style: google + docstring_options: + ignore_init_summary: true + merge_init_into_class: true + better_crossrefs: true + check_crossrefs: true + separate_signature: true + show_source: false + show_root_full_path: false + inventories: + - https://docs.python.org/3/objects.inv + - https://www.mkdocs.org/objects.inv + - https://mkdocstrings.github.io/objects.inv + - https://mkdocstrings.github.io/griffe/objects.inv + - https://mkdocstrings.github.io/autorefs/objects.inv +extra: + version: + provider: mike + default: latest + alias: true diff --git a/pyproject.toml b/pyproject.toml index a1d7f52..d9ab067 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,183 +1,191 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - [project] -name = "mkdocstrings-python-xref" -description = "Enhanced mkdocstrings python handler" +name = "mkdocstrings-python-betterrefs" +version = "0.1.0" +license = "Apache-2.0" +description = "Extended mkdocstrings-python handler with better cross-references support" readme = "README.md" authors = [ - {name = "Christopher Barber", email="Christopher.Barber@analog.com" }, + { name = "Christopher Barber", email = "Christopher.Barber@analog.com" }, + { name = "ItsDrike", email = "itsdrike@protonmail.com" }, ] +maintainers = [{ name = "ItsDrike", email = "itsdrike@protonmail.com" }] classifiers = [ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Topic :: Software Development :: Documentation", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", -] -keywords = [ - "documentation-tool", "mkdocstrings", "mkdocstrings-handler", "python" + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Natural Language :: English", + "Operating System :: OS Independent", + "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", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Topic :: Documentation", + "Topic :: Software Development", + "Topic :: Software Development :: Documentation", + "Topic :: Utilities", + "Typing :: Typed", ] -dynamic = ["version"] -requires-python = ">=3.8" +keywords = ["documentation", "mkdocstrings", "mkdocstrings-handler"] +requires-python = ">=3.9" dependencies = [ - "mkdocstrings-python >=1.6.2,<2.0", - "griffe >=1.0" + # TODO: Look into supported version ranges, for now + # require exact versions to ensure things will work. + "mkdocs>=1.4", + "mkdocs-autorefs>=1.3.0", + "mkdocstrings==0.28.0", + "mkdocstrings-python==1.14.6", + "griffe==1.5.6", + "typing-extensions>=4.0", ] [project.urls] -Repository = "https://github.com/analog-garage/mkdocstrings-python-xref" -Documentation = "https://analog-garage.github.io/mkdocstrings-python-xref/" - -[tool.hatch.version] -path = "src/mkdocstrings_handlers/python_xref/VERSION" -pattern = "\\s*(?P<version>[\\w.]*)" - -[tool.hatch.build] -include = [ - "src/**/*.py", - "src/mkdocstrings_handlers/python_xref/VERSION", - "src/mkdocstrings_handlers/python_xref/py.typed", +Homepage = "https://itsdrike.github.com/mkdocstrings-python-betterrefs/" +Documentation = "https://itsdrike.github.com/mkdocstrings-python-betterrefs/" +Repository = "https://github.com/ItsDrike/mkdocstrings-python-betterrefs" +Issues = "https://github.com/ItsDrike/mkdocstrings-python-betterrefs/issues" + +[tool.uv] +default-groups = ["lint", "test", "docs"] + +[dependency-groups] +lint = [ + "pre-commit>=3.3.3", + "ruff>=0.9.2", + "basedpyright>=1.13.3", ] - -[tool.hatch.build.targets.sdist] -packages = [ - "src/mkdocstrings_handlers", - "src/mkdocstrings_handlers/python_xref", +test = [ + "beautifulsoup4>=4.13.3", + "pytest>=8.3.4", ] - -[tool.hatch.build.targets.wheel] -packages = [ - "src/mkdocstrings_handlers", - "src/mkdocstrings_handlers/python_xref", +docs = [ + # Most of the docs dependencies are already a part of the project itself + "mike>=2.1.2", + "mkdocs-material>=9.5.30", ] -[tool.mypy] -check_untyped_defs = true -mypy_path = "src" -namespace_packages = true -explicit_package_bases = true -files = [ - "src/mkdocstrings_handlers", - "tests" - ] -show_error_codes = true -disallow_untyped_defs = true -disallow_incomplete_defs = true - -[[tool.mypy.overrides]] -module = [ - "bs4" +[tool.pytest.ini_options] +filterwarnings = [ + "error", ] -ignore_missing_imports = true + +[tool.basedpyright] +pythonPlatform = "All" +pythonVersion = "3.9" +typeCheckingMode = "all" + +# Diagnostic behavior settings +strictListInference = false +strictDictionaryInference = false +strictSetInference = false +analyzeUnannotatedFunctions = true +strictParameterNoneValue = true +enableTypeIgnoreComments = true +deprecateTypingAliases = true +enableExperimentalFeatures = false + +# Diagnostic rules +reportAny = false +reportExplicitAny = false +reportImplicitStringConcatenation = false +reportUnreachable = "hint" +reportUnusedParameter = "hint" +reportUnannotatedClassAttribute = false +reportUnknownArgumentType = false # consider enabling +reportUnknownVariableType = false # consider enabling +reportUnknownMemberType = false # consider enabling +reportUnknownParameterType = false # consider enabling +reportUnknownLambdaType = false # consider enabling +reportMissingTypeStubs = "information" # consider bumping to warning/error +reportUninitializedInstanceVariable = false # until https://github.com/DetachHead/basedpyright/issues/491 +reportMissingParameterType = false # ruff's flake8-annotations (ANN) already covers this + gives us more control + +[tool.ruff] +target-version = "py39" +line-length = 119 [tool.ruff.lint] -# TODO add "I" (isort) -# TODO add "RUF" -# TODO add "ARG" -# TODO add "SIM" -# TODO add "S" (bandit) -# TODO add "PT" (pytest style) -# TODO add "CPY101" -select = ["E", "F", "PL", "D", "R", "T10", "EXE"] +select = ["ALL"] + ignore = [ - "D105", # missing doc string for dunder methods - "D200", # one line doc string - "D202", # blank lines after function docstring - "D205", # doc on first line - "D212", # doc on first line - "D410", # blank line after doc section - "D411", # blank line before doc section - "D412", # no blank lines after section header - "D415", # doc title punctuation - "E501", # line too long - "PLC0105", # covariant metatype names - "PLR0913", # too-many-argument - "PLR2004", # magic value - "PT001", # allow @pytest.fixture without parens - "RET504", # unnecessary assignment to variable before return - "S101", # use of assert - do we care? - # TODO: fix the ones below - "D403", # capitalize first word of doc string - "D102", # undocumented public method - "D104", # missing docstring in public package - "D107", # __init__ docstring - "D417", # missing argument description -] -preview = true -explicit-preview-rules = true # only preview explicitly selected rules (E.g. CPY001) - -[tool.ruff.lint.per-file-ignores] -# Ignore some issues in tests -"tests/**" = [ - "F401", # unused import (pytest fixture) - "F403", # wildcard import (for fixtures) - "F405", # defined from star imports (typicallky a pytest fixture) - "F811", # redefinition of unused (typically a pytest fixture) - "PLR0912", # too many branches - "PLR0915", # too many statements - "S", # bandit rules + "C90", # mccabe + "FBT", # flake8-boolean-trap + "CPY", # flake8-copyright + "EM", # flake8-errmsg + "SLF", # flake8-self + "ARG", # flake8-unused-arguments + "TD", # flake8-todos + "FIX", # flake8-fixme + + "D100", # Missing docstring in public module + "D104", # Missing docstring in public package + "D105", # Missing docstring in magic method + "D107", # Missing docstring in __init__ + "D203", # Blank line required before class docstring + "D213", # Multi-line summary should start at second line (incompatible with D212) + "D301", # Use r""" if any backslashes in a docstring + "D405", # Section name should be properly capitalized + "D406", # Section name should end with a newline + "D407", # Missing dashed underline after section + "D408", # Section underline should be in the line following the section's name + "D409", # Section underline should match the length of its name + "D410", # Missing blank line after section + "D411", # Missing blank line before section + "D412", # No blank lines allowed between a section header and its content + "D413", # Missing blank line after last section + "D414", # Section has no content + "D416", # Section name should end with a colon + "D417", # Missing argument descrition in the docstring + + "ANN002", # Missing type annotation for *args + "ANN003", # Missing type annotation for **kwargs + "ANN204", # Missing return type annotation for special method + "ANN401", # Dynamically typed expressions (typing.Any) disallowed + + "SIM102", # use a single if statement instead of nested if statements + "SIM108", # Use ternary operator {contents} instead of if-else-block + + "TC001", # Move application imports used only for annotations into a type-checking block + "TC002", # Move 3rd-party imports used only for annotations into a type-checking block + "TC003", # Move standard library imports used only for annotations into a type-checking block + + "TD002", # Missing author in TODO + "TD003", # Missing issue link on the line following this TODO + + "TRY003", # No f-strings in raise statements + "EM101", # No string literals in exception init + "EM102", # No f-strings in exception init + "PLR2004", # Using unnamed numerical constants + "PGH003", # Using specific rule codes in type ignores + "E731", # Don't assign a lambda expression, use a def ] -[tool.ruff.lint.pylint] -#max-locals = 30 -max-branches = 15 -#max-attributes = 30 +[tool.ruff.lint.extend-per-file-ignores] +"tests/*" = [ + "ANN", # flake8-annotations + "S101", # Use of assert +] [tool.ruff.lint.pydocstyle] convention = "google" -[too.ruff.format] -skip-magic-trailing-comma = false +[tool.ruff.lint.pylint] +max-args = 20 +max-branches = 20 +max-returns = 20 +max-statements = 250 + +[tool.ruff.format] line-ending = "lf" -[tool.pylint.main] -jobs = 0 -# Minimum Python version to use for version dependent checks. -py-version = "3.8" - -[tool.pylint.format] -max-line-length = 110 -max-module-lines = 2000 - -[tool.pylint."messages control"] -disable = [ - "bad-inline-option", - "c-extension-no-member", - "consider-using-from-import", - "deprecated-pragma", - "disallowed-name", - "file-ignored", - "fixme", - "import-outside-toplevel", - "invalid-characters-in-docstring", - "invalid-name", - "locally-disabled", - "multiple-statements", - "no-else-return", - "raw-checker-failed", - "superfluous-parens", - "suppressed-message", - "too-few-public-methods", - "too-many-arguments", - "too-many-branches", - "too-many-instance-attributes", - "too-many-locals", - "too-many-public-methods", - "too-many-statements", - "trailing-newlines", - "trailing-whitespace", - "unspecified-encoding", - "unused-wildcard-import", - "use-dict-literal", - "use-symbolic-message-instead", - "useless-suppression", - "wrong-import-order", - "wrong-import-position", - "wrong-spelling-in-comment", - "wrong-spelling-in-docstring", -] +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +only-include = ["src/mkdocstrings_handlers"] +sources = ["src"] diff --git a/src/mkdocstrings_handlers/python_betterrefs/__init__.py b/src/mkdocstrings_handlers/python_betterrefs/__init__.py new file mode 100644 index 0000000..cbc8211 --- /dev/null +++ b/src/mkdocstrings_handlers/python_betterrefs/__init__.py @@ -0,0 +1,48 @@ +"""Expose the extended mkdocstrings python handler.""" + +from collections.abc import MutableMapping +from pathlib import Path +from typing import Any +from warnings import warn + +from mkdocs.config.defaults import MkDocsConfig + +from mkdocstrings_handlers.python.config import PythonConfig + +from .handler import PythonBetterRefsHandler + +__all__ = ["get_handler"] + + +def get_handler( + handler_config: MutableMapping[str, Any], + tool_config: MkDocsConfig, + **kwargs: Any, +) -> PythonBetterRefsHandler: + """Return an instance of PythonBetterRefsHandler handler. + + This function essentially mimics the same function from mkdocstrings-python, + just returning our extended handler instead of the Python one. + + Arguments: + handler_config: The handler configuration. + tool_config: The tool (SSG) configuration. + + Returns: + An instance of `PythonHandler`. + """ + base_dir = Path(tool_config.config_file_path or "./mkdocs.yml").parent + if "inventories" not in handler_config and "import" in handler_config: + warn("The 'import' key is renamed 'inventories' for the Python handler", FutureWarning, stacklevel=1) + handler_config["inventories"] = handler_config.pop("import", []) + + # PythonConfig will actually store all of the options in a dict without doing any + # checking during this initialization. That means we can just re-use it here, our + # custom options will be stored and we can handle them from __init__. + config = PythonConfig.from_data(**handler_config) + + return PythonBetterRefsHandler( + config=config, + base_dir=base_dir, + **kwargs, + ) diff --git a/src/mkdocstrings_handlers/python_betterrefs/config.py b/src/mkdocstrings_handlers/python_betterrefs/config.py new file mode 100644 index 0000000..5b49c84 --- /dev/null +++ b/src/mkdocstrings_handlers/python_betterrefs/config.py @@ -0,0 +1,58 @@ +from collections.abc import Mapping +from dataclasses import dataclass, fields +from typing import Annotated, Any + +from typing_extensions import Self + +from mkdocstrings_handlers.python.config import ( + Field, + PythonOptions, + _dataclass_options, # pyright: ignore[reportPrivateUsage] +) + + +@dataclass(**_dataclass_options) +class PythonBetterRefsOptions(PythonOptions): + """Accepted input options.""" + + better_crossrefs: Annotated[ + bool, + Field(group="docstrings", description="Whether to enable better crossrefs syntax."), + ] = True + + check_crossrefs: Annotated[ + bool, + Field( + group="docstrings", + description="""Whether to produce improved warnings for invalid cross-references. + + This will only take effect if `better_crossrefs` is also enabled. + """, + ), + ] = True + + @staticmethod + def extract_betterrefs(data: Mapping[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]: + """Extract the options specific to better-refs, leaving the rest untouched. + + Returns: + A tuple of (better-refs specific options, remaining options). + """ + better_refs_fields = {"better_crossrefs", "check_crossrefs"} + copy = dict(data) + return {name: copy.pop(name) for name in data if name in better_refs_fields}, copy + + @classmethod + def merge_python(cls, python_opts: PythonOptions, extra_data: Mapping[str, Any]) -> Self: + """Create a better-refs options from an existing PythonOptions instance and the extra data. + + Note that the passed extra_data is expected to already be coerced. + """ + if type(python_opts) is not PythonOptions: + raise TypeError("This function can only work directly with PythonOptions instances, not any descendents.") + + orig_fields = fields(PythonOptions) # pyright: ignore[reportArgumentType] # PythonOptions is a dataclass + field_names = {field.name for field in orig_fields} + orig_data = {name: getattr(python_opts, name) for name in field_names} + + return cls(**orig_data, **extra_data) diff --git a/src/mkdocstrings_handlers/python_xref/crossref.py b/src/mkdocstrings_handlers/python_betterrefs/crossref.py similarity index 84% rename from src/mkdocstrings_handlers/python_xref/crossref.py rename to src/mkdocstrings_handlers/python_betterrefs/crossref.py index 1035d4e..c298ed7 100644 --- a/src/mkdocstrings_handlers/python_xref/crossref.py +++ b/src/mkdocstrings_handlers/python_betterrefs/crossref.py @@ -1,32 +1,19 @@ -# Copyright (c) 2022-2024. Analog Devices Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. """Support for translating compact relative crossreferences in docstrings.""" from __future__ import annotations import re -from typing import Callable, List, Optional, cast +from collections.abc import Callable +from typing import cast from griffe import Docstring, Object from mkdocstrings.loggers import get_logger -__all__ = [ - "substitute_relative_crossrefs" -] +__all__ = ["substitute_relative_crossrefs"] logger = get_logger(__name__) + def _re_or(*exps: str) -> str: """Construct an "or" regular expression from a sequence of regular expressions. @@ -53,6 +40,7 @@ def _re_named(name: str, exp: str, optional: bool = False) -> str: optchar = "?" if optional else "" return f"(?P<{name}>{exp}){optchar}" + _RE_CROSSREF = re.compile(r"\[([^\[\]]+?)\]\[([^\[\]]*?)\]") """Regular expression that matches general cross-references.""" @@ -75,7 +63,7 @@ def _re_named(name: str, exp: str, optional: bool = False) -> str: ), optional=True, ) - + _re_named("relname", r"(?:[a-zA-Z_][a-zA-Z0-9_\.]*)?") + + _re_named("relname", r"(?:[a-zA-Z_][a-zA-Z0-9_\.]*)?"), ) """Regular expression that matches a relative path reference. @@ -101,22 +89,21 @@ def _always_ok(_ref: str) -> bool: class _RelativeCrossrefProcessor: - """ - A callable object that can substitute relative cross-reference expressions. + """A callable object that can substitute relative cross-reference expressions. This is intended to be used as a substitution function by `re.sub` to process relative cross-references in a doc-string. """ _doc: Docstring - _cur_match: re.Match | None + _cur_match: re.Match[str] | None _cur_input: str _cur_offset: int - _cur_ref_parts: List[str] + _cur_ref_parts: list[str] _ok: bool _check_ref: Callable[[str], bool] - def __init__(self, doc: Docstring, checkref: Optional[Callable[[str], bool]] = None): + def __init__(self, doc: Docstring, checkref: Callable[[str], bool] | None = None): self._doc = doc self._cur_match = None self._cur_input = "" @@ -125,9 +112,8 @@ def __init__(self, doc: Docstring, checkref: Optional[Callable[[str], bool]] = N self._check_ref = checkref or _always_ok self._ok = True - def __call__(self, match: re.Match) -> str: - """ - Process a cross-reference expression. + def __call__(self, match: re.Match[str]) -> str: + """Process a cross-reference expression. This should be called with a match from the _RE_CROSSREF expression which matches expression of the form [<title>][<ref>]. @@ -161,14 +147,17 @@ def __call__(self, match: re.Match) -> str: self._process_append_from_title(ref_match, title) if self._ok: - new_ref = '.'.join(self._cur_ref_parts) + new_ref = ".".join(self._cur_ref_parts) logger.debug( "cross-reference substitution\nin %s:\n[%s][%s] -> [...][%s]", - cast(Object, self._doc.parent).canonical_path, title, ref, new_ref + cast(Object, self._doc.parent).canonical_path, + title, + ref, + new_ref, ) # builtin names get handled specially somehow, so don't check here - if new_ref not in __builtins__ and not checkref(new_ref): # type: ignore[operator] + if new_ref not in __builtins__ and not checkref(new_ref): self._error(f"Cannot load reference '{new_ref}'") if new_ref: @@ -178,19 +167,19 @@ def __call__(self, match: re.Match) -> str: return result - def _start_match(self, match: re.Match) -> None: + def _start_match(self, match: re.Match[str]) -> None: self._cur_match = match self._cur_offset = match.start(0) self._cur_input = match[0] self._ok = True self._cur_ref_parts.clear() - def _process_relname(self, ref_match: re.Match) -> None: + def _process_relname(self, ref_match: re.Match[str]) -> None: relname = ref_match.group("relname").strip(".") if relname: self._cur_ref_parts.append(relname) - def _process_append_from_title(self, ref_match: re.Match, title_text: str) -> None: + def _process_append_from_title(self, ref_match: re.Match[str], title_text: str) -> None: if ref_match.group(0).endswith("."): id_from_title = title_text.strip("`*") if not _RE_ID.fullmatch(id_from_title): @@ -198,7 +187,7 @@ def _process_append_from_title(self, ref_match: re.Match, title_text: str) -> No return self._cur_ref_parts.append(id_from_title) - def _process_parent_specifier(self, ref_match: re.Match) -> None: + def _process_parent_specifier(self, ref_match: re.Match[str]) -> None: if not ref_match.group("parent"): return @@ -218,19 +207,16 @@ def _process_parent_specifier(self, ref_match: re.Match) -> None: if rel_obj is not None and self._ok: self._cur_ref_parts.append(rel_obj.canonical_path) - def _process_current_specifier(self, obj: Object, ref_match: re.Match) -> Optional[Object]: + def _process_current_specifier(self, obj: Object, ref_match: re.Match[str]) -> Object | None: rel_obj: Object | None = None if ref_match.group("current"): if obj.is_function: - self._error( - f"Cannot use '.' in function {obj.canonical_path}", - just_warn=False - ) + self._error(f"Cannot use '.' in function {obj.canonical_path}", just_warn=False) else: rel_obj = obj return rel_obj - def _process_class_specifier(self, obj: Object, ref_match: re.Match) -> Optional[Object]: + def _process_class_specifier(self, obj: Object, ref_match: re.Match[str]) -> Object | None: rel_obj: Object | None = None if ref_match.group("class"): rel_obj = obj @@ -241,7 +227,7 @@ def _process_class_specifier(self, obj: Object, ref_match: re.Match) -> Optional break return rel_obj - def _process_module_specifier(self, obj: Object, ref_match: re.Match) -> Optional[Object]: + def _process_module_specifier(self, obj: Object, ref_match: re.Match[str]) -> Object | None: rel_obj: Object | None = None if ref_match.group("module"): rel_obj = obj @@ -252,7 +238,7 @@ def _process_module_specifier(self, obj: Object, ref_match: re.Match) -> Optiona break return rel_obj - def _process_package_specifier(self, obj: Object, ref_match: re.Match) -> Optional[Object]: + def _process_package_specifier(self, obj: Object, ref_match: re.Match[str]) -> Object | None: # griffe does not distinguish between modules and packages, so we identify a package # as a module that contains other modules. A module that has no parent is considered to # be a package even if it does not contain modules. @@ -275,7 +261,7 @@ def _process_package_specifier(self, obj: Object, ref_match: re.Match) -> Option return rel_obj - def _process_up_specifier(self, obj: Object, ref_match: re.Match) -> Optional[Object]: + def _process_up_specifier(self, obj: Object, ref_match: re.Match[str]) -> Object | None: rel_obj: Object | None = None if ref_match.group("up"): level = len(ref_match.group("up")) @@ -313,12 +299,12 @@ def _error(self, msg: str, just_warn: bool = False) -> None: # that without knowing how much the doc string was unindented. prefix += " \n" - logger.warning(prefix + msg) + logger.warning(prefix + msg) # noqa: G003 self._ok = just_warn -def substitute_relative_crossrefs(obj: Object, checkref: Optional[Callable[[str], bool]] = None) -> None: +def substitute_relative_crossrefs(obj: Object, checkref: Callable[[str], bool] | None = None) -> None: """Recursively expand relative cross-references in all docstrings in tree. Arguments: diff --git a/src/mkdocstrings_handlers/python_betterrefs/handler.py b/src/mkdocstrings_handlers/python_betterrefs/handler.py new file mode 100644 index 0000000..5c14bfb --- /dev/null +++ b/src/mkdocstrings_handlers/python_betterrefs/handler.py @@ -0,0 +1,85 @@ +"""Implementation of python_betterrefs handler.""" + +from __future__ import annotations + +from collections.abc import Mapping +from dataclasses import replace +from pathlib import Path +from typing import Any, ClassVar + +from mkdocstrings.handlers.base import CollectionError, CollectorItem, HandlerOptions +from mkdocstrings.loggers import get_logger +from typing_extensions import override + +from mkdocstrings_handlers.python.config import PythonConfig, PythonOptions +from mkdocstrings_handlers.python.handler import PythonHandler +from mkdocstrings_handlers.python_betterrefs.config import PythonBetterRefsOptions + +from .crossref import substitute_relative_crossrefs + +__all__ = ["PythonBetterRefsHandler"] + +logger = get_logger(__name__) + + +class PythonBetterRefsHandler(PythonHandler): + """Extended version of mkdocstrings Python handler. + + * Converts custom cross-reference syntax into full (absolute) references. + * Checks cross-references early in order to produce errors with source location. + """ + + name: ClassVar[str] = "python_betterrefs" + """The handler's name.""" + + @override + def __init__(self, config: PythonConfig, base_dir: Path, **kwargs: Any) -> None: + # Extract our custom options before thy're passed into PythonHandler init, + # preventing complains about unknown options + self.better_refs_opts, remaining_opts = PythonBetterRefsOptions.extract_betterrefs(config.options) + config = replace(config, options=remaining_opts) # pyright: ignore[reportArgumentType] # PythonConfig is a dataclass + + super().__init__(config, base_dir, **kwargs) + + @override + def get_options(self, local_options: Mapping[str, Any]) -> HandlerOptions: + local_better_refs_opts, local_remaining_opts = PythonBetterRefsOptions.extract_betterrefs(local_options) + python_opts: PythonOptions = super().get_options(local_remaining_opts) + + better_refs_opts = self.better_refs_opts.copy() + better_refs_opts.update(local_better_refs_opts) + + return PythonBetterRefsOptions.merge_python(python_opts, better_refs_opts) + + @override + def render( # pyright: ignore[reportIncompatibleMethodOverride] # we use our type for options + self, + data: CollectorItem, + options: PythonBetterRefsOptions, + ) -> str: + if options.better_crossrefs: + checkref = self._check_ref if options.check_crossrefs else None + substitute_relative_crossrefs(data, checkref=checkref) + + try: + return super().render(data, options) + except Exception: # pragma: no cover + print(f"{data.path=}") # noqa: T201 + raise + + @override + def get_templates_dir(self, handler: str | None = None) -> Path: + # Return the python handler templates dir + if handler == self.name: + handler = "python" + return super().get_templates_dir(handler) + + def _check_ref(self, ref: str) -> bool: + """Check for existence of reference.""" + # Try to collect the reference normally and see if it fails + try: + self.collect(ref, PythonOptions()) + except CollectionError: + return False + else: + return True diff --git a/src/mkdocstrings_handlers/python_xref/py.typed b/src/mkdocstrings_handlers/python_betterrefs/py.typed similarity index 100% rename from src/mkdocstrings_handlers/python_xref/py.typed rename to src/mkdocstrings_handlers/python_betterrefs/py.typed diff --git a/src/mkdocstrings_handlers/python_xref/VERSION b/src/mkdocstrings_handlers/python_xref/VERSION deleted file mode 100644 index fdd3be6..0000000 --- a/src/mkdocstrings_handlers/python_xref/VERSION +++ /dev/null @@ -1 +0,0 @@ -1.6.2 diff --git a/src/mkdocstrings_handlers/python_xref/__init__.py b/src/mkdocstrings_handlers/python_xref/__init__.py deleted file mode 100644 index 56a5170..0000000 --- a/src/mkdocstrings_handlers/python_xref/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) 2022-2023. Analog Devices Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Extended mkdocstrings python handler -""" - -from .handler import PythonRelXRefHandler - -__all__ = ["get_handler"] - -get_handler = PythonRelXRefHandler diff --git a/src/mkdocstrings_handlers/python_xref/handler.py b/src/mkdocstrings_handlers/python_xref/handler.py deleted file mode 100644 index 5128e2f..0000000 --- a/src/mkdocstrings_handlers/python_xref/handler.py +++ /dev/null @@ -1,95 +0,0 @@ -# Copyright (c) 2022=2023. Analog Devices Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Implementation of python_xref handler -""" - -from __future__ import annotations - -from collections import ChainMap -from pathlib import Path -from typing import Any, List, Mapping, Optional - -from griffe import Object -from mkdocstrings.loggers import get_logger -from mkdocstrings_handlers.python.handler import PythonHandler - -from .crossref import substitute_relative_crossrefs - -__all__ = [ - 'PythonRelXRefHandler' -] - -logger = get_logger(__name__) - -class PythonRelXRefHandler(PythonHandler): - """Extended version of mkdocstrings Python handler - - * Converts relative cross-references into full references - * Checks cross-references early in order to produce errors with source location - """ - - handler_name: str = __name__.rsplit('.', 2)[1] - - default_config = dict( - PythonHandler.default_config, - relative_crossrefs = False, - check_crossrefs = True, - ) - - def __init__(self, - theme: str, - custom_templates: Optional[str] = None, - config_file_path: Optional[str] = None, - paths: Optional[List[str]] = None, - locale: str = "en", - **_config: Any, - ): - super().__init__( - handler = self.handler_name, - theme = theme, - custom_templates = custom_templates, - config_file_path = config_file_path, - paths = paths, - locale=locale, - ) - - def render(self, data: Object, config: Mapping[str,Any]) -> str: - final_config = ChainMap(config, self.default_config) # type: ignore[arg-type] - - if final_config["relative_crossrefs"]: - checkref = self._check_ref if final_config["check_crossrefs"] else None - substitute_relative_crossrefs(data, checkref=checkref) - - try: - return super().render(data, config) - except Exception: # pragma: no cover - print(f"{data.path=}") - raise - - def get_templates_dir(self, handler: Optional[str] = None) -> Path: - """See [render][.barf]""" - if handler == self.handler_name: - handler = 'python' - return super().get_templates_dir(handler) - - def _check_ref(self, ref:str) -> bool: - """Check for existence of reference""" - try: - self.collect(ref, {}) - return True - except Exception: # pylint: disable=broad-except - # Only expect a CollectionError but we may as well catch everything. - return False - diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..5c39f54 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,9 @@ +from pathlib import Path + +import pytest + + +@pytest.fixture +def test_project() -> Path: + """Fixture to obtain a directory with a very basic mkdocs project.""" + return Path(__file__).parent / "project" diff --git a/tests/project/README.md b/tests/project/README.md new file mode 100644 index 0000000..80108c2 --- /dev/null +++ b/tests/project/README.md @@ -0,0 +1,4 @@ +# Test project + +This is a simple project used for unit-testing purposes. It uses this custom handler and contains code that tests +various aspects of the handler. diff --git a/tests/project/mkdocs.yml b/tests/project/mkdocs.yml index f3031d2..5ed899f 100644 --- a/tests/project/mkdocs.yml +++ b/tests/project/mkdocs.yml @@ -6,57 +6,26 @@ watch: - src/myproj nav: -- Home: index.md -- API: - - myproj.foo: foo.md - - myproj.bar: bar.md - - myproj.pkg: pkg.md - - myproj.baz: pkg-baz.md - -theme: - name: material - features: - - navigation.tabs - - navigation.tabs.sticky - - navigation.top - palette: - - media: "(prefers-color-scheme: light)" - scheme: default - primary: white - accent: purple - toggle: - icon: material/weather-sunny - name: Switch to dark mode - - media: "(prefers-color-scheme: dark)" - scheme: slate - primary: black - accent: lime - toggle: - icon: material/weather-night - name: Switch to light mode - -markdown_extensions: -- admonition -- pymdownx.snippets: - check_paths: true -- pymdownx.superfences -- pymdownx.tabbed: - alternate_style: true + - Home: index.md + - API: + - myproj.foo: foo.md + - myproj.bar: bar.md + - myproj.pkg: pkg.md + - myproj.baz: pkg-baz.md plugins: -- search -- mkdocstrings: - default_handler: python_xref - handlers: - python_xref: - paths: [src] - options: - docstring_style: google - docstring_options: - ignore_init_summary: yes - merge_init_into_class: yes - separate_signature: yes - show_source: no - show_root_full_path: no - relative_crossrefs: yes - + - mkdocstrings: + default_handler: python_betterrefs + handlers: + python_betterrefs: + paths: [src] + options: + docstring_style: google + docstring_options: + ignore_init_summary: true + merge_init_into_class: true + separate_signature: true + show_source: false + show_root_full_path: false + better_crossrefs: true + check_crossrefs: true diff --git a/tests/project/src/myproj/__init__.py b/tests/project/src/myproj/__init__.py index 411c17c..e69de29 100644 --- a/tests/project/src/myproj/__init__.py +++ b/tests/project/src/myproj/__init__.py @@ -1,13 +0,0 @@ -# Copyright (c) 2022. Analog Devices Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/tests/project/src/myproj/bar.py b/tests/project/src/myproj/bar.py index fc75c9d..6b9c071 100644 --- a/tests/project/src/myproj/bar.py +++ b/tests/project/src/myproj/bar.py @@ -1,35 +1,26 @@ -# Copyright (c) 2022. Analog Devices Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. """This is another module. This is a [bad][.] reference. """ +from typing_extensions import override + from .foo import Foo + class Bar(Foo): """See [bar][.] method.""" def bar(self) -> None: """This is in the [Bar][(c)] class. + Also see the [foo][^.] method and the [func][(m).] function. """ + @override def foo(self) -> None: - """Overrides [Foo.foo][^^^.foo.]""" + """Overrides [Foo.foo][^^^.foo.].""" def func() -> None: """This is a function in the [bar][(m)] module.""" - diff --git a/tests/project/src/myproj/foo.py b/tests/project/src/myproj/foo.py index 74bef28..1118ef3 100644 --- a/tests/project/src/myproj/foo.py +++ b/tests/project/src/myproj/foo.py @@ -1,22 +1,8 @@ -# Copyright (c) 2022. Analog Devices Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -This is a test module. -""" +"""This is a test module.""" + class Foo: - """This is a base class""" + """This is a base class.""" def foo(self) -> None: - """a method""" + """A method.""" diff --git a/tests/project/src/myproj/pkg/__init__.py b/tests/project/src/myproj/pkg/__init__.py index 9e30385..4b31f54 100644 --- a/tests/project/src/myproj/pkg/__init__.py +++ b/tests/project/src/myproj/pkg/__init__.py @@ -1,21 +1,5 @@ -# Copyright (c) 2022. Analog Devices Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -A module -""" +"""A module.""" + def func() -> None: - """ - A function - """ + """A function.""" diff --git a/tests/project/src/myproj/pkg/baz.py b/tests/project/src/myproj/pkg/baz.py index 1ac5406..210c854 100644 --- a/tests/project/src/myproj/pkg/baz.py +++ b/tests/project/src/myproj/pkg/baz.py @@ -1,16 +1 @@ -# Copyright (c) 2022. Analog Devices Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -See [func][(p).] -""" +"""See [func][(p).].""" diff --git a/tests/test_crossref.py b/tests/test_crossref.py index 48e102d..71304d3 100644 --- a/tests/test_crossref.py +++ b/tests/test_crossref.py @@ -1,16 +1,3 @@ -# Copyright (c) 2022-2024. Analog Devices Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. """Unit tests for relative crossref expansion.""" from __future__ import annotations @@ -18,21 +5,22 @@ import inspect import logging import re +from collections.abc import Callable from pathlib import Path -from typing import Callable, Optional import pytest from griffe import Class, Docstring, Function, Module, Object # noinspection PyProtectedMember -from mkdocstrings_handlers.python_xref.crossref import ( - _RE_CROSSREF, - _RE_REL_CROSSREF, - _RelativeCrossrefProcessor, +from mkdocstrings_handlers.python_betterrefs.crossref import ( + _RE_CROSSREF, # pyright: ignore[reportPrivateUsage] + _RE_REL_CROSSREF, # pyright: ignore[reportPrivateUsage] + _RelativeCrossrefProcessor, # pyright: ignore[reportPrivateUsage] substitute_relative_crossrefs, ) -def test_RelativeCrossrefProcessor(caplog: pytest.LogCaptureFixture) -> None: + +def test_RelativeCrossrefProcessor(caplog: pytest.LogCaptureFixture) -> None: # noqa: N802 """Unit test for internal _RelativeCrossrefProcessor class. Arguments: @@ -46,14 +34,17 @@ def test_RelativeCrossrefProcessor(caplog: pytest.LogCaptureFixture) -> None: meth1 = Function(name="meth1", parent=cls1) cls1.members.update(meth1=meth1) - def assert_sub(parent: Object, title: str, ref: str, - expected: str = "", - *, - warning: str = "", - relative: bool = True, - checkref: Optional[Callable[[str],bool]] = None - ) -> None: - """Tests a relative crossref substitution + def assert_sub( + parent: Object, + title: str, + ref: str, + expected: str = "", + *, + warning: str = "", + relative: bool = True, + checkref: Callable[[str], bool] | None = None, + ) -> None: + """Tests a relative crossref substitution. Arguments: parent: assumed parent object for docstring @@ -98,8 +89,8 @@ def assert_sub(parent: Object, title: str, ref: str, assert_sub(meth1, "foo", "(c).baz.", "mod1.mod2.Class1.baz.foo") assert_sub(meth1, "foo", "(m).", "mod1.mod2.foo") assert_sub(meth1, "foo", "mod3.", "mod3.foo") - assert_sub(meth1, "foo", "^^.", "mod1.mod2.foo", checkref = lambda x: True) - assert_sub(meth1, "foo", "...", "mod1.mod2.foo", checkref = lambda x: True) + assert_sub(meth1, "foo", "^^.", "mod1.mod2.foo", checkref=lambda x: True) + assert_sub(meth1, "foo", "...", "mod1.mod2.foo", checkref=lambda x: True) assert_sub(meth1, "Class1", "(p).mod2.", "mod1.mod2.Class1") assert_sub(mod1, "Class1", "(P).mod2.Class1", "mod1.mod2.Class1") @@ -110,8 +101,7 @@ def assert_nocheck(val: str) -> bool: return False assert_sub(cls1, "foo", "?.", "mod1.mod2.Class1.foo", checkref=assert_nocheck) - assert_sub(cls1, "foo", "?mod1.mod2.Class1.foo", "mod1.mod2.Class1.foo", - checkref=assert_nocheck, relative=False) + assert_sub(cls1, "foo", "?mod1.mod2.Class1.foo", "mod1.mod2.Class1.foo", checkref=assert_nocheck, relative=False) # Error cases @@ -121,13 +111,23 @@ def assert_nocheck(val: str) -> bool: assert_sub(meth1, "bad id", "..", warning="not a qualified identifier") assert_sub(mod2, "foo", "(c)", warning="not in a class") assert_sub(meth1, "foo", "^^^^", warning="too many levels") - assert_sub(meth1, "foo", "..", "mod1.mod2.Class1.foo", - warning = "Cannot load reference 'mod1.mod2.Class1.foo'", - checkref=lambda x: False) - assert_sub(meth1, "foo", "mod1.mod2.Class1.foo", "mod1.mod2.Class1.foo", - warning = "Cannot load reference 'mod1.mod2.Class1.foo'", - relative=False, - checkref=lambda x: False) + assert_sub( + meth1, + "foo", + "..", + "mod1.mod2.Class1.foo", + warning="Cannot load reference 'mod1.mod2.Class1.foo'", + checkref=lambda x: False, + ) + assert_sub( + meth1, + "foo", + "mod1.mod2.Class1.foo", + "mod1.mod2.Class1.foo", + warning="Cannot load reference 'mod1.mod2.Class1.foo'", + relative=False, + checkref=lambda x: False, + ) def test_substitute_relative_crossrefs(caplog: pytest.LogCaptureFixture) -> None: @@ -169,7 +169,7 @@ def test_substitute_relative_crossrefs(caplog: pytest.LogCaptureFixture) -> None """ [foo][mod1.mod2.Class1.foo] [bar][mod1.mod2.bar] - """ + """, ) assert len(caplog.records) == 0 diff --git a/tests/test_handler.py b/tests/test_handler.py index bd61bf4..3a61e97 100644 --- a/tests/test_handler.py +++ b/tests/test_handler.py @@ -1,127 +1,329 @@ -# Copyright (c) 2022-2024. Analog Devices Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Unit test for mkdocstrings_handlers.python_xref.handler module""" +"""Unit test for mkdocstrings_handlers.python_betterrefs.handler module.""" from __future__ import annotations -import logging -import os -from os import PathLike from pathlib import Path from typing import Any import pytest +from griffe import Docstring, Module, Object +from mkdocstrings.handlers.base import CollectorItem -from griffe import Docstring, Object, Module -from mkdocstrings.handlers.base import CollectionError +from mkdocstrings_handlers.python.config import PythonConfig, PythonOptions from mkdocstrings_handlers.python.handler import PythonHandler -from mkdocstrings_handlers.python_xref.handler import PythonRelXRefHandler +from mkdocstrings_handlers.python_betterrefs.config import PythonBetterRefsOptions +from mkdocstrings_handlers.python_betterrefs.handler import PythonBetterRefsHandler -def test_handler(tmpdir: PathLike, - monkeypatch: pytest.MonkeyPatch, - caplog: pytest.LogCaptureFixture) -> None: - """Unit test for GarpyPythonHandler class - This is a minimal whitebox test that just checks whether PythonHandler class has been - overridden correctly. A separate test should do doc generation and check the results. +def test_init(test_project: Path) -> None: + """Test the initialization of the custom handler. + + This is primarily to make sure that the inheritance works. """ + handler = PythonBetterRefsHandler( + config=PythonConfig.from_data( + options={"better_crossrefs": True, "check_crossrefs": False, "show_source": True}, + ), + base_dir=test_project, + theme="material", + mdx=[], + mdx_config={}, + ) + + # Custom name for our handler + assert handler.name == "python_betterrefs" + + # Template dirs should be redirected to python handler ones + assert handler.get_templates_dir(handler.name) == handler.get_templates_dir("python") - config_file = os.path.join(tmpdir, 'mkdocs.yml') - os.mkdir(os.path.join(tmpdir, 'path1')) - os.mkdir(os.path.join(tmpdir, 'path2')) - os.makedirs(os.path.join(tmpdir, 'custom_templates', 'python')) - # - # Test construction - # +@pytest.mark.parametrize( + ("input_opts", "expected_global_opts", "expected_betterrefs_opts"), + [ + pytest.param( + {}, + {}, + {}, + id="empty-opts", + ), + pytest.param( + {"show_source": True, "docstring_style": "google"}, + {"show_source": True, "docstring_style": "google"}, + {}, + id="external-opts", + ), + pytest.param( + {"better_crossrefs": True, "check_crossrefs": True}, + {}, + {"better_crossrefs": True, "check_crossrefs": True}, + id="internal-opts", + ), + pytest.param( + {"show_source": True, "docstring_style": "google", "better_crossrefs": True, "check_crossrefs": True}, + {"show_source": True, "docstring_style": "google"}, + {"better_crossrefs": True, "check_crossrefs": True}, + id="mix", + ), + ], +) +def test_options( + test_project: Path, + input_opts: dict[str, Any], + expected_global_opts: dict[str, Any], + expected_betterrefs_opts: dict[str, Any], +) -> None: + """Test whether the options are parsed successfully. - handler = PythonRelXRefHandler( - 'material', - config_file_path = config_file, - custom_templates = 'custom_templates', - paths = ['path1', 'path2'] + This makes sure the custom options for this handler aren't being propagated to + the original handler, where they'd cause issues and are instead correctly extracted. + """ + handler = PythonBetterRefsHandler( + config=PythonConfig.from_data(options=input_opts), + base_dir=test_project, + theme="material", + mdx=[], + mdx_config={}, ) - assert handler.handler_name == 'python_xref' - # NOTE: these could break if PythonHandler changes - # pylint: disable=protected-access - assert handler.handler_name == 'python_xref' - assert handler._config_file_path == config_file - assert os.path.join(tmpdir, 'path1') in handler._paths - assert os.path.join(tmpdir, 'path2') in handler._paths + assert handler.global_options == expected_global_opts + assert handler.better_refs_opts == expected_betterrefs_opts + - # - # Test get_templates_dir() redirection - # +@pytest.mark.parametrize( + ("input_opts", "local_opts", "expected_merged_opts"), + [ + pytest.param( + { + "better_crossrefs": True, + "check_crossrefs": False, + "show_source": True, + }, + {}, + PythonBetterRefsOptions.from_data( + better_crossrefs=True, + check_crossrefs=False, + show_source=True, + ), + id="only-global-opts", + ), + pytest.param( + { + "better_crossrefs": True, + "check_crossrefs": False, + "show_source": True, + }, + {"show_source": True}, + PythonBetterRefsOptions.from_data( + better_crossrefs=True, + check_crossrefs=False, + show_source=True, + ), + id="external-local-opt-unchanged", + ), + pytest.param( + { + "better_crossrefs": True, + "check_crossrefs": False, + "show_source": True, + }, + {"show_source": False}, + PythonBetterRefsOptions.from_data( + better_crossrefs=True, + check_crossrefs=False, + show_source=False, + ), + id="external-local-opt-changed", + ), + pytest.param( + { + "better_crossrefs": True, + "check_crossrefs": False, + "show_source": True, + }, + {"check_crossrefs": False}, + PythonBetterRefsOptions.from_data( + better_crossrefs=True, + check_crossrefs=False, + show_source=True, + ), + id="internal-local-opt-unchanged", + ), + pytest.param( + { + "better_crossrefs": True, + "check_crossrefs": False, + "show_source": True, + }, + {"check_crossrefs": True}, + PythonBetterRefsOptions.from_data( + better_crossrefs=True, + check_crossrefs=True, + show_source=True, + ), + id="internal-local-opt-changed", + ), + pytest.param( + { + "better_crossrefs": True, + "check_crossrefs": False, + "show_source": True, + }, + {"check_crossrefs": True, "show_source": True}, + PythonBetterRefsOptions.from_data( + better_crossrefs=True, + check_crossrefs=True, + show_source=True, + ), + id="mix", + ), + ], +) +def test_options_merging( + input_opts: dict[str, Any], + local_opts: dict[str, Any], + expected_merged_opts: PythonBetterRefsOptions, + test_project: Path, +) -> None: + """Test the logic for local opts merging was overwritten correctly.""" + handler = PythonBetterRefsHandler( + config=PythonConfig.from_data(options=input_opts), + base_dir=test_project, + theme="material", + mdx=[], + mdx_config={}, + ) - assert handler.get_templates_dir(handler.handler_name) == handler.get_templates_dir('python') + merged_opts = handler.get_options(local_opts) + assert type(merged_opts) is PythonBetterRefsOptions + assert merged_opts == expected_merged_opts - # - # Test render() - # - def fake_collect(_self: PythonHandler, identifier: str, _config: dict) -> Any: - if identifier.startswith('mod'): - return Object(identifier) - raise CollectionError(identifier) +@pytest.mark.parametrize( + ("griffe_obj", "options", "checkref_result", "expected_rendered", "expected_error"), + [ + pytest.param( + Module(name="mod", filepath=Path("mod."), docstring=Docstring("[foo][.foo]")), + {"better_crossrefs": False, "check_crossrefs": False}, + True, + "[foo][.foo]", + False, + id="rel-ref-but-disabled", + ), + pytest.param( + Module(name="mod", filepath=Path("mod."), docstring=Docstring("[foo][.foo]")), + {"better_crossrefs": True, "check_crossrefs": False}, + True, + "[foo][mod.foo]", + False, + id="rel-ref-enabled", + ), + pytest.param( + Module(name="mod", filepath=Path("mod."), docstring=Docstring("[bar][.]")), + {"better_crossrefs": True, "check_crossrefs": False}, + True, + "[bar][mod.bar]", + False, + id="rel-ref-enabled2", + ), + pytest.param( + Module(name="mod", filepath=Path("mod."), docstring=Docstring("[foo][mod.foo]")), + {"better_crossrefs": True, "check_crossrefs": True}, + True, + "[foo][mod.foo]", + False, + id="abs-ref-check-crossrefs-pass", + ), + pytest.param( + Module(name="mod", filepath=Path("mod."), docstring=Docstring("[foo][mod.foo]")), + {"better_crossrefs": True, "check_crossrefs": True}, + False, + "[foo][mod.foo]", + True, + id="abs-ref-check-crossrefs-fail", + ), + pytest.param( + Module(name="mod", filepath=Path("mod."), docstring=Docstring("[foo][.foo]")), + {"better_crossrefs": True, "check_crossrefs": True}, + True, + "[foo][mod.foo]", + False, + id="rel-ref-check-crossrefs-pass", + ), + pytest.param( + Module(name="mod", filepath=Path("mod."), docstring=Docstring("[foo][.foo]")), + {"better_crossrefs": True, "check_crossrefs": True}, + False, + "[foo][mod.foo]", + True, + id="rel-ref-check-crossrefs-fail", + ), + pytest.param( + Module(name="mod", filepath=Path("mod."), docstring=Docstring("[foo][mod.foo]")), + {"better_crossrefs": False, "check_crossrefs": True}, + False, + "[foo][mod.foo]", + False, + id="abs-ref-check-crossrefs-nofail-without-betterrefs", + ), + pytest.param( + Module(name="mod", filepath=Path("mod."), docstring=Docstring("[foo][.foo]")), + {"better_crossrefs": False, "check_crossrefs": True}, + False, + "[foo][.foo]", + False, + id="rel-ref-check-crossrefs-nofail-without-betterrefs", + ), + pytest.param( + Module(name="mod", filepath=Path("mod."), docstring=Docstring("[foo][.foo] [bar][.]")), + {"better_crossrefs": True, "check_crossrefs": False}, + True, + "[foo][mod.foo] [bar][mod.bar]", + False, + id="rel-refs-enabled-multiple", + ), + ], +) +def test_render( + griffe_obj: Object, + options: dict[str, Any], + checkref_result: bool, + expected_rendered: str, + expected_error: bool, + test_project: Path, + monkeypatch: pytest.MonkeyPatch, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test that the rendering was overwritten correctly. - def fake_render(_self: PythonHandler, data: Object, _config: dict) -> str: - assert data.docstring is not None + Note that the goal of this test isn't necessarily to test the better ref logic, + rather, it is to test that the handler is using this logic properly and applying + it accordingly to the passed options. + """ + handler = PythonBetterRefsHandler( + config=PythonConfig(), + base_dir=test_project, + theme="material", + mdx=[], + mdx_config={}, + ) + + # patch the render method of the parent class to only return the docstring, + # we don't need/want to see the full template here + def fake_render(_self: PythonHandler, data: CollectorItem, options: PythonOptions) -> str: return data.docstring.value - # Monkeypatch render/collect methods on parent class - monkeypatch.setattr(PythonHandler, 'collect', fake_collect) - monkeypatch.setattr(PythonHandler, 'render', fake_render) - - obj = Module(name='mod', filepath= Path('mod.py')) - docstring = "[foo][.] [bar][bad.]" - obj.docstring = Docstring(docstring, parent=obj) - - rendered = handler.render(obj, {}) - assert rendered == docstring - - rendered = handler.render(obj, dict(relative_crossrefs=False)) - assert rendered == docstring - - rendered = handler.render(obj, dict(relative_crossrefs=True)) - assert rendered == "[foo][mod.foo] [bar][bad.bar]" - assert len(caplog.records) == 1 - _, level, msg = caplog.record_tuples[0] - assert level == logging.WARNING - assert "Cannot load reference 'bad.bar'" in msg - caplog.clear() - - rendered = handler.render(obj, dict(relative_crossrefs=True, check_crossrefs=False)) - assert rendered == "[foo][mod.foo] [bar][bad.bar]" - assert len(caplog.records) == 0 - - rendered = handler.render(obj, dict(relative_crossrefs=True, check_crossrefs=False)) - assert rendered == "[foo][mod.foo] [bar][bad.bar]" - assert len(caplog.records) == 0 - - docstring = "\n\n[foo][bad.foo]" - obj.docstring = Docstring(docstring, parent=obj) - rendered = handler.render(obj, dict(relative_crossrefs=True)) - assert rendered == "[foo][bad.foo]" - assert len(caplog.records) == 1 - _, level, msg = caplog.record_tuples[0] - assert level == logging.WARNING - assert "Cannot load reference 'bad.foo'" in msg - caplog.clear() - - docstring = "[foo][?bad.foo] [bar][?bad.]" - obj.docstring = Docstring(docstring, parent=obj) - rendered = handler.render(obj, dict(relative_crossrefs=True, check_crossrefs=True)) - assert rendered == "[foo][bad.foo] [bar][bad.bar]" - assert len(caplog.records) == 0 + monkeypatch.setattr(PythonHandler, "render", fake_render) + + # Patch check-ref according to the test parameters + monkeypatch.setattr(PythonBetterRefsHandler, "_check_ref", lambda self, ref: checkref_result) + + # Test if rendering works as expected + rendered = handler.render(griffe_obj, PythonBetterRefsOptions.from_data(**options)) + assert rendered == expected_rendered + + # Check whether a log was produced (when a reference wasn't found) + if expected_error: + assert len(caplog.records) >= 1 + assert "Cannot load reference" in caplog.messages[0] + else: + assert len(caplog.records) == 0 diff --git a/tests/test_integration.py b/tests/test_integration.py index 05b82ed..9069396 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -1,117 +1,119 @@ -# Copyright (c) 2022-2024. Analog Devices Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Integration test for python_xref handler""" - -import os +"""Integration test for python_betterrefs handler.""" + import re import subprocess as sp +from collections.abc import Mapping from os import PathLike from pathlib import Path -from typing import Dict, List, Tuple +from typing import cast import bs4 -this_dir = Path(__file__).parent -test_project_dir = this_dir.joinpath('project').absolute() -test_project_mkdocs = test_project_dir.joinpath('mkdocs.yml') -bar_src_file = test_project_dir.joinpath('src', 'myproj', 'bar.py') +def run_test_project_mkdocs(test_project: Path, site_dir: Path) -> sp.CompletedProcess[str]: + """Run mkdocs command on a tiny sample project (contained in the same dir as this file).""" + mkdocs_cmd = [ + "mkdocs", + "build", + "-f", + str(test_project / "mkdocs.yml"), + "-d", + str(site_dir), + ] + return sp.run(mkdocs_cmd, capture_output=True, encoding="utf8", check=False) # noqa: S603 # input is trusted + +def check_autorefs(html_file: Path, cases: Mapping[tuple[str, str], str]) -> None: + """Verify given HTML file contains all of the expected autorefs. -def check_autorefs(autorefs: List[bs4.Tag], cases: Dict[Tuple[str,str],str] ) -> None: - """ - Verify autorefs contain expected cases + Note: + If the HTML file contains some additional autorefs, an AssertionError will be raised. - Arguments: - autorefs: list of autoref tags parsed from HTML - cases: mapping from (<location>,<title>) to generated reference tag - where <location? is the qualified name of the object whose doc string + Args: + html_file: HTML file to check for autorefs. + cases: mapping from (<location>,<title>) to generated reference link (<href>) + where <location> is the qualified name of the object whose doc string contains the cross-reference, and <title> is the text in the cross-reference. """ - cases = cases.copy() + html = html_file.read_text() + soup = bs4.BeautifulSoup(html, "html.parser") + autorefs = soup.find_all("a", class_=["autorefs"]) + + cases = dict(cases) for autoref in autorefs: - curid = autoref.find_previous(id=True).attrs['id'] + # This is for typing purposes only, the find_all filter shouldn't ever find non-tags + if not isinstance(autoref, bs4.Tag): + raise TypeError("Autorefs contained non-tag") + + cur_id = cast(bs4.Tag, autoref.find_previous(id=True)).attrs["id"] text = autoref.string - href = autoref.attrs['href'] - expected_href = cases.get((curid, text)) + href = autoref.attrs["href"] + expected_href = cases.get((cur_id, text)) # pyright: ignore[reportArgumentType] + if expected_href: assert href == expected_href - cases.pop((curid, text)) + _ = cases.pop((cur_id, text)) # pyright: ignore[reportArgumentType] + else: + raise AssertionError(f"Skipping ref: {cur_id=},{text=} -> {href!r} ({autoref!s}") assert len(cases) == 0 -def test_integration(tmpdir: PathLike) -> None: - """An integration test that runs mkdocs on a tiny sample project and - grovels the generated HTML to see that the links were resolved. - """ - - site_dir = Path(tmpdir).joinpath('site') - mkdocs_cmd = [ - 'mkdocs', - 'build', - '-f', - str(test_project_mkdocs), - '-d', - str(site_dir) - ] - result = sp.run(mkdocs_cmd, stdout=sp.PIPE, stderr=sp.PIPE, encoding='utf8', check=False) - - assert result.returncode == 0 +def test_integration(test_project: Path, tmpdir: PathLike[str]) -> None: + """An integration test that runs mkdocs on a tiny sample project. + This then grovels the generated HTML to see that the links were resolved. + """ + site_dir = Path(tmpdir).joinpath("site") + result = run_test_project_mkdocs(test_project, site_dir) + + # Make sure the command succeeded + try: + result.check_returncode() + except sp.CalledProcessError: + print(result.stderr) # noqa: T201 + raise + + # There is a single intentional bad reference in the bar.py file + # make sure it was reported (check_crossrefs). m = re.search( - r"WARNING.*file://(/.*/myproj/bar.py):(\d+):\s*\n\s*Cannot load reference '(.*)'", - result.stderr + r"WARNING.*file://(.*[/\\]myproj[/\\]bar\.py):(\d+):\s*\n\s*Cannot load reference '(.*)'", + result.stderr, ) - assert m is not None - if os.path.sep == '/': - assert m[1] == str(bar_src_file) - assert m[3] == 'myproj.bar.bad' - # Source location not accurate in python 3.7 - bad_line = int(m[2]) - bar_lines = bar_src_file.read_text().splitlines() - assert '[bad]' in bar_lines[bad_line - 1] + assert m is not None, result.stderr + assert m[1] == str(test_project.joinpath("src", "myproj", "bar.py")) + assert m[2] == "3" + assert m[3] == "myproj.bar.bad" - bar_html = site_dir.joinpath('bar', 'index.html').read_text() - bar_bs = bs4.BeautifulSoup(bar_html, 'html.parser') - - autorefs: List[bs4.Tag] = bar_bs.find_all('a', attrs=['autorefs']) - assert len(autorefs) >= 5 + # The original error for invalid references from autorefs should still be present too + m = re.search( + ( + r"WARNING.*mkdocs_autorefs: bar\.md: from (.*[/\\]myproj[/\\]bar.py):(\d+): \(myproj\.bar\) " + r"Could not find cross-reference target '(.*)'" + ), + result.stderr, + ) + assert m is not None, result.stderr + assert m[1] == str(test_project.joinpath("src", "myproj", "bar.py")) + assert m[2] == "1" # line numbers aren't supported by mkdocs_autorefs, this is always 1 + assert m[3] == "myproj.bar.bad" + # Verify the references (autorefs anchor tags) in the generated documentation HTML check_autorefs( - autorefs, + site_dir.joinpath("bar", "index.html"), { - ('myproj.bar.Bar', 'bar') : '#myproj.bar.Bar.bar', - ('myproj.bar.Bar.bar' , 'Bar') : '#myproj.bar.Bar', - ('myproj.bar.Bar.bar', 'foo') : '#myproj.bar.Bar.foo', - ('myproj.bar.Bar.bar', 'func') : '#myproj.bar.func', - ('myproj.bar.Bar.foo', 'Foo.foo') : '../foo/#myproj.foo.Foo.foo', - ('myproj.bar.func', 'bar') : '#myproj.bar' - } + ("myproj.bar.Bar", "Foo"): "../foo/#myproj.foo.Foo", # from bases (parent class) + ("myproj.bar.Bar", "bar"): "#myproj.bar.Bar.bar", + ("myproj.bar.Bar.bar", "Bar"): "#myproj.bar.Bar", + ("myproj.bar.Bar.bar", "foo"): "#myproj.bar.Bar.foo", + ("myproj.bar.Bar.bar", "func"): "#myproj.bar.func", + ("myproj.bar.Bar.foo", "Foo.foo"): "../foo/#myproj.foo.Foo.foo", + ("myproj.bar.func", "bar"): "#myproj.bar", + }, ) - - baz_html = site_dir.joinpath('pkg-baz', 'index.html').read_text() - baz_bs = bs4.BeautifulSoup(baz_html, 'html.parser') - - autorefs = baz_bs.find_all('a', attrs=['autorefs']) - assert len(autorefs) >= 1 - check_autorefs( - autorefs, + site_dir.joinpath("pkg-baz", "index.html"), { - ('myproj.pkg.baz', 'func') : '../pkg/#myproj.pkg.func', - } + ("myproj.pkg.baz", "func"): "../pkg/#myproj.pkg.func", + }, ) - - diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..d63125d --- /dev/null +++ b/uv.lock @@ -0,0 +1,1024 @@ +version = 1 +requires-python = ">=3.9" + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537 }, +] + +[[package]] +name = "basedpyright" +version = "1.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodejs-wheel-binaries" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/c2/5685d040d4f2598788d42bfd2db5f808e9aa2eaee77fcae3c2fbe4ea0e7c/basedpyright-1.26.0.tar.gz", hash = "sha256:5e01f6eb9290a09ef39672106cf1a02924fdc8970e521838bc502ccf0676f32f", size = 24932771 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/72/65308f45bb73efc93075426cac5f37eea937ae364aa675785521cb3512c7/basedpyright-1.26.0-py3-none-any.whl", hash = "sha256:5a6a17f2c389ec313dd2c3644f40e8221bc90252164802e626055341c0a37381", size = 11504579 }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.13.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f0/3c/adaf39ce1fb4afdd21b611e3d530b183bb7759c9b673d60db0e347fd4439/beautifulsoup4-4.13.3.tar.gz", hash = "sha256:1bd32405dacc920b42b83ba01644747ed77456a65760e285fbc47633ceddaf8b", size = 619516 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/49/6abb616eb3cbab6a7cca303dc02fdf3836de2e0b834bf966a7f5271a34d8/beautifulsoup4-4.13.3-py3-none-any.whl", hash = "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16", size = 186015 }, +] + +[[package]] +name = "certifi" +version = "2025.1.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013 }, + { url = "https://files.pythonhosted.org/packages/d0/11/00341177ae71c6f5159a08168bcb98c6e6d196d372c94511f9f6c9afe0c6/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", size = 141285 }, + { url = "https://files.pythonhosted.org/packages/01/09/11d684ea5819e5a8f5100fb0b38cf8d02b514746607934134d31233e02c8/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", size = 151449 }, + { url = "https://files.pythonhosted.org/packages/08/06/9f5a12939db324d905dc1f70591ae7d7898d030d7662f0d426e2286f68c9/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", size = 143892 }, + { url = "https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", size = 146123 }, + { url = "https://files.pythonhosted.org/packages/a9/ac/ab729a15c516da2ab70a05f8722ecfccc3f04ed7a18e45c75bbbaa347d61/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", size = 147943 }, + { url = "https://files.pythonhosted.org/packages/03/d2/3f392f23f042615689456e9a274640c1d2e5dd1d52de36ab8f7955f8f050/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", size = 142063 }, + { url = "https://files.pythonhosted.org/packages/f2/e3/e20aae5e1039a2cd9b08d9205f52142329f887f8cf70da3650326670bddf/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", size = 150578 }, + { url = "https://files.pythonhosted.org/packages/8d/af/779ad72a4da0aed925e1139d458adc486e61076d7ecdcc09e610ea8678db/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", size = 153629 }, + { url = "https://files.pythonhosted.org/packages/c2/b6/7aa450b278e7aa92cf7732140bfd8be21f5f29d5bf334ae987c945276639/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", size = 150778 }, + { url = "https://files.pythonhosted.org/packages/39/f4/d9f4f712d0951dcbfd42920d3db81b00dd23b6ab520419626f4023334056/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", size = 146453 }, + { url = "https://files.pythonhosted.org/packages/49/2b/999d0314e4ee0cff3cb83e6bc9aeddd397eeed693edb4facb901eb8fbb69/charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", size = 95479 }, + { url = "https://files.pythonhosted.org/packages/2d/ce/3cbed41cff67e455a386fb5e5dd8906cdda2ed92fbc6297921f2e4419309/charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", size = 102790 }, + { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 }, + { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 }, + { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 }, + { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 }, + { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 }, + { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 }, + { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 }, + { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 }, + { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 }, + { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 }, + { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 }, + { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 }, + { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 }, + { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, + { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, + { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, + { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, + { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, + { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, + { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, + { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, + { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, + { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, + { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, + { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, + { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, + { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, + { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, + { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, + { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, + { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, + { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, + { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, + { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, + { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, + { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, + { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, + { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, + { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, + { url = "https://files.pythonhosted.org/packages/7f/c0/b913f8f02836ed9ab32ea643c6fe4d3325c3d8627cf6e78098671cafff86/charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41", size = 197867 }, + { url = "https://files.pythonhosted.org/packages/0f/6c/2bee440303d705b6fb1e2ec789543edec83d32d258299b16eed28aad48e0/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f", size = 141385 }, + { url = "https://files.pythonhosted.org/packages/3d/04/cb42585f07f6f9fd3219ffb6f37d5a39b4fd2db2355b23683060029c35f7/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2", size = 151367 }, + { url = "https://files.pythonhosted.org/packages/54/54/2412a5b093acb17f0222de007cc129ec0e0df198b5ad2ce5699355269dfe/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770", size = 143928 }, + { url = "https://files.pythonhosted.org/packages/5a/6d/e2773862b043dcf8a221342954f375392bb2ce6487bcd9f2c1b34e1d6781/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4", size = 146203 }, + { url = "https://files.pythonhosted.org/packages/b9/f8/ca440ef60d8f8916022859885f231abb07ada3c347c03d63f283bec32ef5/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537", size = 148082 }, + { url = "https://files.pythonhosted.org/packages/04/d2/42fd330901aaa4b805a1097856c2edf5095e260a597f65def493f4b8c833/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496", size = 142053 }, + { url = "https://files.pythonhosted.org/packages/9e/af/3a97a4fa3c53586f1910dadfc916e9c4f35eeada36de4108f5096cb7215f/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78", size = 150625 }, + { url = "https://files.pythonhosted.org/packages/26/ae/23d6041322a3556e4da139663d02fb1b3c59a23ab2e2b56432bd2ad63ded/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7", size = 153549 }, + { url = "https://files.pythonhosted.org/packages/94/22/b8f2081c6a77cb20d97e57e0b385b481887aa08019d2459dc2858ed64871/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6", size = 150945 }, + { url = "https://files.pythonhosted.org/packages/c7/0b/c5ec5092747f801b8b093cdf5610e732b809d6cb11f4c51e35fc28d1d389/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294", size = 146595 }, + { url = "https://files.pythonhosted.org/packages/0c/5a/0b59704c38470df6768aa154cc87b1ac7c9bb687990a1559dc8765e8627e/charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5", size = 95453 }, + { url = "https://files.pythonhosted.org/packages/85/2d/a9790237cb4d01a6d57afadc8573c8b73c609ade20b80f4cda30802009ee/charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765", size = 102811 }, + { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, +] + +[[package]] +name = "click" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "distlib" +version = "0.3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, +] + +[[package]] +name = "filelock" +version = "3.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/9c/0b15fb47b464e1b663b1acd1253a062aa5feecb07d4e597daea542ebd2b5/filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e", size = 18027 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/ec/00d68c4ddfedfe64159999e5f8a98fb8442729a63e2077eb9dcd89623d27/filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338", size = 16164 }, +] + +[[package]] +name = "ghp-import" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034 }, +] + +[[package]] +name = "griffe" +version = "1.5.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/88/f0/a001e06c321dfa220103418259afbac50b933eac7a86657a4b572f0517e8/griffe-1.5.6.tar.gz", hash = "sha256:181f6666d5aceb6cd6e2da5a2b646cfb431e47a0da1fda283845734b67e10944", size = 391173 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/87/505777c4e5ca9c4fa5ae53fa4b0d5c2ba13a6d55a503a5594e94a2ba9b5a/griffe-1.5.6-py3-none-any.whl", hash = "sha256:b2a3afe497c6c1f952e54a23095ecc09435016293e77af8478ed65df1022a394", size = 128176 }, +] + +[[package]] +name = "identify" +version = "2.6.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/bf/c68c46601bacd4c6fb4dd751a42b6e7087240eaabc6487f2ef7a48e0e8fc/identify-2.6.6.tar.gz", hash = "sha256:7bec12768ed44ea4761efb47806f0a41f86e7c0a5fdf5950d4648c90eca7e251", size = 99217 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/a1/68a395c17eeefb04917034bd0a1bfa765e7654fa150cca473d669aa3afb5/identify-2.6.6-py2.py3-none-any.whl", hash = "sha256:cbd1810bce79f8b671ecb20f53ee0ae8e86ae84b557de31d89709dc2a48ba881", size = 99083 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "importlib-metadata" +version = "8.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/08/c1395a292bb23fd03bdf572a1357c5a733d3eecbab877641ceacab23db6e/importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580", size = 55767 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/9d/0fb148dc4d6fa4a7dd1d8378168d9b4cd8d4560a6fbf6f0121c5fc34eb68/importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e", size = 26971 }, +] + +[[package]] +name = "importlib-resources" +version = "6.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461 }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "jinja2" +version = "3.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/92/b3130cbbf5591acf9ade8708c365f3238046ac7cb8ccba6e81abccb0ccff/jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", size = 244674 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596 }, +] + +[[package]] +name = "markdown" +version = "3.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/28/3af612670f82f4c056911fbbbb42760255801b3068c48de792d354ff4472/markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", size = 357086 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/08/83871f3c50fc983b88547c196d11cf8c3340e37c32d2e9d6152abe2c61f7/Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803", size = 106349 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357 }, + { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393 }, + { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732 }, + { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866 }, + { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964 }, + { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977 }, + { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366 }, + { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091 }, + { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065 }, + { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514 }, + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, + { url = "https://files.pythonhosted.org/packages/a7/ea/9b1530c3fdeeca613faeb0fb5cbcf2389d816072fab72a71b45749ef6062/MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", size = 14344 }, + { url = "https://files.pythonhosted.org/packages/4b/c2/fbdbfe48848e7112ab05e627e718e854d20192b674952d9042ebd8c9e5de/MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", size = 12389 }, + { url = "https://files.pythonhosted.org/packages/f0/25/7a7c6e4dbd4f867d95d94ca15449e91e52856f6ed1905d58ef1de5e211d0/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", size = 21607 }, + { url = "https://files.pythonhosted.org/packages/53/8f/f339c98a178f3c1e545622206b40986a4c3307fe39f70ccd3d9df9a9e425/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", size = 20728 }, + { url = "https://files.pythonhosted.org/packages/1a/03/8496a1a78308456dbd50b23a385c69b41f2e9661c67ea1329849a598a8f9/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", size = 20826 }, + { url = "https://files.pythonhosted.org/packages/e6/cf/0a490a4bd363048c3022f2f475c8c05582179bb179defcee4766fb3dcc18/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", size = 21843 }, + { url = "https://files.pythonhosted.org/packages/19/a3/34187a78613920dfd3cdf68ef6ce5e99c4f3417f035694074beb8848cd77/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", size = 21219 }, + { url = "https://files.pythonhosted.org/packages/17/d8/5811082f85bb88410ad7e452263af048d685669bbbfb7b595e8689152498/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", size = 20946 }, + { url = "https://files.pythonhosted.org/packages/7c/31/bd635fb5989440d9365c5e3c47556cfea121c7803f5034ac843e8f37c2f2/MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", size = 15063 }, + { url = "https://files.pythonhosted.org/packages/b3/73/085399401383ce949f727afec55ec3abd76648d04b9f22e1c0e99cb4bec3/MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", size = 15506 }, +] + +[[package]] +name = "mergedeep" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354 }, +] + +[[package]] +name = "mike" +version = "2.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "importlib-resources" }, + { name = "jinja2" }, + { name = "mkdocs" }, + { name = "pyparsing" }, + { name = "pyyaml" }, + { name = "pyyaml-env-tag" }, + { name = "verspec" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/f7/2933f1a1fb0e0f077d5d6a92c6c7f8a54e6128241f116dff4df8b6050bbf/mike-2.1.3.tar.gz", hash = "sha256:abd79b8ea483fb0275b7972825d3082e5ae67a41820f8d8a0dc7a3f49944e810", size = 38119 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/1a/31b7cd6e4e7a02df4e076162e9783620777592bea9e4bb036389389af99d/mike-2.1.3-py3-none-any.whl", hash = "sha256:d90c64077e84f06272437b464735130d380703a76a5738b152932884c60c062a", size = 33754 }, +] + +[[package]] +name = "mkdocs" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "ghp-import" }, + { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mergedeep" }, + { name = "mkdocs-get-deps" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "pyyaml" }, + { name = "pyyaml-env-tag" }, + { name = "watchdog" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451 }, +] + +[[package]] +name = "mkdocs-autorefs" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/18/fb1e17fb705228b51bf7b2f791adaf83c0fa708e51bbc003411ba48ae21e/mkdocs_autorefs-1.3.0.tar.gz", hash = "sha256:6867764c099ace9025d6ac24fd07b85a98335fbd30107ef01053697c8f46db61", size = 42597 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/4a/960c441950f98becfa5dd419adab20274939fd575ab848aee2c87e3599ac/mkdocs_autorefs-1.3.0-py3-none-any.whl", hash = "sha256:d180f9778a04e78b7134e31418f238bba56f56d6a8af97873946ff661befffb3", size = 17642 }, +] + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, + { name = "mergedeep" }, + { name = "platformdirs" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521 }, +] + +[[package]] +name = "mkdocs-material" +version = "9.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "colorama" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "mkdocs" }, + { name = "mkdocs-material-extensions" }, + { name = "paginate" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "regex" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/1e/65b4fda4debf5e337b2ad4e692423dba4f5c77f49c4dee170c47a7dbac25/mkdocs_material-9.6.3.tar.gz", hash = "sha256:c87f7d1c39ce6326da5e10e232aed51bae46252e646755900f4b0fc9192fa832", size = 3942608 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/a4/e0da0bc6a7dbfda6a786427f82a0caa4dd1f163249a5a5e5dccbb50c5f1e/mkdocs_material-9.6.3-py3-none-any.whl", hash = "sha256:1125622067e26940806701219303b27c0933e04533560725d97ec26fd16a39cf", size = 8688709 }, +] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728 }, +] + +[[package]] +name = "mkdocstrings" +version = "0.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, + { name = "mkdocs-autorefs" }, + { name = "mkdocs-get-deps" }, + { name = "pymdown-extensions" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/4b/70522427768a4637ffac376140f362dc3d159364fb64e698667e51053d57/mkdocstrings-0.28.0.tar.gz", hash = "sha256:df20afef1eafe36ba466ae20732509ecb74237653a585f5061937e54b553b4e0", size = 3392797 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/c3/e5a319d4de0867c1b59ff22abb93bf898f9812e934ab75dcf7fe94e85bb6/mkdocstrings-0.28.0-py3-none-any.whl", hash = "sha256:84cf3dc910614781fe0fee46ce8006fde7df6cc7cca2e3f799895fb8a9170b39", size = 4700952 }, +] + +[[package]] +name = "mkdocstrings-python" +version = "1.14.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "griffe" }, + { name = "mkdocs-autorefs" }, + { name = "mkdocstrings" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/fd/3cfa0fc2a724e16262f2de63e13365e726426c44a74fcc3e86b31ff3f1fa/mkdocstrings_python-1.14.6.tar.gz", hash = "sha256:3fb6589491614422d781dacca085c0c5a53a7063af37a2fea5864e24e378b03e", size = 422060 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/e4/b4f88e4edd914831b65caa34806012cc00167a7f2dd62e4e407113957b0f/mkdocstrings_python-1.14.6-py3-none-any.whl", hash = "sha256:e0ca11b49ac0f23070afb566245f4ff80ea1700e03c9dbc143d70dbf1cae074e", size = 448699 }, +] + +[[package]] +name = "mkdocstrings-python-betterrefs" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "griffe" }, + { name = "mkdocs" }, + { name = "mkdocs-autorefs" }, + { name = "mkdocstrings" }, + { name = "mkdocstrings-python" }, + { name = "typing-extensions" }, +] + +[package.dev-dependencies] +docs = [ + { name = "mike" }, + { name = "mkdocs-material" }, +] +lint = [ + { name = "basedpyright" }, + { name = "pre-commit" }, + { name = "ruff" }, +] +test = [ + { name = "beautifulsoup4" }, + { name = "pytest" }, +] + +[package.metadata] +requires-dist = [ + { name = "griffe", specifier = "==1.5.6" }, + { name = "mkdocs", specifier = ">=1.4" }, + { name = "mkdocs-autorefs", specifier = ">=1.3.0" }, + { name = "mkdocstrings", specifier = "==0.28.0" }, + { name = "mkdocstrings-python", specifier = "==1.14.6" }, + { name = "typing-extensions", specifier = ">=4.0" }, +] + +[package.metadata.requires-dev] +docs = [ + { name = "mike", specifier = ">=2.1.2" }, + { name = "mkdocs-material", specifier = ">=9.5.30" }, +] +lint = [ + { name = "basedpyright", specifier = ">=1.13.3" }, + { name = "pre-commit", specifier = ">=3.3.3" }, + { name = "ruff", specifier = ">=0.9.2" }, +] +test = [ + { name = "beautifulsoup4", specifier = ">=4.13.3" }, + { name = "pytest", specifier = ">=8.3.4" }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, +] + +[[package]] +name = "nodejs-wheel-binaries" +version = "22.13.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5d/c5/1af2fc54fcc18f4a99426b46f18832a04f755ee340019e1be536187c1e1c/nodejs_wheel_binaries-22.13.1.tar.gz", hash = "sha256:a0c15213c9c3383541be4400a30959883868ce5da9cebb3d63ddc7fe61459308", size = 8053 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/e9/b0dd118e0fd4eabe1ec9c3d9a68df4d811282e8837b811d804f23742e117/nodejs_wheel_binaries-22.13.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:e4f64d0e26600d51cbdd98a6718a19c2d1b8c7538e9e353e95a634a06a8e1a58", size = 51015650 }, + { url = "https://files.pythonhosted.org/packages/cc/a6/9ba835f5d4f3f6b1f01191e7ac0874871f9743de5c42a5a9a54e67c2e2a6/nodejs_wheel_binaries-22.13.1-py2.py3-none-macosx_11_0_x86_64.whl", hash = "sha256:afcb40484bb02f23137f838014724604ae183fd767b30da95b0be1510a40c06d", size = 51814957 }, + { url = "https://files.pythonhosted.org/packages/0d/2e/a430207e5f22bd3dcffb81acbddf57ee4108b9e2b0f99a5578dc2c1ff7fc/nodejs_wheel_binaries-22.13.1-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fc88c98eebabfc36b5270a4ab974a2682746931567ca76a5ca49c54482bbb51", size = 57148437 }, + { url = "https://files.pythonhosted.org/packages/97/f4/5731b6f0c8af434619b4f1b8fd895bc33fca60168cd68133e52841872114/nodejs_wheel_binaries-22.13.1-py2.py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b9f75ea8f5e3e5416256fcb00a98cbe14c8d3b6dcaf17da29c4ade5723026d8", size = 57634451 }, + { url = "https://files.pythonhosted.org/packages/49/28/83166f7e39812e9ef99cfa3e722c54e32dd9de6a1290f3216c2e5d1f4957/nodejs_wheel_binaries-22.13.1-py2.py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:94608702ef6c389d32e89ff3b7a925cb5dedaf55b5d98bd0c4fb3450a8b6d1c1", size = 58794510 }, + { url = "https://files.pythonhosted.org/packages/f7/64/4832ec26d0a7ca7a5574df265d85c6832f9a624024511fc34958227ad740/nodejs_wheel_binaries-22.13.1-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:53a40d0269689aa2eaf2e261cbe5ec256644bc56aae0201ef344b7d8f40ccc79", size = 59738596 }, + { url = "https://files.pythonhosted.org/packages/18/cd/def29615dac250cda3d141e1c03b7153b9a027360bde0272a6768c5fae33/nodejs_wheel_binaries-22.13.1-py2.py3-none-win_amd64.whl", hash = "sha256:549371a929a29fbce8d0ab8f1b5410549946d4f1b0376a5ce635b45f6d05298f", size = 40455444 }, + { url = "https://files.pythonhosted.org/packages/15/d7/6de2bc615203bf590ca437a5cac145b2f86d994ce329489125a0a90ba715/nodejs_wheel_binaries-22.13.1-py2.py3-none-win_arm64.whl", hash = "sha256:cf72d50d755f4e5c0709b0449de01768d96b3b1ec7aa531561415b88f179ad8b", size = 36200929 }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "paginate" +version = "0.5.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746 }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "pre-commit" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/13/b62d075317d8686071eb843f0bb1f195eb332f48869d3c31a4c6f1e063ac/pre_commit-4.1.0.tar.gz", hash = "sha256:ae3f018575a588e30dfddfab9a05448bfbd6b73d78709617b5a2b853549716d4", size = 193330 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/b3/df14c580d82b9627d173ceea305ba898dca135feb360b6d84019d0803d3b/pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b", size = 220560 }, +] + +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, +] + +[[package]] +name = "pymdown-extensions" +version = "10.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/44/e6de2fdc880ad0ec7547ca2e087212be815efbc9a425a8d5ba9ede602cbb/pymdown_extensions-10.14.3.tar.gz", hash = "sha256:41e576ce3f5d650be59e900e4ceff231e0aed2a88cf30acaee41e02f063a061b", size = 846846 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/f5/b9e2a42aa8f9e34d52d66de87941ecd236570c7ed2e87775ed23bbe4e224/pymdown_extensions-10.14.3-py3-none-any.whl", hash = "sha256:05e0bee73d64b9c71a4ae17c72abc2f700e8bc8403755a00580b49a4e9f189e9", size = 264467 }, +] + +[[package]] +name = "pyparsing" +version = "3.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/1a/3544f4f299a47911c2ab3710f534e52fea62a633c96806995da5d25be4b2/pyparsing-3.2.1.tar.gz", hash = "sha256:61980854fd66de3a90028d679a954d5f2623e83144b5afe5ee86f43d762e5f0a", size = 1067694 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/a7/c8a2d361bf89c0d9577c934ebb7421b25dc84bf3a8e3ac0a40aed9acc547/pyparsing-3.2.1-py3-none-any.whl", hash = "sha256:506ff4f4386c4cec0590ec19e6302d3aedb992fdc02c761e90416f158dacf8e1", size = 107716 }, +] + +[[package]] +name = "pytest" +version = "8.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, + { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777 }, + { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318 }, + { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891 }, + { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614 }, + { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360 }, + { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006 }, + { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577 }, + { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593 }, + { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312 }, +] + +[[package]] +name = "pyyaml-env-tag" +version = "0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/8e/da1c6c58f751b70f8ceb1eb25bc25d524e8f14fe16edcce3f4e3ba08629c/pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb", size = 5631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/66/bbb1dd374f5c870f59c5bb1db0e18cbe7fa739415a24cbd95b2d1f5ae0c4/pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069", size = 3911 }, +] + +[[package]] +name = "regex" +version = "2024.11.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/3c/4651f6b130c6842a8f3df82461a8950f923925db8b6961063e82744bddcc/regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91", size = 482674 }, + { url = "https://files.pythonhosted.org/packages/15/51/9f35d12da8434b489c7b7bffc205c474a0a9432a889457026e9bc06a297a/regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0", size = 287684 }, + { url = "https://files.pythonhosted.org/packages/bd/18/b731f5510d1b8fb63c6b6d3484bfa9a59b84cc578ac8b5172970e05ae07c/regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e", size = 284589 }, + { url = "https://files.pythonhosted.org/packages/78/a2/6dd36e16341ab95e4c6073426561b9bfdeb1a9c9b63ab1b579c2e96cb105/regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde", size = 782511 }, + { url = "https://files.pythonhosted.org/packages/1b/2b/323e72d5d2fd8de0d9baa443e1ed70363ed7e7b2fb526f5950c5cb99c364/regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e", size = 821149 }, + { url = "https://files.pythonhosted.org/packages/90/30/63373b9ea468fbef8a907fd273e5c329b8c9535fee36fc8dba5fecac475d/regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2", size = 809707 }, + { url = "https://files.pythonhosted.org/packages/f2/98/26d3830875b53071f1f0ae6d547f1d98e964dd29ad35cbf94439120bb67a/regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf", size = 781702 }, + { url = "https://files.pythonhosted.org/packages/87/55/eb2a068334274db86208ab9d5599ffa63631b9f0f67ed70ea7c82a69bbc8/regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c", size = 771976 }, + { url = "https://files.pythonhosted.org/packages/74/c0/be707bcfe98254d8f9d2cff55d216e946f4ea48ad2fd8cf1428f8c5332ba/regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86", size = 697397 }, + { url = "https://files.pythonhosted.org/packages/49/dc/bb45572ceb49e0f6509f7596e4ba7031f6819ecb26bc7610979af5a77f45/regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67", size = 768726 }, + { url = "https://files.pythonhosted.org/packages/5a/db/f43fd75dc4c0c2d96d0881967897926942e935d700863666f3c844a72ce6/regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d", size = 775098 }, + { url = "https://files.pythonhosted.org/packages/99/d7/f94154db29ab5a89d69ff893159b19ada89e76b915c1293e98603d39838c/regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2", size = 839325 }, + { url = "https://files.pythonhosted.org/packages/f7/17/3cbfab1f23356fbbf07708220ab438a7efa1e0f34195bf857433f79f1788/regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008", size = 843277 }, + { url = "https://files.pythonhosted.org/packages/7e/f2/48b393b51900456155de3ad001900f94298965e1cad1c772b87f9cfea011/regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62", size = 773197 }, + { url = "https://files.pythonhosted.org/packages/45/3f/ef9589aba93e084cd3f8471fded352826dcae8489b650d0b9b27bc5bba8a/regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e", size = 261714 }, + { url = "https://files.pythonhosted.org/packages/42/7e/5f1b92c8468290c465fd50c5318da64319133231415a8aa6ea5ab995a815/regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519", size = 274042 }, + { url = "https://files.pythonhosted.org/packages/58/58/7e4d9493a66c88a7da6d205768119f51af0f684fe7be7bac8328e217a52c/regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638", size = 482669 }, + { url = "https://files.pythonhosted.org/packages/34/4c/8f8e631fcdc2ff978609eaeef1d6994bf2f028b59d9ac67640ed051f1218/regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7", size = 287684 }, + { url = "https://files.pythonhosted.org/packages/c5/1b/f0e4d13e6adf866ce9b069e191f303a30ab1277e037037a365c3aad5cc9c/regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20", size = 284589 }, + { url = "https://files.pythonhosted.org/packages/25/4d/ab21047f446693887f25510887e6820b93f791992994f6498b0318904d4a/regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114", size = 792121 }, + { url = "https://files.pythonhosted.org/packages/45/ee/c867e15cd894985cb32b731d89576c41a4642a57850c162490ea34b78c3b/regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3", size = 831275 }, + { url = "https://files.pythonhosted.org/packages/b3/12/b0f480726cf1c60f6536fa5e1c95275a77624f3ac8fdccf79e6727499e28/regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f", size = 818257 }, + { url = "https://files.pythonhosted.org/packages/bf/ce/0d0e61429f603bac433910d99ef1a02ce45a8967ffbe3cbee48599e62d88/regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0", size = 792727 }, + { url = "https://files.pythonhosted.org/packages/e4/c1/243c83c53d4a419c1556f43777ccb552bccdf79d08fda3980e4e77dd9137/regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55", size = 780667 }, + { url = "https://files.pythonhosted.org/packages/c5/f4/75eb0dd4ce4b37f04928987f1d22547ddaf6c4bae697623c1b05da67a8aa/regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89", size = 776963 }, + { url = "https://files.pythonhosted.org/packages/16/5d/95c568574e630e141a69ff8a254c2f188b4398e813c40d49228c9bbd9875/regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d", size = 784700 }, + { url = "https://files.pythonhosted.org/packages/8e/b5/f8495c7917f15cc6fee1e7f395e324ec3e00ab3c665a7dc9d27562fd5290/regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34", size = 848592 }, + { url = "https://files.pythonhosted.org/packages/1c/80/6dd7118e8cb212c3c60b191b932dc57db93fb2e36fb9e0e92f72a5909af9/regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d", size = 852929 }, + { url = "https://files.pythonhosted.org/packages/11/9b/5a05d2040297d2d254baf95eeeb6df83554e5e1df03bc1a6687fc4ba1f66/regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45", size = 781213 }, + { url = "https://files.pythonhosted.org/packages/26/b7/b14e2440156ab39e0177506c08c18accaf2b8932e39fb092074de733d868/regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9", size = 261734 }, + { url = "https://files.pythonhosted.org/packages/80/32/763a6cc01d21fb3819227a1cc3f60fd251c13c37c27a73b8ff4315433a8e/regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60", size = 274052 }, + { url = "https://files.pythonhosted.org/packages/ba/30/9a87ce8336b172cc232a0db89a3af97929d06c11ceaa19d97d84fa90a8f8/regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", size = 483781 }, + { url = "https://files.pythonhosted.org/packages/01/e8/00008ad4ff4be8b1844786ba6636035f7ef926db5686e4c0f98093612add/regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", size = 288455 }, + { url = "https://files.pythonhosted.org/packages/60/85/cebcc0aff603ea0a201667b203f13ba75d9fc8668fab917ac5b2de3967bc/regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", size = 284759 }, + { url = "https://files.pythonhosted.org/packages/94/2b/701a4b0585cb05472a4da28ee28fdfe155f3638f5e1ec92306d924e5faf0/regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4", size = 794976 }, + { url = "https://files.pythonhosted.org/packages/4b/bf/fa87e563bf5fee75db8915f7352e1887b1249126a1be4813837f5dbec965/regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577", size = 833077 }, + { url = "https://files.pythonhosted.org/packages/a1/56/7295e6bad94b047f4d0834e4779491b81216583c00c288252ef625c01d23/regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3", size = 823160 }, + { url = "https://files.pythonhosted.org/packages/fb/13/e3b075031a738c9598c51cfbc4c7879e26729c53aa9cca59211c44235314/regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e", size = 796896 }, + { url = "https://files.pythonhosted.org/packages/24/56/0b3f1b66d592be6efec23a795b37732682520b47c53da5a32c33ed7d84e3/regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe", size = 783997 }, + { url = "https://files.pythonhosted.org/packages/f9/a1/eb378dada8b91c0e4c5f08ffb56f25fcae47bf52ad18f9b2f33b83e6d498/regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e", size = 781725 }, + { url = "https://files.pythonhosted.org/packages/83/f2/033e7dec0cfd6dda93390089864732a3409246ffe8b042e9554afa9bff4e/regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29", size = 789481 }, + { url = "https://files.pythonhosted.org/packages/83/23/15d4552ea28990a74e7696780c438aadd73a20318c47e527b47a4a5a596d/regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39", size = 852896 }, + { url = "https://files.pythonhosted.org/packages/e3/39/ed4416bc90deedbfdada2568b2cb0bc1fdb98efe11f5378d9892b2a88f8f/regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51", size = 860138 }, + { url = "https://files.pythonhosted.org/packages/93/2d/dd56bb76bd8e95bbce684326302f287455b56242a4f9c61f1bc76e28360e/regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad", size = 787692 }, + { url = "https://files.pythonhosted.org/packages/0b/55/31877a249ab7a5156758246b9c59539abbeba22461b7d8adc9e8475ff73e/regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54", size = 262135 }, + { url = "https://files.pythonhosted.org/packages/38/ec/ad2d7de49a600cdb8dd78434a1aeffe28b9d6fc42eb36afab4a27ad23384/regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b", size = 273567 }, + { url = "https://files.pythonhosted.org/packages/90/73/bcb0e36614601016552fa9344544a3a2ae1809dc1401b100eab02e772e1f/regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", size = 483525 }, + { url = "https://files.pythonhosted.org/packages/0f/3f/f1a082a46b31e25291d830b369b6b0c5576a6f7fb89d3053a354c24b8a83/regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", size = 288324 }, + { url = "https://files.pythonhosted.org/packages/09/c9/4e68181a4a652fb3ef5099e077faf4fd2a694ea6e0f806a7737aff9e758a/regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", size = 284617 }, + { url = "https://files.pythonhosted.org/packages/fc/fd/37868b75eaf63843165f1d2122ca6cb94bfc0271e4428cf58c0616786dce/regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", size = 795023 }, + { url = "https://files.pythonhosted.org/packages/c4/7c/d4cd9c528502a3dedb5c13c146e7a7a539a3853dc20209c8e75d9ba9d1b2/regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", size = 833072 }, + { url = "https://files.pythonhosted.org/packages/4f/db/46f563a08f969159c5a0f0e722260568425363bea43bb7ae370becb66a67/regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", size = 823130 }, + { url = "https://files.pythonhosted.org/packages/db/60/1eeca2074f5b87df394fccaa432ae3fc06c9c9bfa97c5051aed70e6e00c2/regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", size = 796857 }, + { url = "https://files.pythonhosted.org/packages/10/db/ac718a08fcee981554d2f7bb8402f1faa7e868c1345c16ab1ebec54b0d7b/regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", size = 784006 }, + { url = "https://files.pythonhosted.org/packages/c2/41/7da3fe70216cea93144bf12da2b87367590bcf07db97604edeea55dac9ad/regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", size = 781650 }, + { url = "https://files.pythonhosted.org/packages/a7/d5/880921ee4eec393a4752e6ab9f0fe28009435417c3102fc413f3fe81c4e5/regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", size = 789545 }, + { url = "https://files.pythonhosted.org/packages/dc/96/53770115e507081122beca8899ab7f5ae28ae790bfcc82b5e38976df6a77/regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", size = 853045 }, + { url = "https://files.pythonhosted.org/packages/31/d3/1372add5251cc2d44b451bd94f43b2ec78e15a6e82bff6a290ef9fd8f00a/regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", size = 860182 }, + { url = "https://files.pythonhosted.org/packages/ed/e3/c446a64984ea9f69982ba1a69d4658d5014bc7a0ea468a07e1a1265db6e2/regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", size = 787733 }, + { url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122 }, + { url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545 }, + { url = "https://files.pythonhosted.org/packages/89/23/c4a86df398e57e26f93b13ae63acce58771e04bdde86092502496fa57f9c/regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839", size = 482682 }, + { url = "https://files.pythonhosted.org/packages/3c/8b/45c24ab7a51a1658441b961b86209c43e6bb9d39caf1e63f46ce6ea03bc7/regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e", size = 287679 }, + { url = "https://files.pythonhosted.org/packages/7a/d1/598de10b17fdafc452d11f7dada11c3be4e379a8671393e4e3da3c4070df/regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf", size = 284578 }, + { url = "https://files.pythonhosted.org/packages/49/70/c7eaa219efa67a215846766fde18d92d54cb590b6a04ffe43cef30057622/regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b", size = 782012 }, + { url = "https://files.pythonhosted.org/packages/89/e5/ef52c7eb117dd20ff1697968219971d052138965a4d3d9b95e92e549f505/regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0", size = 820580 }, + { url = "https://files.pythonhosted.org/packages/5f/3f/9f5da81aff1d4167ac52711acf789df13e789fe6ac9545552e49138e3282/regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b", size = 809110 }, + { url = "https://files.pythonhosted.org/packages/86/44/2101cc0890c3621b90365c9ee8d7291a597c0722ad66eccd6ffa7f1bcc09/regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef", size = 780919 }, + { url = "https://files.pythonhosted.org/packages/ce/2e/3e0668d8d1c7c3c0d397bf54d92fc182575b3a26939aed5000d3cc78760f/regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48", size = 771515 }, + { url = "https://files.pythonhosted.org/packages/a6/49/1bc4584254355e3dba930a3a2fd7ad26ccba3ebbab7d9100db0aff2eedb0/regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13", size = 696957 }, + { url = "https://files.pythonhosted.org/packages/c8/dd/42879c1fc8a37a887cd08e358af3d3ba9e23038cd77c7fe044a86d9450ba/regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2", size = 768088 }, + { url = "https://files.pythonhosted.org/packages/89/96/c05a0fe173cd2acd29d5e13c1adad8b706bcaa71b169e1ee57dcf2e74584/regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95", size = 774752 }, + { url = "https://files.pythonhosted.org/packages/b5/f3/a757748066255f97f14506483436c5f6aded7af9e37bca04ec30c90ca683/regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9", size = 838862 }, + { url = "https://files.pythonhosted.org/packages/5c/93/c6d2092fd479dcaeea40fc8fa673822829181ded77d294a7f950f1dda6e2/regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f", size = 842622 }, + { url = "https://files.pythonhosted.org/packages/ff/9c/daa99532c72f25051a90ef90e1413a8d54413a9e64614d9095b0c1c154d0/regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b", size = 772713 }, + { url = "https://files.pythonhosted.org/packages/13/5d/61a533ccb8c231b474ac8e3a7d70155b00dfc61af6cafdccd1947df6d735/regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57", size = 261756 }, + { url = "https://files.pythonhosted.org/packages/dc/7b/e59b7f7c91ae110d154370c24133f947262525b5d6406df65f23422acc17/regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983", size = 274110 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[package]] +name = "ruff" +version = "0.9.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/74/6c359f6b9ed85b88df6ef31febce18faeb852f6c9855651dfb1184a46845/ruff-0.9.5.tar.gz", hash = "sha256:11aecd7a633932875ab3cb05a484c99970b9d52606ce9ea912b690b02653d56c", size = 3634177 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/4b/82b7c9ac874e72b82b19fd7eab57d122e2df44d2478d90825854f9232d02/ruff-0.9.5-py3-none-linux_armv6l.whl", hash = "sha256:d466d2abc05f39018d53f681fa1c0ffe9570e6d73cde1b65d23bb557c846f442", size = 11681264 }, + { url = "https://files.pythonhosted.org/packages/27/5c/f5ae0a9564e04108c132e1139d60491c0abc621397fe79a50b3dc0bd704b/ruff-0.9.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38840dbcef63948657fa7605ca363194d2fe8c26ce8f9ae12eee7f098c85ac8a", size = 11657554 }, + { url = "https://files.pythonhosted.org/packages/2a/83/c6926fa3ccb97cdb3c438bb56a490b395770c750bf59f9bc1fe57ae88264/ruff-0.9.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d56ba06da53536b575fbd2b56517f6f95774ff7be0f62c80b9e67430391eeb36", size = 11088959 }, + { url = "https://files.pythonhosted.org/packages/af/a7/42d1832b752fe969ffdbfcb1b4cb477cb271bed5835110fb0a16ef31ab81/ruff-0.9.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7cb2a01da08244c50b20ccfaeb5972e4228c3c3a1989d3ece2bc4b1f996001", size = 11902041 }, + { url = "https://files.pythonhosted.org/packages/53/cf/1fffa09fb518d646f560ccfba59f91b23c731e461d6a4dedd21a393a1ff1/ruff-0.9.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:96d5c76358419bc63a671caac70c18732d4fd0341646ecd01641ddda5c39ca0b", size = 11421069 }, + { url = "https://files.pythonhosted.org/packages/09/27/bb8f1b7304e2a9431f631ae7eadc35550fe0cf620a2a6a0fc4aa3d736f94/ruff-0.9.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:deb8304636ed394211f3a6d46c0e7d9535b016f53adaa8340139859b2359a070", size = 12625095 }, + { url = "https://files.pythonhosted.org/packages/d7/ce/ab00bc9d3df35a5f1b64f5117458160a009f93ae5caf65894ebb63a1842d/ruff-0.9.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:df455000bf59e62b3e8c7ba5ed88a4a2bc64896f900f311dc23ff2dc38156440", size = 13257797 }, + { url = "https://files.pythonhosted.org/packages/88/81/c639a082ae6d8392bc52256058ec60f493c6a4d06d5505bccface3767e61/ruff-0.9.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de92170dfa50c32a2b8206a647949590e752aca8100a0f6b8cefa02ae29dce80", size = 12763793 }, + { url = "https://files.pythonhosted.org/packages/b3/d0/0a3d8f56d1e49af466dc770eeec5c125977ba9479af92e484b5b0251ce9c/ruff-0.9.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d28532d73b1f3f627ba88e1456f50748b37f3a345d2be76e4c653bec6c3e393", size = 14386234 }, + { url = "https://files.pythonhosted.org/packages/04/70/e59c192a3ad476355e7f45fb3a87326f5219cc7c472e6b040c6c6595c8f0/ruff-0.9.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c746d7d1df64f31d90503ece5cc34d7007c06751a7a3bbeee10e5f2463d52d2", size = 12437505 }, + { url = "https://files.pythonhosted.org/packages/55/4e/3abba60a259d79c391713e7a6ccabf7e2c96e5e0a19100bc4204f1a43a51/ruff-0.9.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:11417521d6f2d121fda376f0d2169fb529976c544d653d1d6044f4c5562516ee", size = 11884799 }, + { url = "https://files.pythonhosted.org/packages/a3/db/b0183a01a9f25b4efcae919c18fb41d32f985676c917008620ad692b9d5f/ruff-0.9.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5b9d71c3879eb32de700f2f6fac3d46566f644a91d3130119a6378f9312a38e1", size = 11527411 }, + { url = "https://files.pythonhosted.org/packages/0a/e4/3ebfcebca3dff1559a74c6becff76e0b64689cea02b7aab15b8b32ea245d/ruff-0.9.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2e36c61145e70febcb78483903c43444c6b9d40f6d2f800b5552fec6e4a7bb9a", size = 12078868 }, + { url = "https://files.pythonhosted.org/packages/ec/b2/5ab808833e06c0a1b0d046a51c06ec5687b73c78b116e8d77687dc0cd515/ruff-0.9.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2f71d09aeba026c922aa7aa19a08d7bd27c867aedb2f74285a2639644c1c12f5", size = 12524374 }, + { url = "https://files.pythonhosted.org/packages/e0/51/1432afcc3b7aa6586c480142caae5323d59750925c3559688f2a9867343f/ruff-0.9.5-py3-none-win32.whl", hash = "sha256:134f958d52aa6fdec3b294b8ebe2320a950d10c041473c4316d2e7d7c2544723", size = 9853682 }, + { url = "https://files.pythonhosted.org/packages/b7/ad/c7a900591bd152bb47fc4882a27654ea55c7973e6d5d6396298ad3fd6638/ruff-0.9.5-py3-none-win_amd64.whl", hash = "sha256:78cc6067f6d80b6745b67498fb84e87d32c6fc34992b52bffefbdae3442967d6", size = 10865744 }, + { url = "https://files.pythonhosted.org/packages/75/d9/fde7610abd53c0c76b6af72fc679cb377b27c617ba704e25da834e0a0608/ruff-0.9.5-py3-none-win_arm64.whl", hash = "sha256:18a29f1a005bddb229e580795627d297dfa99f16b30c7039e73278cf6b5f9fa9", size = 10064595 }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, +] + +[[package]] +name = "soupsieve" +version = "2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/ce/fbaeed4f9fb8b2daa961f90591662df6a86c1abf25c548329a86920aedfb/soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", size = 101569 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/c2/fe97d779f3ef3b15f05c94a2f1e3d21732574ed441687474db9d342a7315/soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9", size = 36186 }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "urllib3" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, +] + +[[package]] +name = "verspec" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/44/8126f9f0c44319b2efc65feaad589cadef4d77ece200ae3c9133d58464d0/verspec-0.1.0.tar.gz", hash = "sha256:c4504ca697b2056cdb4bfa7121461f5a0e81809255b41c03dda4ba823637c01e", size = 27123 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl", hash = "sha256:741877d5633cc9464c45a469ae2a31e801e6dbbaa85b9675d481cda100f11c31", size = 19640 }, +] + +[[package]] +name = "virtualenv" +version = "20.29.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/ca/f23dcb02e161a9bba141b1c08aa50e8da6ea25e6d780528f1d385a3efe25/virtualenv-20.29.1.tar.gz", hash = "sha256:b8b8970138d32fb606192cb97f6cd4bb644fa486be9308fb9b63f81091b5dc35", size = 7658028 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/9b/599bcfc7064fbe5740919e78c5df18e5dceb0887e676256a1061bb5ae232/virtualenv-20.29.1-py3-none-any.whl", hash = "sha256:4e4cb403c0b0da39e13b46b1b2476e505cb0046b25f242bee80f62bf990b2779", size = 4282379 }, +] + +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390 }, + { url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389 }, + { url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020 }, + { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393 }, + { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392 }, + { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019 }, + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471 }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449 }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054 }, + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480 }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451 }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057 }, + { url = "https://files.pythonhosted.org/packages/05/52/7223011bb760fce8ddc53416beb65b83a3ea6d7d13738dde75eeb2c89679/watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8", size = 96390 }, + { url = "https://files.pythonhosted.org/packages/9c/62/d2b21bc4e706d3a9d467561f487c2938cbd881c69f3808c43ac1ec242391/watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a", size = 88386 }, + { url = "https://files.pythonhosted.org/packages/ea/22/1c90b20eda9f4132e4603a26296108728a8bfe9584b006bd05dd94548853/watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c", size = 89017 }, + { url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902 }, + { url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380 }, + { url = "https://files.pythonhosted.org/packages/5b/79/69f2b0e8d3f2afd462029031baafb1b75d11bb62703f0e1022b2e54d49ee/watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa", size = 87903 }, + { url = "https://files.pythonhosted.org/packages/e2/2b/dc048dd71c2e5f0f7ebc04dd7912981ec45793a03c0dc462438e0591ba5d/watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e", size = 88381 }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079 }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078 }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076 }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077 }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078 }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077 }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078 }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065 }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070 }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067 }, +] + +[[package]] +name = "zipp" +version = "3.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/50/bad581df71744867e9468ebd0bcd6505de3b275e06f202c2cb016e3ff56f/zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4", size = 24545 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/1a/7e4798e9339adc931158c9d69ecc34f5e6791489d469f5e50ec15e35f458/zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931", size = 9630 }, +] From 542021adaba9c0fba53af4bb8bff5c1e13a87047 Mon Sep 17 00:00:00 2001 From: ItsDrike <itsdrike@protonmail.com> Date: Wed, 12 Feb 2025 21:49:52 +0100 Subject: [PATCH 2/4] Restrict supported version ranges --- pyproject.toml | 9 ++------- uv.lock | 22 ++++++++-------------- 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d9ab067..5cea0a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,13 +33,8 @@ classifiers = [ keywords = ["documentation", "mkdocstrings", "mkdocstrings-handler"] requires-python = ">=3.9" dependencies = [ - # TODO: Look into supported version ranges, for now - # require exact versions to ensure things will work. - "mkdocs>=1.4", - "mkdocs-autorefs>=1.3.0", - "mkdocstrings==0.28.0", - "mkdocstrings-python==1.14.6", - "griffe==1.5.6", + "mkdocstrings-python>=1.14.1,<1.15.0", + "griffe>=1.0.0", "typing-extensions>=4.0", ] diff --git a/uv.lock b/uv.lock index d63125d..6b91416 100644 --- a/uv.lock +++ b/uv.lock @@ -189,14 +189,14 @@ wheels = [ [[package]] name = "griffe" -version = "1.5.6" +version = "1.5.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/88/f0/a001e06c321dfa220103418259afbac50b933eac7a86657a4b572f0517e8/griffe-1.5.6.tar.gz", hash = "sha256:181f6666d5aceb6cd6e2da5a2b646cfb431e47a0da1fda283845734b67e10944", size = 391173 } +sdist = { url = "https://files.pythonhosted.org/packages/59/80/13b6456bfbf8bc854875e58d3a3bad297ee19ebdd693ce62a10fab007e7a/griffe-1.5.7.tar.gz", hash = "sha256:465238c86deaf1137761f700fb343edd8ffc846d72f6de43c3c345ccdfbebe92", size = 391503 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/87/505777c4e5ca9c4fa5ae53fa4b0d5c2ba13a6d55a503a5594e94a2ba9b5a/griffe-1.5.6-py3-none-any.whl", hash = "sha256:b2a3afe497c6c1f952e54a23095ecc09435016293e77af8478ed65df1022a394", size = 128176 }, + { url = "https://files.pythonhosted.org/packages/76/67/b43330ed76f96be098c165338d47ccb952964ed77ba1d075247fbdf05c04/griffe-1.5.7-py3-none-any.whl", hash = "sha256:4af8ec834b64de954d447c7b6672426bb145e71605c74a4e22d510cc79fe7d8b", size = 128294 }, ] [[package]] @@ -477,7 +477,7 @@ wheels = [ [[package]] name = "mkdocstrings-python" -version = "1.14.6" +version = "1.14.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffe" }, @@ -485,9 +485,9 @@ dependencies = [ { name = "mkdocstrings" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f6/fd/3cfa0fc2a724e16262f2de63e13365e726426c44a74fcc3e86b31ff3f1fa/mkdocstrings_python-1.14.6.tar.gz", hash = "sha256:3fb6589491614422d781dacca085c0c5a53a7063af37a2fea5864e24e378b03e", size = 422060 } +sdist = { url = "https://files.pythonhosted.org/packages/86/09/1f14b7e6ca02115e1491f425147d2f218301099db0699a1b40914a125d6e/mkdocstrings_python-1.14.7.tar.gz", hash = "sha256:35100ea5545a9b42155da73de8be74484216031e912feff7a4f6115f206139c7", size = 422162 } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/e4/b4f88e4edd914831b65caa34806012cc00167a7f2dd62e4e407113957b0f/mkdocstrings_python-1.14.6-py3-none-any.whl", hash = "sha256:e0ca11b49ac0f23070afb566245f4ff80ea1700e03c9dbc143d70dbf1cae074e", size = 448699 }, + { url = "https://files.pythonhosted.org/packages/d5/54/46a60fd1e4a696d19e9a47b3eddc025e999edc43ff5cffcc0c84121b6428/mkdocstrings_python-1.14.7-py3-none-any.whl", hash = "sha256:bdcce5544cc2fbee4163a1cf14e218de688849e75ca46c3ece4a28825aac9b41", size = 448699 }, ] [[package]] @@ -496,9 +496,6 @@ version = "0.1.0" source = { editable = "." } dependencies = [ { name = "griffe" }, - { name = "mkdocs" }, - { name = "mkdocs-autorefs" }, - { name = "mkdocstrings" }, { name = "mkdocstrings-python" }, { name = "typing-extensions" }, ] @@ -520,11 +517,8 @@ test = [ [package.metadata] requires-dist = [ - { name = "griffe", specifier = "==1.5.6" }, - { name = "mkdocs", specifier = ">=1.4" }, - { name = "mkdocs-autorefs", specifier = ">=1.3.0" }, - { name = "mkdocstrings", specifier = "==0.28.0" }, - { name = "mkdocstrings-python", specifier = "==1.14.6" }, + { name = "griffe", specifier = ">=1.0.0" }, + { name = "mkdocstrings-python", specifier = ">=1.14.1,<1.15.0" }, { name = "typing-extensions", specifier = ">=4.0" }, ] From 389b624ff7571350f4f881cce7c3328c34dd4973 Mon Sep 17 00:00:00 2001 From: ItsDrike <itsdrike@protonmail.com> Date: Wed, 12 Feb 2025 22:01:34 +0100 Subject: [PATCH 3/4] Add a PyPI publish workflow --- .github/workflows/publish.yml | 83 +++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..7f2d3da --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,83 @@ +name: Publish to PyPI / GitHub + +on: + push: + tags: + - "v*" + +permissions: + contents: read + +env: + PYTHON_VERSION: "3.14" + +jobs: + build: + name: "Build the project" + runs-on: ubuntu-latest + + outputs: + prerelease: ${{ steps.check-version.outputs.prerelease }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup uv + uses: astral-sh/setup-uv@v5 + with: + version: "latest" + python-version: ${{ env.PYTHON_VERSION }} + enable-cache: true + cache-suffix: "publish-py${{ env.python-version }}" + + - name: Install dependencies + run: | + # We only need the project dependencies, no development deps + uv sync --no-default-groups + + - name: Upload build files + uses: actions/upload-artifact@v4 + with: + name: "dist" + path: "dist/" + if-no-files-found: error + retention-days: 5 + + publish-github: + name: "Publish a GitHub release" + needs: build + runs-on: ubuntu-latest + + steps: + - name: Download the distribution files from PR artifact + uses: actions/download-artifact@v4 + with: + name: "dist" + path: "dist/" + + - name: Create Release + uses: ncipollo/release-action@v1 + with: + artifacts: "dist/*" + bodyFile: changelog.txt + draft: false + + publish-pypi: + name: "Publish to PyPI" + needs: build + runs-on: ubuntu-latest + permissions: + # Used to authenticate to PyPI via OIDC. + id-token: write + + steps: + - name: Download the distribution files from PR artifact + uses: actions/download-artifact@v4 + with: + name: "dist" + path: "dist/" + + # This uses PyPI's trusted publishing, so no token is required + - name: Release to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 From 191226aa5ab2ca3817958d6ed33c4493a0a35579 Mon Sep 17 00:00:00 2001 From: ItsDrike <itsdrike@protonmail.com> Date: Wed, 12 Feb 2025 22:02:02 +0100 Subject: [PATCH 4/4] v1.0.0 --- pyproject.toml | 4 ++-- uv.lock | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5cea0a6..b46d470 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "mkdocstrings-python-betterrefs" -version = "0.1.0" +version = "1.0.0" license = "Apache-2.0" description = "Extended mkdocstrings-python handler with better cross-references support" readme = "README.md" @@ -10,7 +10,7 @@ authors = [ ] maintainers = [{ name = "ItsDrike", email = "itsdrike@protonmail.com" }] classifiers = [ - "Development Status :: 3 - Alpha", + "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", diff --git a/uv.lock b/uv.lock index 6b91416..7e7b85e 100644 --- a/uv.lock +++ b/uv.lock @@ -492,7 +492,7 @@ wheels = [ [[package]] name = "mkdocstrings-python-betterrefs" -version = "0.1.0" +version = "1.0.0" source = { editable = "." } dependencies = [ { name = "griffe" },