Skip to main content

Write Marimo notebooks that also work as CLI scripts, with unified UI controls

Project description

moops

PyPI Open in molab

Easily write Marimo notebooks that work as CLI scripts (and more!) with minimal boilerplate.

Marimo supports notebooks running as CLI scripts, but until now this required maintaining matching input handling implementations.

Using moops, both implementations are merged into one.

Installation

uv add (or pip install) moops

Transition guide

  • Create your argument group: args = moops.Group()
  • Replace your mo.ui usages with using methods of args
  • Add args.interface call, preferably as the top cell, and provide the UI elements to it. This makes the notebook works as a script and adds info about it in the notebook.

Now your notebook doubles as a CLI script

Running notebooks from Python

Notebooks can also be called from Python with moops.run. This is useful for testing notebook logic without launching Marimo, and for reusing notebook logic from other code.

Expose a variable named result from the notebook:

@app.cell
def _(input_text, mode_dropdown):
    result = mode_dropdown.value(input_text.value)
    return (result,)

Then call the notebook module directly:

import moops
from examples import name_casing

result = moops.run(
    name_casing,
    text="Hello World",
    style="snake_case",
)

assert result == "hello_world"

Keyword arguments override moops.Group inputs by their option names, with leading dashes removed and dashes converted to underscores. If no overrides are provided, moops.run uses the notebook defaults.

URL query parameters

In browser notebooks, Group() lets URL query parameters initialize controls and keeps later control changes reflected in the URL.

args = moops.Group()
input_text = args.text(value="", help_text="Input text")
style = args.dropdown(
    ["snake_case", "camel_case"],
    value="snake_case",
    help_text="Output style",
    allow_select_none=False,
)

Opening the notebook with ?input_text=Hello&style=camel_case initializes those controls from the URL. Query keys use the same names as moops.run keyword arguments. For subgroups, use dot-separated names such as ?casing.style=camel_case.

Presets

Presets save and restore named groups of control values from a JSON file stored next to the calling notebook as <notebook>_presets.json.

get_preset, set_preset = mo.state(None)
args = moops.Group(presets=moops.Presets(get_preset, set_preset))

Custom notebook controls

Use args.custom() when the notebook needs an interactive control that moops does not wrap directly, while the CLI should use a supported fallback control. The fallback supplies the CLI parser, help text, defaults, and query-parameter format.

plot_selection = mo.ui.matplotlib(ax)
fallback_slider = args.range_slider(
    start=0,
    stop=100,
    value=[10, 50],
    option="--x-range",
    help_text="X axis range",
)
x_range = args.custom(
    plot_selection,
    fallback_slider,
    value=lambda plot:
        [plot.value.x_min, plot.value.x_max]
        if plot.value else fallback_slider.value,
)

Property-based testing

moops.testing.notebook_interface returns the notebook's Interface, from which .strategy() generates a Hypothesis strategy that produces valid moops.run kwargs by introspecting the notebook's interface — dropdowns yield their allowed keys, switches yield booleans, and text fields yield arbitrary strings.

from examples import name_casing

_name_casing_interface = moops.testing.notebook_interface(name_casing)
_name_casing_defaults = _name_casing_interface.default

@hypothesis.given(_name_casing_interface.strategy())
def test_name_casing_preserves_alphanumeric_count(kwargs):
    result = moops.run(name_casing, **kwargs)
    input_text = kwargs.get("input_text", _name_casing_defaults["input_text"])
    assert sum(c.isalnum() for c in result) == sum(c.isalnum() for c in input_text)

Running the examples

From the project root:

uv run examples/notebook.py
uv run --with matplotlib --with numpy examples/custom_control.py --x-range 30,70

Or uv run marimo edit to run as notebooks.

Feedback welcome

This is an early release — issues, ideas, and pull requests are very welcome on GitHub.

Project details


Download files

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

Source Distribution

moops-0.3.1.tar.gz (20.0 kB view details)

Uploaded Source

Built Distribution

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

moops-0.3.1-py3-none-any.whl (24.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: moops-0.3.1.tar.gz
  • Upload date:
  • Size: 20.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for moops-0.3.1.tar.gz
Algorithm Hash digest
SHA256 efcbd7c1bbac3d4b0a26361883c5199d963d5e169d22c0648b1101e0c17d5676
MD5 952f60f60d5b1350df3fe7b921e729c8
BLAKE2b-256 57fbb3401f8c6a614e5d207f8a19fe2581424ee51ccfb3478c4016f27789e634

See more details on using hashes here.

Provenance

The following attestation bundles were made for moops-0.3.1.tar.gz:

Publisher: publish.yml on yairchu/moops

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

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

File metadata

  • Download URL: moops-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 24.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for moops-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 fb8cabe7d7e8fe6e985c418e5be126b0f3e0d2b0149eb035eabcd0924336a13f
MD5 291062caa395a71bbb38172744b06266
BLAKE2b-256 14b6a623940a72667151da64e8ec7638ff903737253574d2ded8c281371f1984

See more details on using hashes here.

Provenance

The following attestation bundles were made for moops-0.3.1-py3-none-any.whl:

Publisher: publish.yml on yairchu/moops

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page