Docs Generator - PyCharter
Description: Data Contract Management, ETL Pipelines, and Quality Assurance for Python

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`

    33
    34
    35
    36
    37
    38
    39
    40
    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`

    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    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`

    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    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`

    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    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`

    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    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`

    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    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`

    53
    54
    55
    56
    57
    58
    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`

    60
    61
    62
    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`

    64
    65
    66
    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`

    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    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`

    94
    95
    96
    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`

     98
     99
    100
    101
    102
    103
    104
    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`

    106
    107
    108
    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`

    110
    111
    112
    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`

    114
    115
    116
    117
    118
    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

- Contract Parser
- Data Contracts & Validation tutorial