Skip to main content

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

Agent-assisted migrations

If you use a coding agent, moops includes an agent skill for migrating existing marimo notebooks: migrate-marimo-to-moops.

Install or point your agent at that skill, then ask it to migrate a notebook to moops while preserving the notebook's behavior.

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.composition 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.

Variant inputs

Use args.variant() to create branch subgroups controlled by a selector. Branch controls are normal controls and should still be passed to args.interface(); inactive branch controls are disabled automatically, and CLI help groups branch options under selector-specific headings.

source = args.dropdown(
    ["heuristic", "file"],
    value="heuristic",
    option="--source",
    help_text="Seed source",
    allow_select_none=False,
)
seed = args.variant("seed", source)

budget = seed["heuristic"].number(value=100, help_text="Heuristic budget")
path = seed["file"].text(value="", help_text="Result file")

interface = args.interface(
    source,
    seed["heuristic"].interface(budget),
    seed["file"].interface(path),
)

Pass short_options=True to args.variant() to omit the variant group name from branch CLI options, such as --file-path instead of --seed-file-path. Moops rejects shortened options that would collide.

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))

With presets enabled, the command line shown in the script callout is editable: edit it in place (or paste a different command) and commit to initialize every control from those arguments. Malformed input is reported inline.

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.

build(value) is a factory that constructs the notebook component from the fallback's resolved value. Passing a factory (rather than a pre-built control) lets controls_from recreate the component when the notebook is mirrored into a parent. value(component, fallback) maps the component's value to the fallback's shape.

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

Property-based testing

moops.interface_of 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.

Hypothesis is an optional dependency, since it is only needed for .strategy(); install it with pip install moops[test] (or just pip install hypothesis).

from examples.composition import name_casing

_name_casing_interface = moops.interface_of(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

The examples/ directory is grouped by topic:

  • basics/ — small notebooks covering options, flags, file inputs, dataclass inputs, and status output
  • custom_controls/args.custom() and mirroring it via controls_from
  • composition/ — embedding and varying notebooks (embed, variant_embed)
  • game_of_life/ — a worked multi-notebook example
  • passthrough/ — passing values between notebooks

From the project root:

uv run examples/composition/notebook.py
uv run --with matplotlib --with numpy examples/custom_controls/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.

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.14.4.tar.gz (62.2 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.14.4-py3-none-any.whl (73.5 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for moops-0.14.4.tar.gz
Algorithm Hash digest
SHA256 9d128cf885cc330e38f366f9f835cf57cbd6814287e85fdc1ff44891e8b85e2a
MD5 c64433c911bb457edebb7f3263d45b0e
BLAKE2b-256 1d768582bc853b13bb8269bae5a9ce4ebf67e283d3cf394eba3be48c2ef58dcb

See more details on using hashes here.

Provenance

The following attestation bundles were made for moops-0.14.4.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.14.4-py3-none-any.whl.

File metadata

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

File hashes

Hashes for moops-0.14.4-py3-none-any.whl
Algorithm Hash digest
SHA256 d318d3add96a3753ce406951f7d395e434cd92860739bf5dda12ecd2f12f1b72
MD5 7fa745599f92b3a6c3076f64f7ae2362
BLAKE2b-256 552e3b924b40fa3a9bc7bccbca1b08be35389ef3aaa77b9b538191c6c0594259

See more details on using hashes here.

Provenance

The following attestation bundles were made for moops-0.14.4-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.

Release history Release notifications | RSS feed

0.15.5

2 files

0.15.4

2 files

0.15.3

2 files

0.15.2

2 files

0.15.1

2 files

0.15.0

2 files

This release

0.14.4 This release

2 files

0.14.3

2 files

0.14.2

2 files

0.14.1

2 files

0.14.0

2 files

0.13.12

2 files

0.13.11

2 files

0.13.10

2 files

0.13.9

2 files

0.13.8

2 files

0.13.7

2 files

0.13.6

2 files

0.13.5

2 files

0.13.4

2 files

0.13.3

2 files

0.13.2

2 files

0.13.1

2 files

0.13.0

2 files

0.12.2

2 files

0.12.1

2 files

0.12.0

2 files

0.11.5

2 files

0.11.4

2 files

0.11.3

2 files

0.11.2

2 files

0.11.1

2 files

0.11.0

2 files

0.10.0

2 files

0.9.0

2 files

0.8.0

2 files

0.7.2

2 files

0.7.1

2 files

0.7.0

2 files

0.6.1

2 files

0.6.0

2 files

0.5.1

2 files

0.5.0

2 files

0.4.0

2 files

0.3.3

2 files

0.3.2

2 files

0.3.1

2 files

0.3.0

2 files

0.2.0

2 files

0.1.1

2 files

0.1.0

2 files

Supported by

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