End-user API
The package root is the supported end-user boundary. Start with import
scribpy, then combine focused functions rather than importing internal
pipeline steps.
Note
Every name documented on this page is listed in scribpy.__all__. If a
name is not importable as scribpy.<Name>, it belongs to the
Extension API instead.
Object-level workflow
The functions below operate on a small set of domain objects. This sequence diagram shows how they interact for the common validate-then-build path:
Two things to note: concatenate consumes an in-memory MarkdownCollection
and writes the assembled Markdown, while html_export and mkdocs_export
read from disk again — html_export from the assembled Markdown file,
mkdocs_export from the original source project. See
Export below.
Create a project
from pathlib import Path
import scribpy
scribpy.init_skeleton(
Path("handbook"),
title="Team Handbook",
author="Ada",
version="1.0.0",
)
To create a hierarchy from headings:
outline = Path("outline.md")
nodes = scribpy.parse_outline(outline, max_depth=4)
print(nodes[0].title, nodes[0].children)
scribpy.init_from_outline(
outline,
Path("handbook"),
max_depth=4,
)
Both initialization workflows protect an existing scribpy.yml with
ScaffoldCollisionError.
try:
scribpy.init_skeleton(Path("handbook"), title="Team Handbook")
except scribpy.ScaffoldCollisionError as error:
print(f"A project already exists at: {error.path}")
An invalid outline (for example, a heading level jump that skips levels, or
max_depth outside 1-6) raises OutlineValidationError, which carries a
one-based line_number:
try:
nodes = scribpy.parse_outline(outline, max_depth=4)
except scribpy.OutlineValidationError as error:
print(f"Outline problem at line {error.line_number}: {error.detail}")
Work with one Markdown value
page = scribpy.MarkdownFile.from_path("handbook/index.md")
print(page.name, page.suffix, page.encoding)
updated = page.replace_text("Draft", "Published")
updated.write("work/index.md")
document = updated.to_document()
for image in document.image_references:
print(image.target, image.line, image.column)
MarkdownFile keeps path, content, and encoding. MarkdownDocument is
path-free in-memory content. Both are immutable-style values: with_content
and replace_text return new objects. MarkdownImageReference records the
alt text, raw target, optional title, and optional one-based line/column of
one image found inside a MarkdownDocument.
document = scribpy.MarkdownDocument(
'# Page\n\n\n'
)
reference = document.image_references[0]
assert reference.target == "assets/logo.png"
assert reference.title == "Brand"
Validate with or without presentation
report = scribpy.validate_project("handbook")
if not report.is_valid:
scribpy.render_validation_report(report)
For a small script where printing is desired immediately:
if not scribpy.valid_report("handbook"):
raise SystemExit(1)
Pass a custom Rich Console to valid_report or
render_validation_report when capturing output in a host application.
validate_project(root) returns a ProjectValidationReport — inspect
is_valid, has_errors, markdown_count, manifest_count, and iterate
diagnostics (a tuple of ProjectDiagnostic). Filter by severity with
by_severity:
report = scribpy.validate_project("handbook")
warnings = report.by_severity(scribpy.DiagnosticSeverity.WARNING)
for warning in warnings:
print(warning.code, warning.path, warning.message)
print(f"{report.markdown_count} Markdown files, "
f"{report.manifest_count} manifests inspected")
| Function | Purpose |
|---|---|
validate_project(root) |
Returns a structured ProjectValidationReport. Does not print. Use in services, tests, CI gates. |
valid_report(root, *, console=None) |
Calls validate_project, renders with Rich, returns bool. Convenience wrapper, not a distinct validator. |
render_validation_report(report, *, console=None) |
Presents an existing report. Does not revalidate the filesystem. |
Load, diagnose, and assemble
root = Path("handbook")
collection = scribpy.MarkdownCollection.from_tree(root, encoding="utf-8")
diagnostics = collection.diagnose()
if diagnostics.has_errors:
raise scribpy.InvalidMarkdownError(diagnostics.summary())
output = Path("build/handbook.md")
scribpy.concatenate(collection, output)
The manifest belongs to the collection and controls transforms:
print(collection.manifest.project)
print(collection.manifest.build.toc)
print([item.path for item in collection.files])
collection.manifest is a RootManifest (path, project, build,
order). Folders below the root use the smaller FolderManifest (path,
title, order), loaded automatically while traversing — you normally do
not construct either by hand.
collection.diagnose(rules=None) runs the eight default collection rules
when rules is omitted, or exactly the rules you pass:
errors_only = [
rule
for rule in (
scribpy.SourceFirstHeadingH1Rule(),
scribpy.SourceH1CountRule(),
scribpy.HeadingLevelOverflowRule(),
scribpy.InternalMarkdownLinkRule(),
scribpy.LocalImageMissingRule(),
scribpy.ExternalImageReferenceRule(),
)
]
report = collection.diagnose(errors_only)
These six rule classes are exported from scribpy.__all__ because they are
useful to reference directly (for example, to build a custom subset). Two
more default rules — LocalAnchorLinkRule and ImageOutsideRootRule — exist
but are only reachable from scribpy.core.diagnostics; see the
Extension API.
CollectionDiagnosticReport (returned by diagnose()) exposes has_errors,
by_severity(severity), and summary(). CollectionDiagnostic is the
individual finding (code, severity, message, path, line).
CollectionDiagnosticRule is the structural protocol every rule satisfies —
see the Extension API for writing your own.
Export
scribpy.html_export(
Path("build/handbook.md"),
Path("build/handbook.html"),
toc_depth=3,
css=Path("handbook.css"),
)
scribpy.mkdocs_export(
Path("handbook"),
Path("build/handbook-site"),
)
HTML consumes assembled Markdown. MkDocs export consumes the original project. This distinction matches the CLI and prevents accidental double assembly.
html_export(source, output, toc_depth=3, css=None) reads UTF-8 Markdown at
source, strips Scribpy's own TOC block, derives navigation from headings,
and writes a self-contained HTML file with inlined CSS and menu JavaScript.
It does not create the output's parent directory, and raises
FileNotFoundError if source or css is missing.
mkdocs_export(source, output) loads the project at source again (not the
assembled Markdown), renders diagrams, collects images, derives navigation,
and writes a minimal mkdocs.yml under output. It raises
ScaffoldCollisionError if output/mkdocs.yml already exists.
Add temporary logging
with scribpy.logging_context(
level="DEBUG",
file=Path("build/scribpy.log"),
console=True,
):
collection = scribpy.MarkdownCollection.from_tree("handbook")
scribpy.concatenate(collection, Path("build/handbook.md"))
The context removes its handlers and restores the previous logger level on
exit. level accepts a standard name ("DEBUG", "INFO", ...) or a numeric
logging level. Set console=False to suppress stderr output while still
writing to file.
Handling exceptions
concatenate, html_export, and mkdocs_export do not convert failures
into reports — they raise. Catch only what your application can explain or
recover from:
try:
collection = scribpy.MarkdownCollection.from_tree(root)
scribpy.concatenate(collection, output)
except scribpy.InvalidScribpyManifestError as error:
print(f"Invalid manifest {error.path}: {error.detail}")
except scribpy.InvalidMarkdownError as error:
print(f"Invalid Markdown: {error.detail}")
except (scribpy.PlantUmlRenderError, scribpy.MermaidRenderError) as error:
print(f"Diagram rendering failed: {error}")
except scribpy.ScaffoldCollisionError as error:
print(f"Target already has a project: {error.path}")
except OSError as error:
print(f"Filesystem operation failed: {error}")
PlantUmlRenderError and MermaidRenderError are both exported from
scribpy.__all__ — every diagram-rendering failure raised anywhere in the
pipeline (server backend, Kroki backend, or Mermaid CLI) surfaces as one of
these two domain exceptions, never a raw requests/subprocess error.
All exceptions except ScribpyManifestWarning inherit from ScribpyError,
so a broad boundary can also do:
try:
scribpy.concatenate(collection, output)
except scribpy.ScribpyError as error:
print(f"Scribpy could not complete the build: {error}")
Avoid catching bare Exception: unexpected defects should remain visible.
| Exception | Raised for | Key attributes |
|---|---|---|
ScribpyError |
Base class for all domain failures. | — |
InvalidMarkdownError |
Blocking Markdown/collection structure problem. | detail |
InvalidScribpyManifestError |
Invalid scribpy.yml. |
path, detail |
PlantUmlRenderError |
PlantUML backend failed to render. | detail |
MermaidRenderError |
Mermaid backend failed to render. | detail |
OutlineValidationError |
Outline Markdown is structurally invalid. | line_number, detail |
ScaffoldCollisionError |
Target already has a scribpy.yml. |
path |
ScribpyManifestWarning |
Ignored manifest key (a UserWarning, not raised). |
— |
See the Python API reference for every root export and the complete tutorial for a runnable workflow.