Skip to main content

Symbol-aware markdown snippet references for pymdownx.snippets

Project description

pymdown-symbolic-snippets

Extension that makes pymdownx.snippets symbol-aware.

Migration

  • Canonical package/distribution name is now pymdown-symbolic-snippets.
  • Canonical Python module path is now pymdown_symbolic_snippets.
  • Compatibility aliases remain for one release: zensical_symbolic_snippets extension key and zensical_code_references import path.
  • Importing zensical_code_references emits a DeprecationWarning.

What this gives you

Markdown usage stays standard:

--8<-- "my_pkg.api:Client.send"

You reference symbols instead of brittle line ranges, so snippets stay correct when code moves.

Backward compatibility

Existing pymdownx.snippets syntax continues to work unchanged. Symbolic resolution is only applied when the target matches a real Python module under module_roots.

--8<-- "docs/intro.md"
--8<-- "docs/intro.md:intro"
--8<-- "src/my_pkg/api.py:12:20"
--8<-- "my_pkg.api:Client.send"

Install

uv add pymdown-symbolic-snippets

Or:

pip install pymdown-symbolic-snippets

zensical is optional. This package works as a Python-Markdown extension without zensical; install zensical only if you want to run Zensical builds.

Why this exists

Raw line ranges are brittle. If code moves, references like file.py:88:121 rot.

This extension allows snippet references by Python symbol and resolves them to real line spans at build time using AST.

Symbol reference format

<module.path>:<symbol>(.<nested>)[:start[:end]]

Examples:

  • my_pkg.api:build_payload
  • my_pkg.api:Client.send
  • my_pkg.config:DEFAULT_TIMEOUT:-1:2

Resolved output is rewritten to standard snippets format:

path/to/file.py:start:end

For method references without selectors, output uses a multi-range selector so the class header and method body are included together:

path/to/file.py:class_header_start:class_header_end,method_start:method_end

Selector behavior

  • If you don't add a selector, the whole symbol is used.
  • For method references with no selector, the selection includes the class declaration line(s) and that method's body, but not other methods in the class (for example, not __init__).
  • :start means "start at this line (relative to the symbol) and go to the end of the symbol."
  • :start:end means "use only this relative line range inside the symbol."
  • Positive numbers are 1-based: 1 is the first line of the symbol.
  • 0 and negative numbers are offsets: 0 is the symbol start, -1 is one line above the symbol start.
  • If start or end goes past the file limits, values are clamped to valid file bounds.
  • If the range is invalid (end < start), resolution fails.

Ecosystem comparison

Research summary across pymdown-extensions, mkdocs, mkdocs-material, and common include plugins:

Tool / project Symbol path include (module:Class.method) AST/introspection-based resolution Generic snippet include Equivalent to this project
pymdownx.snippets No No Yes (file, line ranges, named marker sections) No
mkdocs No No Not in core (delegates to extensions/plugins) No
mkdocs-material No No Yes via upstream pymdownx.snippets No
mkdocstrings/python Partial (API object rendering) Yes (object collection) No (not a general snippets include engine) Partial
mkdocs-codeinclude-plugin No No Yes (token/brace-targeted blocks) No
mkdocs-include-markdown-plugin No No Yes (delimiter-based includes) No
pymdown-symbolic-snippets (this project) Yes Yes Yes (rewrites to pymdownx.snippets line spans) Yes

Bottom line: existing options either slice by lines/markers or render API docs; none provide first-class symbol-addressed snippet transclusion in the same workflow as pymdownx.snippets.

Python-Markdown configuration

Use it as a normal Python-Markdown extension, placed before pymdownx.snippets:

import markdown

md = markdown.Markdown(
    extensions=[
        "pymdown_symbolic_snippets",
        "pymdownx.snippets",
    ],
    extension_configs={
        "pymdown_symbolic_snippets": {
            "module_roots": ["src"],
            "fail_on_unresolved": True,
        },
        "pymdownx.snippets": {
            "base_path": ["src"],
            "check_paths": True,
        },
    },
)

Zensical configuration (site.toml)

[project.markdown_extensions.pymdown_symbolic_snippets]
module_roots = ["src"]
fail_on_unresolved = true

[project.markdown_extensions.pymdownx.highlight]
anchor_linenums = true
line_spans = "__span"
pygments_lang_class = true

[project.markdown_extensions.pymdownx.snippets]
base_path = ["src"]
check_paths = true

[project.markdown_extensions.pymdownx.superfences]

If you define project.markdown_extensions explicitly, include all extensions you rely on. Leaving out pymdownx.superfences/pymdownx.highlight causes fenced blocks to render as plain text.

Included proof project

This repo includes a working Zensical example that references this package's own source to prove behavior:

  • Config: examples/symbolic-snippets/site.toml
  • Docs page: examples/symbolic-snippets/docs/index.md

Build it:

uv run zensical build --config-file examples/symbolic-snippets/site.toml

Tiny output example (from examples/symbolic-snippets/site/index.html):

def parse_symbolic_reference(value: str) -> SymbolicReference | None:
    if ":" not in value:
        return None

Tests

Run:

uv run pytest

The suite includes parser/resolver tests plus an E2E Zensical build test that asserts resolved symbols are rendered in generated HTML.

Release automation

GitHub Actions is configured to publish to PyPI on strict semver tags:

  • CI workflow: .github/workflows/ci.yml
  • Release workflow: .github/workflows/release.yml
  • Trigger: push tag matching vX.Y.Z
  • Guardrails: tag must be strict semver and must match project.version in pyproject.toml
  • Gate: test matrix (3.13, 3.14) must pass before publish

One-time GitHub setup for trusted publishing:

  1. Create environment pypi in repository settings.
  2. Configure PyPI Trusted Publisher for this repository/workflow.

Release command:

git tag v0.1.0
git push upstream v0.1.0

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pymdown_symbolic_snippets-0.1.0.tar.gz (7.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pymdown_symbolic_snippets-0.1.0-py3-none-any.whl (9.4 kB view details)

Uploaded Python 3

File details

Details for the file pymdown_symbolic_snippets-0.1.0.tar.gz.

File metadata

  • Download URL: pymdown_symbolic_snippets-0.1.0.tar.gz
  • Upload date:
  • Size: 7.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.8 {"installer":{"name":"uv","version":"0.10.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"NixOS","version":"26.05","id":"yarara","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for pymdown_symbolic_snippets-0.1.0.tar.gz
Algorithm Hash digest
SHA256 4079a389cbfd484dfe9a4bc082f8ca6c58af01e055421811e72b43cd4d86e631
MD5 b4e2b10add509f4a07158ff26c1e6202
BLAKE2b-256 93c3cb3b7787d3c80edac5c344799acdb999ced4f6243dce80d9455532f410e5

See more details on using hashes here.

File details

Details for the file pymdown_symbolic_snippets-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: pymdown_symbolic_snippets-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 9.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.8 {"installer":{"name":"uv","version":"0.10.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"NixOS","version":"26.05","id":"yarara","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for pymdown_symbolic_snippets-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f1a452e4b56174fd004f3c9ef89ff554cb0e1e6969ceb85eb1b3e6b9d8213841
MD5 6f906b10dc2548a5640412b3763a351d
BLAKE2b-256 802101d82f08886f5c23201e8299ed3d9033893d8c234e14132efca5370bc60d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page