Skip to content

Python API Reference

Reference documentation for python-mdma, generated from docstrings in the mdma-python repository.

mdma — a typed Markdown templating format.

See https://github.com/ (project README) for the full language specification.

MdmaError

Bases: Exception

Base class for all errors raised while parsing or rendering an .mdma file.

Source code in mdma/errors.py
class MdmaError(Exception):
    """Base class for all errors raised while parsing or rendering an .mdma file."""

MdmaSyntaxError

Bases: MdmaError

The .mdma source does not conform to the grammar.

Source code in mdma/errors.py
class MdmaSyntaxError(MdmaError):
    """The .mdma source does not conform to the grammar."""

MissingInputError

Bases: MdmaError

A required input (no default) was not supplied at render time.

Source code in mdma/errors.py
class MissingInputError(MdmaError):
    """A required input (no default) was not supplied at render time."""

    def __init__(self, name: str) -> None:
        self.name = name
        super().__init__(f"MissingInput: {name}")

MdmaTypeError

Bases: MdmaError

An input value's runtime type does not match its declared type.

Source code in mdma/errors.py
class MdmaTypeError(MdmaError):
    """An input value's runtime type does not match its declared type."""

    def __init__(self, expected: str, actual: str) -> None:
        self.expected = expected
        self.actual = actual
        super().__init__(f"TypeError: expected {expected}, got {actual}")

MdmaReferenceError

Bases: MdmaError

A block or variable reference could not be resolved.

Source code in mdma/errors.py
class MdmaReferenceError(MdmaError):
    """A block or variable reference could not be resolved."""

    def __init__(self, message: str) -> None:
        super().__init__(message)

    @classmethod
    def forward_block(cls, name: str) -> "MdmaReferenceError":
        return cls(f"ReferenceError: block '{name}' not yet rendered")

    @classmethod
    def undefined(cls, name: str) -> "MdmaReferenceError":
        return cls(f"ReferenceError: '{name}' is not defined")

FilterError

Bases: MdmaError

A filter was applied to a value of the wrong type.

Source code in mdma/errors.py
class FilterError(MdmaError):
    """A filter was applied to a value of the wrong type."""

    def __init__(self, filter_name: str, expected_type: str) -> None:
        self.filter_name = filter_name
        self.expected_type = expected_type
        super().__init__(f"FilterError: '{filter_name}' expects {expected_type}")

DuplicateNameError

Bases: MdmaError

Two items in a <multiple:> block computed the same <name:> value.

Source code in mdma/errors.py
class DuplicateNameError(MdmaError):
    """Two items in a `<multiple:>` block computed the same `<name:>` value."""

    def __init__(self, computed_name: str, block_name: str) -> None:
        self.computed_name = computed_name
        self.block_name = block_name
        super().__init__(f"DuplicateName: '{computed_name}' in block '{block_name}'")

render(source, inputs=None)

Render an .mdma source string against an inputs object.

Returns a dict mapping each block name to its rendered string. A <multiple:> block renders to a list of strings, or -- if it also declares <name:> -- to a dict keyed by each item's computed name.

Source code in mdma/renderer.py
def render(source: str, inputs: Dict[str, Any] = None) -> Dict[str, RenderedValue]:
    """Render an .mdma source string against an inputs object.

    Returns a dict mapping each block name to its rendered string. A
    `<multiple:>` block renders to a list of strings, or -- if it also
    declares `<name:>` -- to a dict keyed by each item's computed name.
    """
    template = parse_file(source)
    resolved_inputs = validate_inputs(template.inputs, inputs or {})
    all_block_names = {block.name for block in template.blocks}

    rendered: Dict[str, RenderedValue] = {}
    for block in template.blocks:
        scope_base = Scope(rendered, resolved_inputs, all_block_names)
        if block.multiple_var:
            items = resolved_inputs.get(block.multiple_source) or []
            if block.name_expr is not None:
                named_results: Dict[str, str] = {}
                for item in items:
                    item_scope = scope_base.child({block.multiple_var: item})
                    computed_name = _stringify_name(evaluate(block.name_expr, item_scope))
                    if computed_name in named_results:
                        raise DuplicateNameError(computed_name, block.name)
                    named_results[computed_name] = _render_nodes(block.body, item_scope)
                rendered[block.name] = named_results
            else:
                results = []
                for item in items:
                    item_scope = scope_base.child({block.multiple_var: item})
                    results.append(_render_nodes(block.body, item_scope))
                rendered[block.name] = results
        else:
            rendered[block.name] = _render_nodes(block.body, scope_base)
    return rendered

render_file(path, inputs=None)

Read path as UTF-8 and render it. Equivalent to render(Path(path).read_text(), inputs).

Source code in mdma/renderer.py
def render_file(path: Union[str, os.PathLike], inputs: Dict[str, Any] = None) -> Dict[str, RenderedValue]:
    """Read `path` as UTF-8 and render it. Equivalent to `render(Path(path).read_text(), inputs)`."""
    return render(Path(path).read_text(encoding="utf-8"), inputs)

write_output(result, output_dir, block=None)

Write one or all rendered blocks from a render() result to .md files.

block=None (default) writes every top-level block; block="name" writes only that one. A string-valued block is written to {output_dir}/{block}.md. A <multiple:> block (list or <name:>-keyed dict) is written to {output_dir}/{block}/, one file per item -- {name}.md if the block declared <name:>, otherwise {index}.md.

Raises MdmaReferenceError if block isn't a key in result.

Source code in mdma/output.py
def write_output(
    result: Dict[str, RenderedValue],
    output_dir: Union[str, os.PathLike],
    block: Optional[str] = None,
) -> List[Path]:
    """Write one or all rendered blocks from a `render()` result to `.md` files.

    `block=None` (default) writes every top-level block; `block="name"` writes
    only that one. A string-valued block is written to `{output_dir}/{block}.md`.
    A `<multiple:>` block (list or `<name:>`-keyed dict) is written to
    `{output_dir}/{block}/`, one file per item -- `{name}.md` if the block
    declared `<name:>`, otherwise `{index}.md`.

    Raises `MdmaReferenceError` if `block` isn't a key in `result`.
    """
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)

    if block is not None:
        if block not in result:
            raise MdmaReferenceError(f"ReferenceError: block '{block}' not found in render result")
        return _write_block(block, result[block], output_path)

    written: List[Path] = []
    for name, value in result.items():
        written.extend(_write_block(name, value, output_path))
    return written