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/generator.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/generator.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 = []

    # Get title from schema or metadata
    title = self._get_title(contract)
    version = contract.versions.get("schema") or contract.schema.get("version")

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

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

    # Schema section
    if include_schema and contract.schema:
        parts.append(self.generate_schema_section(contract.schema))

    # Coercion rules section
    if include_coercions and contract.coercion_rules:
        parts.append(self.generate_coercion_section(contract.coercion_rules))

    # Validation rules section
    if include_validations and contract.validation_rules:
        parts.append(self.generate_validation_section(contract.validation_rules))

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

    # Footer for HTML
    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/generator.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 ""

    # Build table rows for fields
    headers = ["Field", "Type", "Required", "Description", "Constraints"]
    rows = []

    for field_name, field_def in properties.items():
        row = self._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: dict[str, Any]) -> str

Generate documentation for coercion rules.

Parameters:

Name Type Description Default
rules dict[str, Any]

Coercion rules dictionary

required

Returns:

Type Description
str

Formatted coercion documentation

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

    Args:
        rules: Coercion rules dictionary

    Returns:
        Formatted coercion documentation
    """
    # Filter out version key
    coercion_rules = {k: v for k, v in rules.items() if k != "version"}

    if not coercion_rules:
        return ""

    headers = ["Field", "Coercion", "Description"]
    rows = []

    for field, coercion in coercion_rules.items():
        if isinstance(coercion, str):
            coercion_name = coercion
            description = self._get_coercion_description(coercion_name)
        elif isinstance(coercion, dict):
            coercion_name = coercion.get("type", str(coercion))
            description = coercion.get(
                "description", self._get_coercion_description(coercion_name)
            )
        else:
            coercion_name = str(coercion)
            description = ""

        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: dict[str, Any]) -> str

Generate documentation for validation rules.

Parameters:

Name Type Description Default
rules dict[str, Any]

Validation rules dictionary

required

Returns:

Type Description
str

Formatted validation documentation

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

    Args:
        rules: Validation rules dictionary

    Returns:
        Formatted validation documentation
    """
    # Filter out version key
    validation_rules = {k: v for k, v in rules.items() if k != "version"}

    if not validation_rules:
        return ""

    headers = ["Field", "Validation", "Configuration"]
    rows = []

    for field, validations in validation_rules.items():
        if isinstance(validations, dict):
            for val_name, val_config in validations.items():
                config_str = self._format_config(val_config)
                rows.append([f"`{field}`", f"`{val_name}`", config_str])
        elif isinstance(validations, str):
            rows.append([f"`{field}`", f"`{validations}`", ""])
        elif isinstance(validations, list):
            for val in validations:
                rows.append([f"`{field}`", f"`{val}`", ""])

    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/generator.py
def generate_metadata_section(self, contract: ContractMetadata) -> str:
    """
    Generate documentation for contract metadata.

    Args:
        contract: ContractMetadata object

    Returns:
        Formatted metadata documentation
    """
    parts = []

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

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

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

    # Other metadata
    other_metadata = {
        k: v
        for k, v in contract.metadata.items()
        if k not in ["ownership", "governance_rules", "version"]
    }
    if other_metadata:
        other_content = self._format_metadata_dict(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:8000/api/v1/contracts/{contract_id}/docs

The response is a Markdown string.

Import

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

See also