Skip to main content

rich-metadata

A Rich-based TUI toolkit for building interactive metadata browsers.

Define your entities declaratively, wire up your API, and get an interactive terminal browser with pagination, navigation, lazy loading, and image support.

Install

pip install rich-metadata

Quick start

from rich_metadata import (
    BaseNavigator,
    DisplayEngine,
    EntityDef,
    HeaderField,
    HeaderLink,
    SectionDef,
    SummaryField,
    TableColumn,
)

# 1. Define your entities
book_def = EntityDef(
    type_name="book",
    summary=[
        SummaryField(key="title", style="bold"),
        SummaryField(prefix="by ", key="author"),
        SummaryField(key="year", style="dim"),
    ],
    header_fields=[
        HeaderField("Author", key="author"),
        HeaderField("Year", key="year"),
        HeaderField("Genre", key="genre"),
        HeaderField("Pages", key="pages"),
        HeaderField("Publisher", key="publisher"),
    ],
    sections=[
        SectionDef(
            "chapters",
            navigable=True,
            columns=[
                TableColumn("#", "number", width=4),
                TableColumn("Title", "title", style="bold"),
                TableColumn("Pages", "pages"),
            ],
        ),
        SectionDef("description", lazy=True),
    ],
    header_links=[
        HeaderLink("Author: {author}", "author", ref_key="author_url"),
    ],
    footer=["url"],
)

# 2. Create a display engine and register definitions
engine = DisplayEngine()
engine.register(book_def)

# 3. Use the engine directly
entity = {"_type": "book", "title": "Dune", "author": "Frank Herbert", "year": "1965"}
engine.details(entity)    # render header + all sections
engine.summary(entity)    # one-line summary
engine.header(entity)     # header panel with image

# 4. Or wire up a navigator for interactive browsing
class BookAPI:
    def get(self, ref: str, *, full: bool = False, **kwargs) -> dict | None:
        ...  # fetch entity by URL/ID, return dict with "_type" key
        # `full=True` is passed when the caller wants every section eagerly
        # populated (e.g. `--json` / `--full` flows). Extra kwargs are forwarded.

    def search(self, query: str, exact_match: bool = True) -> list[dict]:
        ...  # return list of entity dicts (each with "_type" and "name")

navigator = BaseNavigator(
    engine,
    apis={"book": BookAPI(), "author": AuthorAPI()},
    entity_ref_key="url",
    lazy_fetchers={
        ("book", "description"): lambda api, entity: api.fetch_description(entity["id"]),
    },
)
navigator.navigate(entity)  # interactive section menu
navigator.browse(fetch_page=my_search_fn)  # paginated results

Core concepts

Entity dicts

Entities are plain Python dicts with a _type key for routing:

{"_type": "book", "title": "Dune", "author": "Frank Herbert", "year": "1965"}

EntityDef

Declares how an entity type is displayed. Each EntityDef configures:

Field Purpose
type_name Entity type identifier (matches _type in dicts)
summary One-line display fields (SummaryField list)
header_fields Key-value pairs in the detail panel (HeaderField list)
header_image_key Dict key holding image bytes for the header panel
header_title Custom title callable (dict) -> str
panel_border_style Rich style for the header panel border
sections Expandable content sections (SectionDef list)
header_links Navigable links shown in the section menu (HeaderLink list)
footer Keys (strings) or callables (dict) -> str | None for lines below the panel
auto_full If True, render every section inline (no menu) and offer prev/next sibling navigation

SectionDef

Defines a content section. The rendering mode is auto-detected:

  • Has columns -> table (if data is a list, rows are flat; if dict[str, list], rows are grouped with headers)
  • Has custom_render -> custom rendering function
  • Neither -> text panel

Key options: navigable (items can be drilled into), lazy (fetched on demand), duration_key (sums and shows total duration).

SummaryField

One segment of a one-line entity summary. Supports style, prefix (text before the value), fallback (shown when key is missing), and transform (receives value if key is set, or the whole entity dict if not).

HeaderField

A labeled row in the detail panel. Either reads from key directly, or computes via transform (receives value if key is set, or the whole entity dict if not).

TableColumn

A column in a table section. Supports style, justify, width, and transform.

A navigable link shown in the section menu (e.g., "Author: Frank Herbert ->"). Uses ref_key to read a URL/ID from the entity dict, or ref_fn(entity) -> str | None for computed refs.

DisplayEngine

The rendering engine. Key methods:

engine = DisplayEngine()              # uses default Rich console
engine = DisplayEngine(my_console)    # custom console

engine.register(entity_def)           # register an EntityDef
engine.summary(entity)                # one-line summary
engine.header(entity)                 # detail panel with optional image
engine.section(entity, "chapters")    # render a single section
engine.details(entity)                # header + all non-lazy sections
engine.select_from_list(items)        # numbered selection prompt

BaseNavigator

Interactive browser with pagination, back-navigation, and lazy fetching.

navigator = BaseNavigator(
    engine,
    apis={"book": book_api, "author": author_api},
    entity_ref_key="url",
    lazy_fetchers={
        ("book", "description"): lambda api, entity: api.fetch_desc(entity["id"]),
    },
)
Parameter Purpose
apis {type: api}. Each API needs .get(ref, *, full=False, **kwargs) and (for search_and_navigate) .search(query, exact_match=True)
entity_ref_key Key to extract the navigable ref from item dicts (default: "url")
lazy_fetchers {(type, section): callable(api, entity) -> data} for lazy sections

Key methods:

  • navigate(entity) -- Interactive loop: header, section menu, lazy fetching, header link navigation, back-navigation, and prev/next sibling navigation when siblings= is provided.
  • display_or_navigate(entity, *, json_output=False, full=False) -- Dispatch one entity: JSON dump (internal keys stripped), full inline render, or interactive navigate(). Used by CLIs that share one code path for --json / --full / interactive modes.
  • search_and_navigate(query, types, *, exact_first=True, json_output=False, full=False) -- Search across types (each API's .search() is called), prefer exact name matches when exact_first, then select and navigate. Passes full= to .get() so backends can fetch eagerly when needed.
  • browse(fetch_page=..., *, render_page=None, title=None, page_size=25, full=False, loop=False) -- Paginated results with selection. fetch_page(start, count) -> (results, total). loop=True re-displays the page after a child view returns (works with both full and interactive modes).
  • browse_sources(sources, *, full=False) -- Pick from named browsable sources ([(label, fetch_page), ...]), then browse the selected one. Skips the menu when only one source is supplied.

Items with _type but no ref (no url or whatever entity_ref_key is) are treated as inline entities -- navigated directly without fetching.

QuitSignal

BaseNavigator._input raises QuitSignal (an Exception subclass) on Ctrl+C / EOF so the interactive loops can unwind cleanly. Catch it at your CLI entry point to exit quietly:

from rich_metadata import QuitSignal

try:
    navigator.navigate(entity)
except QuitSignal:
    pass

CLI helpers

Shared CLI utilities:

from rich_metadata import (
    configure_logging,     # loguru setup (debug if verbose, else warnings)
    resolve_entity_type,   # extract (type, query) from parsed args
    strip_internal_keys,   # remove _prefixed keys for JSON output
    list_fetcher,          # wrap a list into a fetch_page callable for browse()
    page_fetcher,          # adapt page-number APIs to browse()'s offset interface
    parse_date,            # parse 'YYYY-MM-DD' string to date
    parse_date_args,       # parse --from/--to argparse flags into dates
    months_in_range,       # list of 'YYYY-MM' strings covering a date range
)

list_fetcher and page_fetcher are adapters for BaseNavigator.browse():

# Wrap a pre-fetched list
navigator.browse(fetch_page=list_fetcher(my_items))

# Adapt a page-number API (fetch(page) -> (results, has_more))
navigator.browse(fetch_page=page_fetcher(api.search, first_page=initial_results))

Image support

Terminal image rendering for iTerm2 and Kitty:

from rich_metadata import get_image_escape, show_image_beside

# Get raw escape sequence for image bytes
escape = get_image_escape(image_bytes, width=20, height=10)

# Show an image beside a Rich renderable
show_image_beside(console, image_bytes, my_panel, img_width=20)

Duration helpers

from rich_metadata import parse_duration, format_duration

parse_duration("3:45")      # 225 (seconds)
parse_duration("1:02:30")   # 3750
format_duration(225)         # "3:45"
format_duration(3750)        # "1:02:30"

License

MIT

Metadata

Release files for rich-metadata 0.1.9

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for rich-metadata 0.1.9
File Size Uploaded Provenance
rich_metadata-0.1.9.tar.gz 19.1 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for rich-metadata 0.1.9
File Interpreter ABI Platform
rich_metadata-0.1.9-py3-none-any.whl Python 3 none any Details

Total release size: 41.8 kB

Release files / rich_metadata-0.1.9.tar.gz

Download URL rich_metadata-0.1.9.tar.gz
Size 19.1 kB
Tags Source
SHA-256 checksum
How to use checksums
d08fed595bc94ca6adea1b139b05adbeaa63e09c34b1b3d9c5482a196506b145
BLAKE2b-256 checksum
How to use checksums
8faddd36430a08d2da2c316df12719577d9219fb66098d7ab7bd9cf83e32b8b2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 25, 2026.

Transparency log

Release files / rich_metadata-0.1.9-py3-none-any.whl

Download URL rich_metadata-0.1.9-py3-none-any.whl
Size 22.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6fc27c3f7d4885ddfaa5937084875036b89082ce875791443fe5be752b3a473d
BLAKE2b-256 checksum
How to use checksums
84bf893deff1a5711695e56cae526cb03161b7f395102bf3fbe29dd8bac04622
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 25, 2026.

Transparency log

Security for rich-metadata 0.1.9

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for rich_metadata-0.1.9.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 25, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page