Skip to content

Docs Generator

The docs generator transforms a ContractMetadata object into human-readable documentation. The default output format is Markdown, making it easy to pipe generated docs into static site generators, pull requests, or wikis.

Classes

DocsGenerator

DocsGenerator(renderer: DocsRenderer | None = None)

Generate documentation from data contracts.

Transforms ContractMetadata objects into human-readable documentation in various formats (Markdown, HTML, etc.).

Example

from pycharter import parse_contract_file from pycharter.docs_generator import DocsGenerator

contract = parse_contract_file("contract.yaml") generator = DocsGenerator() docs = generator.generate(contract) print(docs)

Parameters:

Name Type Description Default
renderer DocsRenderer | None

Renderer to use for output format. Defaults to MarkdownRenderer.

None
Source code in src/pycharter/docs_generator/_docs_gen_core.py
def __init__(self, renderer: DocsRenderer | None = None):
    """Initialize the documentation generator.

    Args:
        renderer: Renderer to use for output format. Defaults to MarkdownRenderer.
    """
    self.renderer = renderer or MarkdownRenderer()

generate

generate(
    contract: ContractMetadata,
    include_schema: bool = True,
    include_coercions: bool = True,
    include_validations: bool = True,
    include_metadata: bool = True,
) -> str

Generate full documentation for a contract.

Parameters:

Name Type Description Default
contract ContractMetadata

ContractMetadata object to document.

required
include_schema bool

Include schema field documentation.

True
include_coercions bool

Include coercion rules documentation.

True
include_validations bool

Include validation rules documentation.

True
include_metadata bool

Include metadata/ownership documentation.

True

Returns:

Type Description
str

Generated documentation as string.

Source code in src/pycharter/docs_generator/_docs_gen_core.py
def generate(
    self,
    contract: ContractMetadata,
    include_schema: bool = True,
    include_coercions: bool = True,
    include_validations: bool = True,
    include_metadata: bool = True,
) -> str:
    """Generate full documentation for a contract.

    Args:
        contract: ContractMetadata object to document.
        include_schema: Include schema field documentation.
        include_coercions: Include coercion rules documentation.
        include_validations: Include validation rules documentation.
        include_metadata: Include metadata/ownership documentation.

    Returns:
        Generated documentation as string.
    """
    parts: list[str] = []

    schema = getattr(contract, "json_schema", None)
    title = self._get_title(contract)
    version = contract.versions.get("json_schema") or (
        schema.get("version") if schema else None
    )

    parts.append(self.renderer.render_header(title, version))

    description = (schema or {}).get("description") or (
        contract.metadata or {}
    ).get("description")
    if description:
        parts.append(self.renderer.render_description(description))

    if include_schema and schema:
        parts.append(self.generate_schema_section(schema))

    if include_coercions and contract.coercion_rules:
        parts.append(self.generate_coercion_section(contract.coercion_rules))

    if include_validations and contract.validation_rules:
        parts.append(self.generate_validation_section(contract.validation_rules))

    if include_metadata and contract.metadata:
        parts.append(self.generate_metadata_section(contract))

    if hasattr(self.renderer, "render_footer"):
        parts.append(self.renderer.render_footer())

    return "".join(parts)

generate_schema_section

generate_schema_section(schema: dict[str, Any]) -> str

Generate documentation for schema fields.

Parameters:

Name Type Description Default
schema dict[str, Any]

JSON Schema dictionary.

required

Returns:

Type Description
str

Formatted schema documentation.

Source code in src/pycharter/docs_generator/_docs_gen_core.py
def generate_schema_section(self, schema: dict[str, Any]) -> str:
    """Generate documentation for schema fields.

    Args:
        schema: JSON Schema dictionary.

    Returns:
        Formatted schema documentation.
    """
    properties = schema.get("properties", {})
    required = set(schema.get("required", []))

    if not properties:
        return ""

    headers = ["Field", "Type", "Required", "Description", "Constraints"]
    rows = []

    for field_name, field_def in properties.items():
        row = build_field_row(field_name, field_def, field_name in required)
        rows.append(row)

    table = self.renderer.render_table(headers, rows)
    return self.renderer.render_section("Schema Fields", table)

generate_coercion_section

generate_coercion_section(rules: Any) -> str

Generate documentation for coercion rules.

Parameters:

Name Type Description Default
rules Any

Canonical coercion-rule payload ({"rules": [...]}) or an already-extracted {target: operation_name} mapping. The legacy {field: operation} direct mapping is rejected via the canonical extractor.

required

Returns:

Type Description
str

Formatted coercion documentation.

Source code in src/pycharter/docs_generator/_docs_gen_core.py
def generate_coercion_section(self, rules: Any) -> str:
    """Generate documentation for coercion rules.

    Args:
        rules: Canonical coercion-rule payload (``{"rules": [...]}``)
            or an already-extracted ``{target: operation_name}``
            mapping. The legacy ``{field: operation}`` direct
            mapping is rejected via the canonical extractor.

    Returns:
        Formatted coercion documentation.
    """
    from pycharter.semantic.contracts.rules_canonical import to_rules_dict

    if not rules:
        return ""
    if isinstance(rules, dict) and "rules" not in rules:
        mapping = {k: v for k, v in rules.items() if k != "version"}
    else:
        mapping = to_rules_dict(rules, kind="coercion")
    if not mapping:
        return ""

    headers = ["Field", "Coercion", "Description"]
    rows: list[list[str]] = []

    for field, coercion_name in mapping.items():
        description = get_coercion_description(coercion_name)
        rows.append([f"`{field}`", f"`{coercion_name}`", description])

    content = self.renderer.render_description(
        "Coercions are applied before validation to transform incoming data."
    )
    content += self.renderer.render_table(headers, rows)
    return self.renderer.render_section("Coercion Rules", content)

generate_validation_section

generate_validation_section(rules: Any) -> str

Generate documentation for validation rules.

Parameters:

Name Type Description Default
rules Any

Canonical validation-rule payload ({"rules": [...]}) or an already-extracted {target: {assertion: parameters}} mapping. The legacy {field: {assertion: parameters}} direct mapping is rejected via the canonical extractor.

required

Returns:

Type Description
str

Formatted validation documentation.

Source code in src/pycharter/docs_generator/_docs_gen_core.py
def generate_validation_section(self, rules: Any) -> str:
    """Generate documentation for validation rules.

    Args:
        rules: Canonical validation-rule payload
            (``{"rules": [...]}``) or an already-extracted
            ``{target: {assertion: parameters}}`` mapping. The
            legacy ``{field: {assertion: parameters}}`` direct
            mapping is rejected via the canonical extractor.

    Returns:
        Formatted validation documentation.
    """
    from pycharter.semantic.contracts.rules_canonical import to_rules_dict

    if not rules:
        return ""
    if isinstance(rules, dict) and "rules" not in rules:
        mapping = {k: v for k, v in rules.items() if k != "version"}
    else:
        mapping = to_rules_dict(rules, kind="validation")
    if not mapping:
        return ""

    headers = ["Field", "Validation", "Configuration"]
    rows: list[list[str]] = []

    for field, validations in mapping.items():
        for val_name, val_config in validations.items():
            config_str = format_config(val_config)
            rows.append([f"`{field}`", f"`{val_name}`", config_str])

    content = self.renderer.render_description(
        "Validations are applied after coercion to ensure data meets requirements."
    )
    content += self.renderer.render_table(headers, rows)
    return self.renderer.render_section("Validation Rules", content)

generate_metadata_section

generate_metadata_section(
    contract: ContractMetadata,
) -> str

Generate documentation for contract metadata.

Parameters:

Name Type Description Default
contract ContractMetadata

ContractMetadata object.

required

Returns:

Type Description
str

Formatted metadata documentation.

Source code in src/pycharter/docs_generator/_docs_gen_core.py
def generate_metadata_section(self, contract: ContractMetadata) -> str:
    """Generate documentation for contract metadata.

    Args:
        contract: ContractMetadata object.

    Returns:
        Formatted metadata documentation.
    """
    parts: list[str] = []

    if contract.ownership:
        ownership_content = format_ownership(self.renderer, contract.ownership)
        parts.append(self.renderer.render_section("Ownership", ownership_content))

    if contract.governance_rules:
        governance_content = format_governance(
            self.renderer, contract.governance_rules
        )
        parts.append(
            self.renderer.render_section("Governance Rules", governance_content)
        )

    if contract.versions:
        version_content = format_versions(self.renderer, contract.versions)
        parts.append(
            self.renderer.render_section("Version Information", version_content)
        )

    other_metadata = {
        k: v
        for k, v in contract.metadata.items()
        if k not in ["ownership", "governance_rules", "version"]
    }
    if other_metadata:
        other_content = format_metadata_dict(self.renderer, other_metadata)
        parts.append(
            self.renderer.render_section("Additional Metadata", other_content)
        )

    return "".join(parts)

MarkdownRenderer

Render documentation as Markdown.

render_header

render_header(
    title: str, version: str | None = None
) -> str

Render document header with title and optional version.

Source code in src/pycharter/docs_generator/renderers.py
def render_header(self, title: str, version: str | None = None) -> str:
    """Render document header with title and optional version."""
    header = f"# {title}\n\n"
    if version:
        header += f"**Version:** `{version}`\n\n"
    return header

render_section

render_section(title: str, content: str) -> str

Render a section with title and content.

Source code in src/pycharter/docs_generator/renderers.py
def render_section(self, title: str, content: str) -> str:
    """Render a section with title and content."""
    return f"## {title}\n\n{content}\n\n"

render_subsection

render_subsection(title: str, content: str) -> str

Render a subsection with title and content.

Source code in src/pycharter/docs_generator/renderers.py
def render_subsection(self, title: str, content: str) -> str:
    """Render a subsection with title and content."""
    return f"### {title}\n\n{content}\n\n"

render_table

render_table(
    headers: list[str],
    rows: list[list[str | None]],
    caption: str | None = None,
) -> str

Render a Markdown table.

Source code in src/pycharter/docs_generator/renderers.py
def render_table(
    self,
    headers: list[str],
    rows: list[list[str | None]],
    caption: str | None = None,
) -> str:
    """Render a Markdown table."""
    if not headers or not rows:
        return ""

    lines = []
    if caption:
        lines.append(f"*{caption}*\n")

    # Header row
    lines.append("| " + " | ".join(headers) + " |")
    # Separator
    lines.append("| " + " | ".join(["---"] * len(headers)) + " |")
    # Data rows
    for row in rows:
        # Ensure row has same length as headers
        padded_row = row + [""] * (len(headers) - len(row))
        lines.append("| " + " | ".join(padded_row[: len(headers)]) + " |")

    return "\n".join(lines) + "\n"

render_code_block

render_code_block(code: str, language: str = '') -> str

Render a fenced code block.

Source code in src/pycharter/docs_generator/renderers.py
def render_code_block(self, code: str, language: str = "") -> str:
    """Render a fenced code block."""
    return f"```{language}\n{code}\n```\n"

render_list

render_list(items: list[str], ordered: bool = False) -> str

Render a list of items.

Source code in src/pycharter/docs_generator/renderers.py
def render_list(self, items: list[str], ordered: bool = False) -> str:
    """Render a list of items."""
    if not items:
        return ""
    if ordered:
        return "\n".join(f"{i + 1}. {item}" for i, item in enumerate(items)) + "\n"
    return "\n".join(f"- {item}" for item in items) + "\n"

render_description

render_description(text: str) -> str

Render description/paragraph text.

Source code in src/pycharter/docs_generator/renderers.py
def render_description(self, text: str) -> str:
    """Render description/paragraph text."""
    return f"{text}\n\n"

render_badge

render_badge(label: str, value: str) -> str

Render a badge as inline code.

Source code in src/pycharter/docs_generator/renderers.py
def render_badge(self, label: str, value: str) -> str:
    """Render a badge as inline code."""
    return f"`{label}: {value}`"

render_key_value

render_key_value(key: str, value: Any) -> str

Render a key-value pair.

Source code in src/pycharter/docs_generator/renderers.py
def render_key_value(self, key: str, value: Any) -> str:
    """Render a key-value pair."""
    if isinstance(value, dict | list):
        return f"**{key}:**\n{self.render_code_block(str(value), 'json')}"
    return f"**{key}:** {value}\n"

Quick start

from pycharter import parse_contract_file
from pycharter.docs_generator import DocsGenerator

# Parse a contract
contract = parse_contract_file("contracts/orders.yaml")

# Generate Markdown documentation
generator = DocsGenerator()
docs = generator.generate(contract)
print(docs)

Selective output

Control which sections are included:

docs = generator.generate(
    contract,
    include_schema=True,
    include_coercions=True,
    include_validations=True,
    include_metadata=True,
)

Saving to a file

from pathlib import Path

docs = generator.generate(contract)
Path("docs/contracts/orders.md").write_text(docs)

Generating docs for all contracts

from pathlib import Path
from pycharter import parse_contract_file
from pycharter.docs_generator import DocsGenerator

generator = DocsGenerator()
output_dir = Path("docs/contracts")
output_dir.mkdir(parents=True, exist_ok=True)

for contract_file in Path("contracts").glob("*.yaml"):
    contract = parse_contract_file(str(contract_file))
    docs = generator.generate(contract)
    out = output_dir / f"{contract_file.stem}.md"
    out.write_text(docs)
    print(f"Generated {out}")

Custom renderer

Implement DocsRenderer to produce HTML, RST, or any other format:

from pycharter.docs_generator.renderers import DocsRenderer
from pycharter.docs_generator import DocsGenerator


class HtmlRenderer(DocsRenderer):
    def render_header(self, title: str, version: str | None) -> str:
        ver = f" <small>v{version}</small>" if version else ""
        return f"<h1>{title}{ver}</h1>"

    def render_description(self, description: str) -> str:
        return f"<p>{description}</p>"

    # ... implement remaining methods


generator = DocsGenerator(renderer=HtmlRenderer())
html = generator.generate(contract)

REST API

Generate documentation for a stored contract via the REST API:

curl http://localhost:8002/api/v1/ops/docs/{contract_name}/{contract_version}

The response is a Markdown string.

Import

from pycharter.docs_generator import DocsGenerator
from pycharter.docs_generator.renderers import MarkdownRenderer, DocsRenderer

See also