API reference#
Report#
- class MarkdownReport[source]#
Bases:
objectBuild a Markdown document.
Provides a fluent interface for building markdown documents with various content types including headings, text, tables, lists, code blocks, and more. Supports Jinja2 template rendering for dynamic content generation.
Method chaining for fluent report building
Jinja2 template support for dynamic content
Polars DataFrame integration for tables and CSV exports
Automatic formatting of numeric data with configurable precision
Numbered figures and captions
Portable semantic callouts
Support for nested lists and various markdown elements
Export to file or string rendering
Example
report = (MarkdownReport() .frontmatter(title="My Report", author="John Doe", date="2024-06-26") .frontmatter({"description": "A comprehensive analysis\nwith multiple sections"}) .directive("class", "title") .title("My Report") .horizontal_rule() .directive("class", "segue") .heading("Overview") .table_of_contents() .heading("Heading level 2") .text(["Paragraph 1", "Paragraph 2"]) .heading("Heading level 3", level=3) .table(df, title="Data Summary") .bullet_list(["Point 1", ["Sub-point 1", "Sub-point 2"], "Point 2"]) .numbered_list(["Step 1", "Step 2"]) .code_block("print('Hello, World!')", language="python", title="Example Code") .horizontal_rule() .text("Report generated on {{date}}", params={"date": "2024-06-26"}) .save("report.md") ) print(report)
Every content method returns the report itself, so calls chain. Content is held as a Markdown syntax tree rather than as text, so
renderis what serializes it; a report can be rendered repeatedly and keeps building afterwards.- __init__(anchor_style=HeadingAnchorStyle.IMPLICIT)[source]#
Create an empty report with its own parser and no frontmatter.
- Parameters:
anchor_style (HeadingAnchorStyle) – How each heading’s anchor is written into the rendered document. The default writes nothing and relies on the anchor the renderer derives from the heading text, which is what
table_of_contentslinks to; passHeadingAnchorStyle.HTMLorHeadingAnchorStyle.ATTRIBUTEfor a renderer that derives none.
Example
report = MarkdownReport(anchor_style=HeadingAnchorStyle.HTML)
- append(block)[source]#
Append a block’s content to this report.
This is the extension point behind
table,code_block, andtable_of_contents, and the way to add a block of your own: any object with a__report__method satisfiesReportBlock.A block implementing
__resolve__(aDeferredReportBlock) is stored as a placeholder and resolved duringrender, once the whole document is known; every other block contributes its content immediately.Example
@dataclass(frozen=True) class Callout: message: str def __report__(self, report: MarkdownReport) -> BlockContent: return f"> **Note:** {self.message}" report.append(Callout("Numbers are provisional."))
- copy()[source]#
Return an independent report holding the same content and metadata.
Content, frontmatter, and parser state are independent, so appending to the copy never affects this report. Use it to build several documents from a shared preamble.
Example
preamble = MarkdownReport().title("Weekly Report").table_of_contents() for team in teams: preamble.copy().heading(team.name).table(team.metrics).save(f"{team.name}.md")
- __add__(block)[source]#
Return a copy of this report with a block appended, leaving it unchanged.
Example
summary = base + Callout("All checks passed") # base is untouched
- __iadd__(block)[source]#
Append a block to this report in place.
Example
report += Callout("All checks passed")
- frontmatter(data=None, **kwargs)[source]#
Merge YAML frontmatter fields into the report metadata.
Fields accumulate across calls and later values win, so frontmatter can be set up front and amended once results are known. The block is emitted at the top of the document by
render, in insertion order, and is omitted entirely when no fields were set.- Parameters:
Example
report.frontmatter(title="Q3 Review", author="Asif") report.frontmatter({"table-of-contents": True}) # key needs the mapping form
- markdown(content, params=None)[source]#
Parse and append raw Markdown content.
The escape hatch for Markdown the other methods don’t build: block quotes, footnotes, images, or a whole section held as a string. Content is parsed, not inserted verbatim, so it must be valid Markdown; use
raw_tokenviaappendfor text that must survive untouched.- Parameters:
Example
report.markdown("> Quoted, with an ") report.markdown("Owner: {{name}}", params={"name": "Asif"})
- directive(name, value=None)[source]#
Append a smolslides HTML-comment directive.
Directives are HTML comments, so they are invisible to Markdown renderers that don’t understand them.
- Parameters:
Example
report.directive("class", "title") # <!-- _class: title --> report.directive("paginate") # <!-- _paginate -->
- title(text, params=None)[source]#
Append an H1 heading.
Shorthand for
heading(text, level=1). Headings added by any method are whattable_of_contentslater collects.Example
report.title("Q3 Review")
- heading(text, level=2, params=None)[source]#
Append a heading at a level from one through six.
Inline Markdown in the text is parsed, so a heading can carry emphasis or a link. Every heading becomes an entry in
table_of_contents, nested by its level and linked to the heading’s anchor — headings repeating the same text are numbered apart, asfindingsandfindings-1.- Parameters:
- Raises:
ValueError – if level is outside the Markdown heading range.
Example
report.heading("Findings") report.heading("Region: {{region}}", level=3, params={"region": "EMEA"})
- text(content, params=None)[source]#
Parse and append one or more Markdown text blocks.
A list appends each entry as its own separate block, which is how to get distinct paragraphs; a single string containing blank lines parses into paragraphs too.
- Parameters:
Example
report.text("A single paragraph with **emphasis**.") report.text(["First paragraph.", "Second paragraph."]) report.text("Generated {{date}}", params={"date": "2024-06-26"})
- callout(message, kind=CalloutKind.NOTE, title=None, params=None)[source]#
Append a titled block quote drawing attention to content.
- Parameters:
Example
report.callout( "Numbers are provisional.", kind=CalloutKind.WARNING, )
- bullet_list(items, params=None)[source]#
Append an unordered list, nesting sublists to any depth.
Items are parsed as inline Markdown, so they can carry emphasis, code, or links. A sublist is written as a list immediately after the item it hangs beneath. An empty list appends an empty list block.
- Parameters:
- Raises:
ValueError – if a sublist has no preceding item to nest beneath.
Example
report.bullet_list(["Revenue up 4%", "Churn flat", "See [detail](d.md)"]) report.bullet_list([ "Infrastructure", ["Database", "Cache", ["Redis", "Memcached"]], "Application", ])
- numbered_list(items, params=None)[source]#
Append a consecutively numbered list, nesting sublists to any depth.
Numbering is generated from position, starting at 1 at every level — don’t write numbers into the items themselves. A sublist is written as a list immediately after the item it hangs beneath.
- Parameters:
- Raises:
ValueError – if a sublist has no preceding item to nest beneath.
Example
report.numbered_list(["Extract", "Transform", "Load"]) report.numbered_list([ "Extract", ["Read the source", "Validate the schema"], "Load", ])
- table(df, title=None, params=None, decimal_places=2)[source]#
Append every DataFrame column and row as a GFM Markdown table.
The whole frame is written — there is no row or column limit, so slice the frame first if it is large. Column names become the header row and floats are rounded for display only.
- Parameters:
Example
report.table(metrics.head(20), title="Top 20 by revenue", decimal_places=1)
- csv(df, title=None, params=None, decimal_places=2, wrap_code=True)[source]#
Append a DataFrame as CSV, optionally inside a fenced code block.
Useful where a reader is meant to copy the numbers out rather than read them in a table.
- Parameters:
df (DataFrame) – The frame to serialize.
title (str | None) – Bold caption placed above the block.
params (Mapping[str, Any] | None) – Template variables, applied to the title.
decimal_places (int) – Digits after the point for float columns.
wrap_code (bool) – True fences the CSV in a
csvcode block. False emits it as raw document text, which is only valid where the surrounding Markdown tolerates it.
Example
report.csv(metrics, title="Raw data")
- code_block(code, language='', title=None, params=None)[source]#
Append a syntax-highlighted fenced code block.
Code is fenced, not parsed, so Markdown inside it stays literal.
- Parameters:
code (str) – Source text, reproduced as given.
language (str) – Info string driving highlighting; “” for a plain fence.
title (str | None) – Bold caption placed above the block.
params (Mapping[str, Any] | None) – Template variables, applied to the code as well as the title. Leave it None — the default — when the code contains Jinja-like braces of its own, which templating would otherwise substitute.
Example
report.code_block("select 1", language="sql", title="Query")
- figure(source, alt_text, caption=None, params=None, is_embedded=False)[source]#
Append an image with an optional numbered caption.
Figures are numbered in document order during
render.- Parameters:
source (str | Path) – Image path or URL written into the Markdown image destination.
alt_text (str) – Literal alternative text describing the image.
caption (str | None) – Optional inline-Markdown caption.
params (Mapping[str, Any] | None) – Template variables applied to source, alternative text, and caption.
is_embedded (bool) – True reads a local raster image into a base64 data URL or inserts a local SVG as inline markup. False links to source.
- Raises:
FigureEmbeddingError – during rendering, if an embedded source is not a supported local image.
Example
report.figure( "charts/revenue.png", alt_text="Revenue by region", caption="Quarterly revenue by region.", )
- line_break()[source]#
Append one additional blank line between document blocks.
Blocks are already separated by a blank line when rendered; this adds one more for extra visual spacing.
Example
report.text("Above").line_break().text("Below")
- horizontal_rule()[source]#
Append a thematic break, rendered as
---.Example
report.horizontal_rule()
- table_of_contents(start_level=1, depth=6, is_linked=True)[source]#
Append a nested table of contents covering the report’s headings.
Resolved at
rendertime, not now, so it can be placed near the top and still list headings appended afterwards. Entries nest by heading level and link to each heading’s anchor.- Parameters:
start_level (int) – Shallowest heading level listed. Raise it to skip the document title, or a section heading a slide deck repeats.
depth (int) – How many heading levels to list, counting from
start_level. Lower it to keep the contents short in a deeply nested report.is_linked (bool) – False renders entries as plain text, for a renderer whose heading anchors cannot be relied on.
- Raises:
ValueError – if start_level is outside the Markdown heading range, or depth is less than one.
Example
report.title("Q3 Review").table_of_contents().heading("Revenue") # the contents list includes "Revenue", added after the call report.table_of_contents(start_level=2, depth=2) # h2 and h3 only
- render()[source]#
Serialize the complete report as a Markdown string.
Resolves deferred blocks, writes heading anchors in the report’s
anchor_style, and prepends the frontmatter, leaving the report itself unchanged — rendering is repeatable, and content can still be appended afterwards.- Returns:
The rendered document, including a trailing newline.
- Return type:
Example
markdown = report.render()
Blocks#
- class Callout[source]#
Bases:
objectA titled block quote drawing attention to report content.
- Variables:
message (str) – Markdown content displayed inside the callout.
kind (mdreport.callout.CalloutKind) – Semantic category supplying the default title.
title (str | None) – Optional title overriding the category name.
params (collections.abc.Mapping[str, Any] | None) – Template variables applied to the message and custom title.
- kind: CalloutKind = 'note'#
- __init__(message, kind=CalloutKind.NOTE, title=None, params=None)#
- class CalloutKind[source]#
Bases:
StrEnumPortable semantic categories for a report callout.
- NOTE = 'note'#
- TIP = 'tip'#
- IMPORTANT = 'important'#
- WARNING = 'warning'#
- CAUTION = 'caution'#
- __new__(value)#
- class CodeBlock[source]#
Bases:
objectA fenced code block tagged with an optional language.
The block behind
MarkdownReport.code_block. Construct it directly to hold a snippet as a value and append it withreport.append(...)orreport + ....- Variables:
code (str) – Source text, fenced rather than parsed, so Markdown in it stays literal.
language (str) – Info string driving highlighting; “” for a plain fence.
title (str | None) – Bold caption placed above the block.
params (collections.abc.Mapping[str, Any] | None) – Template variables, applied to the code as well as the title. Leave it None when the code contains Jinja-like braces of its own.
Example
report.append(CodeBlock("select 1", language="sql", title="Query"))
- __init__(code, language='', title=None, params=None)#
- class Figure[source]#
Bases:
objectAn image with alternative text and an optional numbered caption.
Figures are numbered in document order during rendering.
- Variables:
source (str | pathlib.Path) – Image path or URL written into the Markdown image destination.
alt_text (str) – Literal alternative text describing the image.
caption (str | None) – Optional inline-Markdown caption, prefixed with its figure number.
params (collections.abc.Mapping[str, Any] | None) – Template variables applied to source, alternative text, and caption.
is_embedded (bool) – True reads a local raster image into a base64 data URL or inserts a local SVG as inline markup. False leaves source as a link.
- __init__(source, alt_text, caption=None, params=None, is_embedded=False)#
- class Table[source]#
Bases:
objectEvery column and row of a DataFrame as a GFM table.
The block behind
MarkdownReport.table. Construct it directly to hold a table as a value — to pass it around, reuse it across reports, or append it withreport + table.- Variables:
dataframe (polars.dataframe.frame.DataFrame) – The frame to render, in full; slice it first if it is large.
title (str | None) – Bold caption placed above the table.
params (collections.abc.Mapping[str, Any] | None) – Template variables, applied to the title.
decimal_places (int) – Digits after the point for float columns.
Example
summary = Table(metrics, title="Q3 {{region}}", params={"region": "EMEA"}) report.append(summary)
- dataframe: DataFrame#
- __init__(dataframe, title=None, params=None, decimal_places=2)#
- class TableOfContents[source]#
Bases:
objectA nested list of the report’s headings, linked to their anchors.
The block behind
MarkdownReport.table_of_contents, and the referenceDeferredReportBlock: it is appended as a placeholder and resolved duringrender, so it lists headings added after it as well as before. Entries nest by heading level.Entries link to the anchor a renderer derives from the heading text, which resolves as-is on GitHub, GitLab, Pandoc, and MkDocs. Where the renderer generates no anchors, build the report with a
MarkdownReportanchor_stylethat writes them into the document.- Parameters:
start_level – Shallowest heading level listed; headings above it are skipped along with the nesting they would have introduced.
depth – How many heading levels to list, counting from
start_level.is_linked – False renders entries as plain text, for a document whose anchors cannot be relied on.
- Raises:
ValueError – if start_level is outside the Markdown heading range, or depth is less than one.
Example
report.append(TableOfContents()) report.append(TableOfContents(start_level=2, depth=2)) # h2 and h3 only
- __resolve__(document, report)[source]#
Return list tokens mirroring the document’s heading hierarchy.
- entries(document)[source]#
Collect the headings in scope into a hierarchy, in document order.
- Raises:
ValueError – if a heading node contains no inline token.
- __init__(start_level=1, depth=6, is_linked=True)#
Heading anchors#
- class HeadingAnchorStyle[source]#
Bases:
StrEnumHow a heading’s anchor is written into the rendered Markdown.
IMPLICITwrites nothing and relies on the anchor the renderer derives from the heading text — what GitHub, GitLab, Pandoc, MkDocs, and Docusaurus all do, and whatslugifyreproduces.HTMLprefixes the heading with an<a id="...">element, for renderers that generate no anchors of their own.ATTRIBUTEappends the{#slug}attribute Pandoc, kramdown, and python-markdown’sattr_listunderstand; anything else renders it as visible text.- IMPLICIT = 'implicit'#
- HTML = 'html'#
- ATTRIBUTE = 'attribute'#
- __new__(value)#
- slugify(text)[source]#
Return the anchor slug a heading of this text is linked by.
Follows the GitHub algorithm — case folded, punctuation dropped, spaces turned into hyphens — so a link to the slug resolves on every renderer that derives heading anchors the same way, with nothing written into the document. Text that slugifies to nothing yields
section.
Extension protocols#
- class ReportBlock[source]#
Bases:
ProtocolA self-contained unit of report content.
- __report__(report)[source]#
Return this block’s content, as Markdown text or as tokens.
The report is passed for its
parser, which the token builders inmarkdown_tokens(paragraph_tokens,table_tokens,list_tokens) take. Implementations must not append to it.
- __init__(*args, **kwargs)#
- class DeferredReportBlock[source]#
Bases:
ProtocolReport content whose value depends on the completed document.
A deferred block is appended as a placeholder and resolved once, at render time, against the document as it finally stands. Use it for content that reads the rest of the report — tables of contents, summaries, and figure numbering.
- __resolve__(document, report)[source]#
Return this block’s content for the completed document.
The document excludes deferred placeholders’ own content, so a deferred block never observes another deferred block’s output.
- __init__(*args, **kwargs)#
- BlockContent = BlockContent#
Type alias.
Type aliases are created through the type statement:
type Alias = int
In this example, Alias and int will be treated equivalently by static type checkers.
At runtime, Alias is an instance of TypeAliasType. The __name__ attribute holds the name of the type alias. The value of the type alias is stored in the __value__ attribute. It is evaluated lazily, so the value is computed only if the attribute is accessed.
Type aliases can also be generic:
type ListOrSet[T] = list[T] | set[T]
In this case, the type parameters of the alias are stored in the __type_params__ attribute.
See PEP 695 for more information.
Errors#
- class FigureEmbeddingError[source]#
Bases:
ValueErrorA figure source cannot be embedded as an image in the report.
Token builders#
- paragraph_tokens(parser, content, *, is_hidden=False)[source]#
Build a paragraph token pair containing parsed inline Markdown.
- bold_paragraph_tokens(parser, content)[source]#
Build a paragraph whose complete inline content is strong text.
- heading_tokens(parser, content, level)[source]#
Build a heading at a level from one through six.
- Raises:
ValueError – if level is outside the Markdown heading range.
- list_tokens(parser, items, *, is_ordered)[source]#
Build an ordered or unordered list, nesting sublists to any depth.
A list element nests beneath the item that precedes it. Every level carries the marker chosen by is_ordered, so an ordered list nests ordered sublists.
- Raises:
ValueError – if a sublist has no preceding item to nest beneath.
- table_cell_tokens(parser, content, *, is_header)[source]#
Build one table header or body cell with inline Markdown.