Configuration¶
nbprint supports three complementary ways to configure a report:
Notebook-first — point nbprint at an existing
.ipynband drive sectioning, styling, and page layout from cell tags, cell metadata, and a small%%nbprintmagic.YAML-first — describe the full document in YAML (via
pydanticmodels, composed withlernaandomegaconf).Hybrid — start from a notebook and add YAML overlays that target cells by index, tag, section, or cell type without rewriting the notebook.
graph TB
subgraph Inputs
nb("notebook<br>(.ipynb)")
yml("YAML config<br>(.yaml)")
ovl("overlays<br>(YAML or<br>notebook metadata)")
end
nbct[/nbprint<br>template/]
pjs[/paged.js<br>layout engine/]
nbc{nbconvert}
o@{ shape: doc, label: "HTML / PDF" }
nb e1@-->nbc
yml e2@-->nbc
ovl -.->|merged at ingest| nbc
nbct --> nbc
pjs --- nbct
nbc e3@--> o
e1@{animate: true}
e2@{animate: true}
e3@{animate: true}
Notebook-First Workflow¶
Point nbprint at a .ipynb and it is treated as a full configuration input. All notebook metadata under notebook.metadata.nbprint becomes configuration; cell metadata and tags drive routing, styling, and visibility.
nbprint path/to/report.ipynb
Worked examples live in examples/:
notebook-sections.ipynb— section routing via tags and cell metadata, with page/output config in notebook metadata.notebook-runtime.ipynb— runtime API (NBPrintCell) and%%nbprintmagic.notebook-overlays.ipynb—overlaysandlayout_overlaysembedded in notebook metadata.
Section routing from cell metadata¶
Equivalent to a tag, but takes priority:
// cell.metadata.nbprint
{
"section": "frontmatter"
}
Notebook-level page and output config¶
Embed a full page/output spec directly in the notebook — no YAML required:
// notebook.metadata.nbprint
{
"page": {
"_target_": "nbprint.PageGlobal",
"size": "Letter",
"orientation": "portrait"
},
"outputs": {
"_target_": "nbprint.NBConvertOutputs",
"target": "pdf"
}
}
YAML or CLI overrides always take precedence over notebook-embedded values, so you can use a notebook’s defaults and override on the command line.
NBPrintCell runtime API¶
Inside a running notebook you can express cell-level intent through a Python object. Metadata is persisted via a hidden display() output with a custom MIME type; nbprint picks it up at ingestion time.
from nbprint import NBPrintCell, Style, Font
NBPrintCell(section="covermatter", css=":scope { text-align: center; }")
with NBPrintCell(style=Style(font=Font(size=24))):
display(Markdown("# Cover Page"))
%%nbprint cell magic¶
A lighter alternative for quick one-liners:
%%nbprint section=frontmatter css=":scope { font-size: 18px; }"
display(Markdown("# Introduction"))
Explicit cell metadata beats runtime metadata beats magic — so a hand-edited value in cell.metadata.nbprint always wins.
YAML-First Workflow¶
YAML is the most expressive way to describe a report: every field of every pydantic model is reachable, and the lerna config system makes it easy to share pieces across reports.
A minimal example:
---
debug: false
outputs:
_target_: nbprint.NBConvertOutputs
root: ./outputs
target: html
content:
- _target_: nbprint.ContentMarkdown
content: |
# A Generic Report
## A Subtitle
css: ":scope { text-align: center; }"
- _target_: nbprint.ContentPageBreak
- _target_: nbprint.ContentTableOfContents
- _target_: nbprint.ContentPageBreak
- _target_: nbprint.ContentFlexRowLayout
sizes: [1, 1]
content:
- _target_: nbprint.ContentFlexColumnLayout
content:
- _target_: nbprint.ContentMarkdown
content: "# Left column"
- _target_: nbprint.ContentFlexColumnLayout
content:
- _target_: nbprint.ContentMarkdown
content: "# Right column"
Run it:
nbprint examples/basic.yaml
Common content types include ContentMarkdown, ContentCode, ContentPageBreak, ContentTableOfContents, and the layout containers ContentFlexRowLayout / ContentFlexColumnLayout / ContentInlineLayout.
Output formats & nbconvert configuration¶
The outputs block (NBConvertOutputs) controls how the generated notebook is
executed and converted. Common fields:
Field |
Default |
Description |
|---|---|---|
|
|
Output format: |
|
|
Execute the notebook before converting |
|
|
Per-cell execution timeout (seconds) |
|
|
nbconvert template name |
Passing arbitrary nbconvert / traitlets options¶
Anything configurable on an nbconvert exporter, preprocessor, or the app itself
is reachable generically through nbconvert_config — no nbprint field is needed
per option. Entries map 1:1 onto nbconvert’s --Class.trait=value CLI options
and are applied to the conversion (exporter) pass.
For example, WebPDFExporter.page_render_timeout
controls how long (in ms) the headless browser waits for JavaScript output to
settle before capturing the PDF — useful for notebooks with heavy interactive
charts:
outputs:
_target_: nbprint.NBConvertOutputs
target: webpdf
nbconvert_config:
# flat dotted keys ...
WebPDFExporter.page_render_timeout: 5000
# ... or nested namespaces — both are equivalent
TemplateExporter:
exclude_input: true
The nested form maps directly onto a hydra/lerna CLI override:
nbprint examples/basic.yaml \
++outputs.target=webpdf \
++outputs.nbconvert_config.WebPDFExporter.page_render_timeout=5000
Scalars pass through as-is, booleans render as True/False, and lists are
JSON-encoded so container traits round-trip through the CLI.
Traits that nbprint already manages — NbConvertApp.export_format (to),
TemplateExporter.template_name (template), NbConvertApp.output_base
(output), ExecutePreprocessor.enabled (execute), and
ExecutePreprocessor.timeout — are rejected in nbconvert_config with an error
pointing at the dedicated field (target, template, naming/root,
execute, timeout). Set those via their own fields instead.
Structured Sections¶
For book-style documents, nbprint supports 13 ordered sections grouped into 6 logical groups:
Group |
Sections |
Description |
|---|---|---|
Prematter |
|
Hidden content executed before report construction |
Covermatter |
|
Cover page content |
Frontmatter |
|
Title page, legal, dedication, TOC, preface |
Middlematter |
|
Main body and chapter dividers |
Endmatter |
|
Supplementary material, index, bibliography |
Rearmatter |
|
Back cover content |
Instead of a flat list, content: can be keyed by section name. Sections are rendered in document order; unused sections are simply empty.
content:
covermatter:
- _target_: nbprint.ContentMarkdown
content: "# My Report"
css: ":scope { text-align: center; }"
table_of_contents:
- _target_: nbprint.ContentTableOfContents
middlematter:
- _target_: nbprint.ContentMarkdown
content: "# Section One"
endmatter:
- _target_: nbprint.ContentMarkdown
content: "# Bibliography"
Flat-list content: is still fully supported — it is treated as middlematter.
Chapters (list-of-lists middlematter)¶
For multi-chapter documents, supply middlematter as a list of lists. Each
sublist is a chapter: its first element is promoted to a chapter separator
and the remaining elements form the chapter body. nbprint interleaves them in
document order — [chapter1-title, *chapter1-body, chapter2-title, *chapter2-body, …].
content:
middlematter:
- # Chapter One — first item becomes the title page
- _target_: nbprint.ContentMarkdown
content: "# Chapter One"
- _target_: nbprint.ContentMarkdown
content: "Body of the first chapter…"
- # Chapter Two
- _target_: nbprint.ContentMarkdown
content: "# Chapter Two"
- _target_: nbprint.ContentMarkdown
content: "Body of the second chapter…"
By default each separator renders as a standalone chapter title page — a hard page break is inserted before and after it. To keep separators inline with their chapter body instead, disable the behavior:
content:
separator_title_pages: false
middlematter:
- [...]
The promoted separators are also available directly under the
middlematter_separators section if you prefer to populate them explicitly
alongside a flat middlematter list.
See examples/chapters.yaml for a full working example.
Per-section page layout¶
Each section can carry its own page layout via PageGlobal.pages. This generates CSS @page <section> { ... } rules and maps cells to them via data-nbprint-section attributes:
page:
_target_: nbprint.PageGlobal
bottom_right:
_target_: nbprint.PageRegion
content:
_target_: nbprint.PageNumber
pages:
covermatter:
# no headers/footers on the cover
frontmatter:
counter_reset: true
counter_style: lower-roman
middlematter:
counter_reset: true
See examples/sections.yaml for a full working example.
Section-level style defaults¶
Each section can carry a default Style that every cell in that section inherits. A cell’s own Style fields take precedence; unset fields fall back to the section default.
content:
section_styles:
covermatter:
font: {size: 28}
horizontal_alignment: center
frontmatter:
font: {family: "Georgia, serif"}
covermatter:
- _target_: nbprint.ContentMarkdown
content: "# My Report"
Formatting Overlays¶
Overlays are rules that apply formatting to ingested notebook cells without modifying the notebook itself. There are two kinds.
Overlay — merge formatting onto matching cells¶
Matches cells by index, tag, cell type, or target section, and merges css / classname / attrs / style / ignore into the resulting Content:
overlays:
- match: {index: 0}
css: ":scope { text-align: center; }"
- match: {tag: "chart"}
style:
border: {bottom: {width: 2, style: solid, color: "#333"}}
- match: {cell_type: markdown}
classname: "prose-body"
- match: {section: covermatter}
style:
font: {size: 28}
Match fields combine with AND semantics — an empty match matches every cell. Multiple overlays apply in list order; later overlays stack on top of earlier ones:
cssis appendedclassnameis appended (accumulated as a list)attrsis merged (overlay keys win on collision)styleis merged viaStyle.merge(overlay fields win)ignoreis set when provided
Overlays can also come from notebook metadata. YAML-declared overlays and notebook-declared overlays compose:
// notebook.metadata.nbprint
{
"overlays": [
{"match": {"tag": "emphasis"}, "css": ":scope { font-style: italic; }"}
]
}
LayoutOverlay — wrap contiguous cells in a flex container¶
A layout overlay matches the same way but wraps contiguous runs of matched cells (same section, consecutive notebook indices) in a ContentFlexRowLayout, ContentFlexColumnLayout, or ContentInlineLayout:
layout_overlays:
# Any two adjacent "sidebyside"-tagged cells become a row
- match: {tag: "sidebyside"}
layout: row
sizes: [1, 1]
# Cells at notebook indices 3..5 (inclusive) get stacked in a column
- index_range: [3, 5]
layout: column
# Wrapper itself can carry CSS / classname / style
- match: {tag: "gallery"}
layout: inline
css: ":scope { gap: 8px; }"
classname: gallery-wrap
Non-contiguous matches produce multiple wrappers. Both kinds of overlay can be combined: formatting overlays apply to the individual cells, and a layout overlay then wraps them.
Lerna Config Composition¶
nbprint uses lerna (a Rust-powered, Hydra-compatible config framework) for YAML composition. Everything Hydra supports works identically, and lerna adds a few features that are particularly useful for reports.
Composing reports from parts¶
Pull in reusable pieces via a defaults: list:
# examples/hydra.yaml
defaults:
- config: inline # examples/hydra/config/inline.yaml
- page: report # examples/hydra/page/report.yaml
- parameters: string1 # examples/hydra/parameters/string1.yaml
- _self_
outputs:
naming: "{{name}}"
root: ./outputs
This lets a handful of reusable YAML files describe a large family of reports.
CLI list manipulation¶
lerna supports list operations directly from the CLI — useful for adding a single chart or section to an existing report without editing the YAML:
# Append a content item
nbprint examples/basic.yaml \
'content=append({_target_: nbprint.ContentMarkdown, content: "# Added"})'
# Prepend
nbprint report.yaml 'content=prepend({_target_: nbprint.ContentPageBreak})'
# Insert at a specific index
nbprint report.yaml 'content=insert(0, {_target_: nbprint.ContentMarkdown, content: "Intro"})'
# Remove by index / value / clear
nbprint report.yaml 'content=remove_at(-1)'
nbprint report.yaml 'tags=remove_value(draft)'
nbprint report.yaml 'overlays=list_clear()'
Operation |
Effect |
|---|---|
|
Add item(s) to end |
|
Add item(s) to beginning |
|
Insert at index |
|
Remove by index (negative allowed) |
|
Remove first matching item |
|
Clear all items |
Quote the whole override for shell safety. Works in bash, zsh, fish, PowerShell, and cmd.
Sweeps and multirun¶
Generate a family of reports from one command:
nbprint report.yaml -m \
parameters.model=choice(ridge,lasso,elasticnet) \
parameters.alpha=interval(0.01,0.5)
Combined with outputs.naming: "{{name}}-{{parameters.model}}-{{parameters.alpha}}" this produces one cleanly named HTML/PDF per combination.
_patch_ — modify composed configs before CLI overrides¶
Pull in a vendored config and surgically modify it:
defaults:
- vendor/large_defaults
- _self_
- _patch_:
- ~unwanted_key # delete a key
- content=remove_value(stale) # drop a content item
- +overlays=append({match: {tag: draft}, ignore: true}) # add an overlay
Patches apply after composition but before CLI overrides, so CLI still wins. See the lerna README for the full _patch_ reference (scoped _patch_@package, _here_ / _global_ prefixes, nested accumulation, etc.).
Default overrides at the call site¶
nbprint’s CLI accepts default overrides without editing YAML:
nbprint examples/basic.ipynb \
++callable=/nbprintx \ # switch to the multirun model
+nbprint.outputs.naming='{{name}}-{{date}}-{{a}}' \
+nbprintx.parameters=[{a: 1},{a: 2},{a: 3}] \ # inline JSON or file path
++nbprint.outputs.execute=False # disable execution
Running a Report¶
nbprint examples/basic.yaml # YAML-first
nbprint path/to/report.ipynb # notebook-first
nbprint report.yaml notebook=foo.ipynb # hybrid: YAML + notebook
The output lands in the directory set by outputs.root (default ./outputs), named per outputs.naming (default {{name}}-{{date}}). Both the generated notebook and the chosen render target (html, pdf, …) are written; the notebook is useful for inspection and further experimentation.
A rendered PDF of the basic example lives here.
ccflow Integration¶
nbprint is compatible with ccflow callable models. nbprint outputs like NBConvertOutputs inherit from ccflow.ResultsBase, and parameters inherit from ccflow.ContextBase.
nbprint-cc +nbprint.name=test +context=[]
ccflow workflows are exposed as multi-run models:
nbprint examples/basic.ipynb \
++callable=/nbprintx \
+nbprint.outputs.naming='{{name}}-{{date}}-{{a}}' \
+nbprintx.parameters=[{a: 1},{a: 2},{a: 3}] \
++nbprint.outputs.execute=False
Precedence Summary¶
When the same piece of configuration can come from multiple places, resolution order is:
CLI overrides — always highest priority
YAML explicit values (
config.yaml,_self_)YAML
_patch_directives (applied during composition)Notebook-level metadata (
notebook.metadata.nbprint.{page,outputs,overlays,layout_overlays})Defaults from composed configs and pydantic model defaults
For section assignment specifically:
cell.metadata.nbprint.sectionnbprint:section:<name>cell tagNBPrintCell(section=...)MIME metadata%%nbprint section=...magicDefault:
middlematter