Skip to main content

cast

Build live, notebook-backed presentations from Python functions.

PyPI Python License Status Built with

The cast editor: a toolbar of blocks, a slide canvas with a figure, table, image, and text, and an inspector panel.

The cast editor — a Plotly figure, a scrollable data table, a vector image, and rich text arranged on the canvas, with the selected block's controls in the inspector. Every asset comes from a decorated notebook function.

Project note: This repo is vibe coded, and the frontend in particular — the in-browser canvas editor and everything it renders — was built by iteration rather than by a dedicated frontend engineer. It is experimental and fast-moving, so expect rough edges alongside the parts that work well.

What it does

The usual path from analysis to slides means exporting a chart, pasting it into a deck, and repeating the whole process whenever the data changes. cast removes that step. You decorate the functions you have already written, and they become building blocks on a live web canvas. You arrange those blocks, add text, images, and shapes, and present — and when the underlying data changes, the slides update without a reload.

import cast, polars as pl, plotly.express as px

deck = cast.Cast("sales.cast.json")  # opens this workspace, or creates it
deck.serve()                          # live editor in a background thread

@deck.data                         # returns a polars LazyFrame -> a data source
def sales():
    return pl.scan_parquet("sales.parquet")

@deck.figure                       # a factory: takes one table, returns a Plotly figure
def trend(tbl):
    df = tbl.select(["month", "revenue"]).collect()
    return px.line(df, x="month", y="revenue", markers=True)

# Build the deck in the browser; no registration call is needed.

That is the entire program. Open the printed URL, add the trend figure with the sales table, and it appears on a slide.

Install

pip install cast-func

cast requires Python 3.9 or newer. The runtime dependencies — fastapi, uvicorn, polars, plotly, numpy, pandas, pyarrow, and notebook — are installed automatically. The package is imported as cast:

import cast

The decorators

Everything on a slide begins as a decorated function in the notebook. Create a workspace with deck = cast.Cast("talk.cast.json"), then use its decorators. Registration happens when the function is defined, and each decorated function still returns its original value, so existing notebook code is unaffected. Every decorator supports both the bare form (@deck.data) and the parameterized form (@deck.data(name=..., title=...)). The same decorators are importable at module level (cast.data, cast.figure, cast.html, cast.image); deck.data and cast.data are interchangeable.

Decorator The function returns Registered as
@deck.data a polars.LazyFrame a data source that feeds figures and table blocks
@deck.figure (factory) a Plotly figure from a table a chart that can be rebound to any table
@deck.html an HTML string (or an object with _repr_html_) a sandboxed iframe block
@deck.image a path, bytes, SVG, or data URI an original-quality image asset

@deck.data

@deck.data
def sales():
    return pl.scan_parquet("sales.parquet")

@deck.data(title="Q2 Forecast")
def forecast():
    return pl.scan_parquet("forecast.parquet")

The decorator registers the table immediately and resolves its LazyFrame lazily when a figure or table block first needs it. Calling the function remains optional; calling it again refreshes every slide that uses it, live, with no reload or re-export. Data sources feed both figures and table blocks. Table blocks render with sticky column headers, horizontal and vertical scrolling, optional row numbers and stripes, and a preview limit of up to 1,000 rows; the same scrollable table is preserved in present mode and in exports.

@deck.figure

@deck.figure(title="Value over time")
def trend(tbl):
    df = tbl.select(["date", "value"]).collect()
    return px.line(df, x="date", y="value", markers=True)

@deck.figure registers a factory at decoration time, not a rendered chart — you do not pass a table in the notebook. In the browser you add the figure to the inventory and choose which table feeds it, and you can add the same factory again with a different table. Each instance has its own table dropdown; picking a table re-runs the figure function on the server against that data. If the chosen table is incompatible, the block shows the error inline and invites another choice rather than crashing.

@deck.html

@deck.html(title="Callout")
def callout():
    return "<h2>Custom HTML</h2><button onclick=\"this.textContent='clicked'\">Click</button>"

@deck.html registers a custom HTML object at decoration time. Add it from the HTML dropdown to insert the returned markup into a sandboxed iframe block. This is useful for small widgets, controls, styled notes, or visualization snippets that are not Plotly figures. Objects exposing _repr_html_() are also accepted.

@deck.image

from pathlib import Path

@deck.image(title="Study design", alt="Diagram of the study workflow")
def study_design():
    return Path("figures/study-design.svg")  # PNG, JPEG, bytes, SVG, and data URIs also work

@deck.image registers publication-quality image assets at decoration time. The function may return a PNG/JPEG/SVG file path, raw image bytes, SVG markup, a data URI, or a notebook object with a PNG, JPEG, or SVG rich representation; Matplotlib figures and Pillow images are accepted through their standard save methods. The live app serves the original bytes without resizing or recompression, so SVG remains vector and raster images keep their full resolution. The inspector provides contain/cover/stretch behavior, a source aspect-ratio action, smooth/crisp/pixel rendering, transparency, alt text, corner radius, and opacity.

Composing the deck

Once assets are registered, the browser is a free-form canvas:

  • Figures, tables, HTML, images, text, and shapes (rectangle, ellipse, triangle, line) can be placed anywhere on a 16:9 canvas.
  • Blocks can be dragged, resized from any corner, rotated, and stacked in z-order; arrow keys nudge the selection and the Delete key removes it.
  • The inspector's Arrange controls align a block to the canvas (left/center/right, top/middle/bottom), duplicate it, or send it back in the stack.
  • Slides are managed from the rail on the left: add, reorder, delete, or duplicate a slide.
  • Text boxes use slide-native rich text rather than Markdown. Editing happens on the same element used for presentation, so typography, wrapping, and spacing do not change between design and present modes; font, size, color, weight, alignment, and line height are set in the inspector, and double-clicking a text block edits it in place. The text style menu also includes a polished code treatment with monospace defaults, code-safe whitespace, and an editor-like frame that is preserved in present mode and frozen exports.
  • A theme sets accent, background, foreground, and font once for the whole deck.
  • Edits stream over Server-Sent Events, so multiple tabs and changing data stay in sync.
  • Present mode is full-screen with arrow-key navigation, and figures remain interactive.

The editor is served by the small FastAPI application that deck.serve() starts in a background thread. serve() is idempotent, so calling it more than once does not start a second server.

Save, reopen, and export

deck = cast.Cast("talk.cast.json")    # load it, or create an empty workspace
deck.save()                           # atomically update talk.cast.json
deck.save(as_="talk-copy.cast.json") # write a copy; talk.cast.json stays active
deck.load()                           # reload the active workspace from disk
deck.freeze("talk.html")              # self-contained HTML, no server required

Cast(path) makes that JSON file the workspace source of truth. If the path already exists it is validated and loaded; if it does not exist, cast creates a new empty presentation there immediately. The editor's Save button performs the same atomic update as deck.save(), so saving does not depend on a browser download. Python reserves the word as, so Save As is spelled as_.

save and load round-trip an editable deck as human-readable JSON. The document preserves slide order, text, geometry, styles, rotations, theme, manually uploaded images, and references to decorated assets. It deliberately does not serialize Python functions or data frames; rerun the notebook cells that define those assets before reopening the deck. The editor's Open action can load another JSON document into the current workspace; press Save to commit it to the file originally passed to Cast.

freeze produces the shareable output: figures are pre-rendered to Plotly JSON, images are embedded, and tables, text, and shapes are inlined into a single HTML file with client-side slide navigation and still-interactive charts. It needs no running server.

How it fits together

   NOTEBOOK  ->  decorate functions  ->  ASSET LIBRARY  ->  compose on the CANVAS  ->  present / freeze
  (your data)    @data @figure           (tables, figures,   (arrange, style, theme)    (live or portable)
                 @html @image             html, images)

The notebook is the source of truth for data and logic; the bound .cast.json workspace is the source of truth for layout. cast keeps the two in sync.

Run the example

python examples/demo.py

Then open http://127.0.0.1:8000. Add the trend figure with the monthly table, add it again with daily, try the deliberately incompatible wide table to see the inline error, and add the HTML callout and the vector workflow image. The full source is in examples/demo.py.

API reference

Call Description
cast.Cast(path) Open an existing .cast.json workspace or create a new one.
deck.serve(port=8000, host="127.0.0.1", open=False) Start the editor in a background thread and return the URL; open=True embeds an IFrame in a notebook. Idempotent.
@deck.data / @deck.data(name=, title=) Register a LazyFrame-returning function as a data source.
@deck.figure / @deck.figure(name=, title=) Register a table -> Plotly figure factory.
@deck.html / @deck.html(name=, title=) Register an HTML-returning factory for iframe blocks.
@deck.image / @deck.image(name=, title=, alt=) Register an original-quality image asset.
deck.save() / deck.save(as_=path) Update the active workspace or atomically write a separate copy.
deck.load() Reload the active workspace (registered assets remain available).
deck.freeze(path) Export a self-contained, portable HTML file.

Notes and limitations

  • The frontend is vibe coded and still settling; some interactions are rough.
  • The page is built at import time. If you edit cast/templates.py, restart the kernel or process to see the change — a browser refresh alone will not reload it.
  • save and load store references to decorated assets, not the Python behind them. Rerun the notebook cells that define the assets before calling load.

License

MIT. See LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cast_func-0.3.1.tar.gz (60.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cast_func-0.3.1-py3-none-any.whl (59.3 kB view details)

Uploaded Python 3

File details

Details for the file cast_func-0.3.1.tar.gz.

File metadata

  • Download URL: cast_func-0.3.1.tar.gz
  • Upload date:
  • Size: 60.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.4 CPython/3.13.7 Darwin/24.5.0

File hashes

Hashes for cast_func-0.3.1.tar.gz
Algorithm Hash digest
SHA256 74602d28450d0717bdc1de88312c758c927532a5eff833505205ed080e314231
MD5 16e55a8bf200321044e2c1cd8f0abd96
BLAKE2b-256 c9f2d95ace6f2008b0d2e99096aa45cec6172e68bc40f1e7d08a4b9a35e4e9eb

See more details on using hashes here.

File details

Details for the file cast_func-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: cast_func-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 59.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.4 CPython/3.13.7 Darwin/24.5.0

File hashes

Hashes for cast_func-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 996b04e82fb7db3b8cf400fbad6442dd04f77b810e7ce4aebee98418f00705a9
MD5 34cb544490a78afbfec4eb2f4b3b5144
BLAKE2b-256 827c8485b0593f612a426d5061ce7d519e9d80c409584319bebd7c5d811617f8

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.3.1 This release

2 files

0.3.0

2 files

0.2.0

2 files

0.1.0

2 files

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