Claude Code for doctest: Python Documentation Testing — Claude Skills 360 Blog
Blog / AI / Claude Code for doctest: Python Documentation Testing
AI

Claude Code for doctest: Python Documentation Testing

Published: November 14, 2028
Read time: 5 min read
By: Claude Skills 360

Python’s doctest module finds and runs interactive Python examples embedded in docstrings, ensuring documentation stays accurate by executing it as tests. import doctest. Run module: doctest.testmod(m=None, verbose=False, optionflags=0) — tests all docstrings in the current (or given) module; returns (failure_count, test_count). Run file: doctest.testfile(filename, verbose=False, optionflags=0) — runs examples from a .txt or .rst file. Single docstring: doctest.run_docstring_examples(f, globs, verbose=False, name="NoName", optionflags=0). Extract: doctest.DocTestFinder().find(obj) → list of DocTest objects. Execute: doctest.DocTestRunner(verbose=False, optionflags=0).run(test)(failure_count, test_count). Unittest integration: doctest.DocTestSuite(module) and doctest.DocFileSuite(filename) return unittest.TestSuite. Directives (in # doctest: comments): +ELLIPSIS — match ... to any text; +NORMALIZE_WHITESPACE — collapse whitespace differences; +SKIP — skip this example; +IGNORE_EXCEPTION_DETAIL — match exception type only; +DONT_ACCEPT_TRUE_FOR_1 — strict bool/int separation. Option flags combine with |: doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE. Claude Code generates embedded test suites, example validators, API contract checkers, and living documentation verifiers.

CLAUDE.md for doctest

## doctest Stack
- Stdlib: import doctest
- Run:    doctest.testmod()                          # tests current module
-         doctest.testmod(mymodule, verbose=True)
-         doctest.testfile("examples.txt")
- Flags:  doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE
- Pytest: pytest --doctest-modules  (runs all module doctests)
- Note:   Put "if __name__ == '__main__': doctest.testmod()" at end of file

doctest Documentation Testing Pipeline

# app/doctestutil.py — run, collect, report, inject, and validate doctests
from __future__ import annotations

import doctest
import importlib
import io
import sys
import textwrap
import types
import unittest
from dataclasses import dataclass, field
from pathlib import Path


# ─────────────────────────────────────────────────────────────────────────────
# 1. Run helpers
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class DocTestResult:
    name:     str
    passed:   int
    failed:   int
    output:   str

    @property
    def ok(self) -> bool:
        return self.failed == 0

    def __str__(self) -> str:
        status = "PASS" if self.ok else "FAIL"
        return (f"[{status}] {self.name}  "
                f"{self.passed}/{self.passed + self.failed} passed")


DEFAULT_FLAGS = doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE


def run_module_tests(
    module: types.ModuleType | str | None = None,
    verbose: bool = False,
    optionflags: int = DEFAULT_FLAGS,
) -> DocTestResult:
    """
    Run all docstring examples in a module.
    module may be a module object, a module name string, or None (caller's module).

    Example:
        result = run_module_tests("collections")
        print(result)
    """
    if isinstance(module, str):
        module = importlib.import_module(module)

    buf = io.StringIO()
    old_out = sys.stdout
    sys.stdout = buf
    try:
        failure_count, test_count = doctest.testmod(
            module,
            verbose=verbose,
            optionflags=optionflags,
        )
    finally:
        sys.stdout = old_out

    passed = test_count - failure_count
    name = getattr(module, "__name__", str(module))
    return DocTestResult(
        name=name,
        passed=passed,
        failed=failure_count,
        output=buf.getvalue(),
    )


def run_file_tests(
    filename: str | Path,
    verbose: bool = False,
    optionflags: int = DEFAULT_FLAGS,
    globs: dict | None = None,
) -> DocTestResult:
    """
    Run all doctest examples in a plain text or .rst file.

    Example:
        result = run_file_tests("docs/tutorial.rst")
        print(result)
    """
    buf = io.StringIO()
    old_out = sys.stdout
    sys.stdout = buf
    try:
        failure_count, test_count = doctest.testfile(
            str(filename),
            verbose=verbose,
            optionflags=optionflags,
            extraglobs=globs or {},
        )
    finally:
        sys.stdout = old_out

    passed = test_count - failure_count
    return DocTestResult(
        name=str(filename),
        passed=passed,
        failed=failure_count,
        output=buf.getvalue(),
    )


def run_docstring(
    obj: object,
    globs: dict | None = None,
    verbose: bool = False,
    optionflags: int = DEFAULT_FLAGS,
    name: str | None = None,
) -> DocTestResult:
    """
    Run doctest examples from a single function, class, or string.

    Example:
        def add(a, b):
            '''
            >>> add(1, 2)
            3
            '''
            return a + b
        result = run_docstring(add)
        print(result)
    """
    g = {"__builtins__": __builtins__}
    if globs:
        g.update(globs)
    if isinstance(obj, str):
        doc = obj
    else:
        doc = getattr(obj, "__doc__", "") or ""

    obj_name = name or getattr(obj, "__name__", "docstring")
    buf = io.StringIO()
    old_out = sys.stdout
    sys.stdout = buf
    try:
        doctest.run_docstring_examples(
            obj, g, verbose=verbose, name=obj_name,
            optionflags=optionflags,
        )
    finally:
        sys.stdout = old_out
    output = buf.getvalue()
    # run_docstring_examples doesn't return counts; count failures from output
    failed = output.count("Failed example:")
    passed_approx = doc.count(">>> ") - failed
    return DocTestResult(
        name=obj_name,
        passed=max(0, passed_approx),
        failed=failed,
        output=output,
    )


# ─────────────────────────────────────────────────────────────────────────────
# 2. DocTest collection
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class DocTestItem:
    name:       str
    filename:   str
    lineno:     int
    example_count: int
    docstring_snippet: str   # first 80 chars of docstring

    def __str__(self) -> str:
        return (f"{self.name}  [{Path(self.filename).name}:{self.lineno}]  "
                f"{self.example_count} examples")


def collect_doctests(
    module: types.ModuleType | str,
    extraglobs: dict | None = None,
) -> list[DocTestItem]:
    """
    Collect all DocTest objects from a module without running them.

    Example:
        items = collect_doctests("os.path")
        for item in items:
            print(item)
    """
    if isinstance(module, str):
        module = importlib.import_module(module)

    finder = doctest.DocTestFinder()
    tests = finder.find(module, extraglobs=extraglobs)
    items = []
    for test in tests:
        if test.examples:
            items.append(DocTestItem(
                name=test.name,
                filename=test.filename or "<unknown>",
                lineno=test.lineno or 0,
                example_count=len(test.examples),
                docstring_snippet=(test.docstring or "")[:80].replace("\n", " "),
            ))
    return sorted(items, key=lambda i: i.lineno)


# ─────────────────────────────────────────────────────────────────────────────
# 3. Inline doctest builder
# ─────────────────────────────────────────────────────────────────────────────

def make_doctest_suite(
    module: types.ModuleType | str,
    optionflags: int = DEFAULT_FLAGS,
) -> unittest.TestSuite:
    """
    Return a unittest.TestSuite from all doctests in a module.
    Use with unittest.TextTestRunner or pytest's --collect-only.

    Example:
        suite = make_doctest_suite("mymodule")
        runner = unittest.TextTestRunner(verbosity=2)
        runner.run(suite)
    """
    if isinstance(module, str):
        module = importlib.import_module(module)
    return doctest.DocTestSuite(module, optionflags=optionflags)


def make_file_suite(
    filename: str | Path,
    optionflags: int = DEFAULT_FLAGS,
    globs: dict | None = None,
) -> unittest.TestSuite:
    """
    Return a unittest.TestSuite from a doctest text file.

    Example:
        suite = make_file_suite("docs/tutorial.txt")
    """
    return doctest.DocFileSuite(
        str(filename),
        optionflags=optionflags,
        extraglobs=globs or {},
    )


# ─────────────────────────────────────────────────────────────────────────────
# 4. Docstring validator / injector
# ─────────────────────────────────────────────────────────────────────────────

def validate_docstring_examples(
    source: str,
    globs: dict | None = None,
    optionflags: int = DEFAULT_FLAGS,
) -> DocTestResult:
    """
    Parse and run doctest examples from a raw docstring string.
    Useful for validating auto-generated or templated docstrings.

    Example:
        doc = '''
        >>> 1 + 1
        2
        >>> sorted([3, 1, 2])
        [1, 2, 3]
        '''
        result = validate_docstring_examples(doc)
        print(result)
    """
    return run_docstring(source, globs=globs, optionflags=optionflags,
                         name="inline")


def build_expects(fn, *args_list, globs: dict | None = None) -> str:
    """
    Capture actual output for calls and format as doctest expectation strings.
    Useful for bootstrapping new doctests from known-good calls.

    Example:
        snippet = build_expects(sorted, [3,1,2], [5,4])
        print(snippet)
        # >>> sorted([3, 1, 2])
        # [1, 2, 3]
        # >>> sorted([5, 4])
        # [4, 5]
    """
    import traceback as _tb
    lines = []
    g = {"__builtins__": __builtins__, fn.__name__: fn}
    if globs:
        g.update(globs)
    for args in args_list:
        if not isinstance(args, tuple):
            args = (args,)
        call_repr = f"{fn.__name__}({', '.join(repr(a) for a in args)})"
        try:
            result = fn(*args)
            lines.append(f">>> {call_repr}")
            lines.append(repr(result))
        except Exception as e:
            lines.append(f">>> {call_repr}")
            lines.append(f"Traceback (most recent call last):")
            lines.append(f"    ...")
            lines.append(f"{type(e).__name__}: {e}")
        lines.append("")
    return "\n".join(lines)


# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    import tempfile
    print("=== doctest demo ===")

    # ── run_docstring with passing and failing examples ───────────────────────
    print("\n--- run_docstring (passing) ---")

    def multiply(a, b):
        """
        Multiply two numbers.

        >>> multiply(3, 4)
        12
        >>> multiply(2.5, 2)
        5.0
        >>> multiply('ab', 3)
        'ababab'
        """
        return a * b

    result = run_docstring(multiply)
    print(f"  {result}")

    # ── run_docstring with deliberate failure ─────────────────────────────────
    print("\n--- run_docstring (failing example) ---")
    bad_doc = """
    >>> 1 + 1
    3
    """
    result2 = run_docstring(bad_doc, name="bad_example")
    print(f"  {result2}")
    if result2.output:
        print(f"  output snippet: {result2.output[:120]!r}")

    # ── collect_doctests from stdlib module ───────────────────────────────────
    print("\n--- collect_doctests('textwrap') ---")
    items = collect_doctests("textwrap")
    for item in items:
        print(f"  {item}")

    # ── run from text file ────────────────────────────────────────────────────
    print("\n--- run_file_tests ---")
    with tempfile.TemporaryDirectory() as tmp:
        txt = Path(tmp) / "examples.txt"
        txt.write_text(textwrap.dedent("""\
            Math examples
            =============

            >>> 2 ** 10
            1024
            >>> sorted([3, 1, 2])
            [1, 2, 3]
            >>> list(range(3))
            [0, 1, 2]
        """))
        file_result = run_file_tests(txt)
        print(f"  {file_result}")

    # ── build_expects ─────────────────────────────────────────────────────────
    print("\n--- build_expects ---")
    snippet = build_expects(sorted, [3, 1, 2], [5, 4], ("z", "a"))
    for line in snippet.splitlines():
        print(f"  {line}")

    # ── ELLIPSIS directive example ────────────────────────────────────────────
    print("\n--- ELLIPSIS directive ---")
    ellipsis_doc = """
    >>> import datetime; str(datetime.date.today())  # doctest: +ELLIPSIS
    '20...'
    """
    result3 = run_docstring(ellipsis_doc, name="ellipsis_example",
                            optionflags=doctest.ELLIPSIS)
    print(f"  {result3}")

    print("\n=== done ===")

For the pytest alternative — pytest --doctest-modules and pytest --doctest-glob="*.rst" run all doctests alongside the regular test suite, with rich failure output, fixture injection via doctest_namespace, and # doctest: +SKIP support — use pytest for projects that already use pytest as the test runner; doctest.testmod() / DocTestSuite are preferable when you want zero-dependency test running (e.g., inside if __name__ == "__main__":) or when integrating with unittest runners. For the unittest alternative — unittest.TestCase with explicit assertEqual/assertRaises calls provides structured tests with setup/teardown, test isolation, and full failure introspection — use unittest or pytest for complex logic-heavy tests; use doctest for verifying that the examples in your docstrings remain correct, treating the docstring itself as the test specification — the key advantage is that passing doctests are both tests and accurate documentation simultaneously. The Claude Skills 360 bundle includes doctest skill sets covering DocTestResult with run_module_tests()/run_file_tests()/run_docstring() runners, DocTestItem with collect_doctests() finder, make_doctest_suite()/make_file_suite() unittest bridges, and validate_docstring_examples()/build_expects() docstring utilities. Start with the free tier to try documentation testing patterns and doctest pipeline code generation.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free