pytest-marimo is a pytest plugin that enforces marimo notebook quality rules.
It is aimed at checks that are specific to marimo’s reactive notebook model, not generic Python linting.
pytest-marimo is a lightweight semantic checker for marimo notebooks that runs in Python tooling workflows.
It focuses on enforcing notebook patterns that are easy to miss in review, such as:
on_changecallback usage where reactivity is clearer- cross-cell mutation of shared objects
- non-idempotent cell behaviour
- mixed test/helper cells and fixture placement conventions
marimo already gives you:
- native notebook testing with
pytest - built-in notebook linting via
marimo check(announcement)
pytest-marimo is designed to complement those tools with opinionated, team-level checks tailored to a stricter “production notebook” style.
In practice:
- use Ruff for general Python quality/security
- use marimo check for core notebook validity and formatting rules
- use pytest-marimo for extra policy checks around reactive design and notebook maintainability
pytest-marimo is especially useful as an automated quality gate when notebooks are generated or edited by coding agents (for example Claude, Warp, Codex, or similar tools).
Adding it to pre-commit and CI helps catch marimo-specific issues immediately, so agents can self-correct before code reaches review.
Example pre-commit hook:
repos:
- repo: local
hooks:
- id: pytest-marimo-quality
name: pytest-marimo-quality
entry: uv run pytest-marimo-quality experiments notebooks
language: system
pass_filenames: falseThis keeps the feedback loop short:
- agent proposes notebook edits
- pre-commit/CI runs Ruff +
pytest-marimochecks - agent fixes violations and retries
M001: prefer reactive dependencies overon_changehandlers.M002: keep test cells focused; avoid mixing tests with helper/setup code in the same cell.M003: avoid mutable module-level state in notebook files.M004: prefer fixtures inconftest.pyor helper modules rather than notebook modules.M005: avoid cross-cell mutation of shared objects (including notebook inputs and module-level mutable state).M006: avoid non-idempotent calls in cells (for examplerandom.*,np.random.*,time.time,uuid.uuid4).
Install in a project:
uv add --dev pytest-marimoRun checks explicitly:
uv run pytest --marimo-checkEnable by default in pyproject.toml:
[tool.pytest.ini_options]
marimo_check = trueFilter rules:
uv run pytest --marimo-check --marimo-check-select M001 --marimo-check-ignore M004Run combined Ruff + pytest-marimo semantic checks:
uv run pytest-marimo-quality path/to/notebooksSkip Ruff and run only pytest-marimo semantic checks:
uv run pytest-marimo-quality --skip-ruff path/to/notebooksThe repository includes notebook fixtures in tests/fixtures:
tests/fixtures/synthetic: synthetic notebooks for targeted pass/fail checks.tests/fixtures/real: real-world notebooks sourced from public marimo repositories (with provenance intests/fixtures/real/SOURCES.txt).