feat: Add basic direct edit/run support for .ipynb-formatted notebooks.#9899
Open
asford wants to merge 5 commits into
Open
feat: Add basic direct edit/run support for .ipynb-formatted notebooks.#9899asford wants to merge 5 commits into
asford wants to merge 5 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
for more information, see https://pre-commit.ci
Contributor
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds first-class handling for Jupyter .ipynb notebooks across serialization, CLI validation, and workspace discovery.
Changes:
- Introduces
IpynbNotebookSerializerand registers.ipynbin the serializer registry, plus conversion IR↔ipynb helpers. - Extends discovery/validation paths (directory scanning, OS filesystem, CLI file handling) to recognize marimo
.ipynbnotebooks. - Adds/updates tests and test dependencies (nbformat) to cover
.ipynbbehavior and round-trips.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/_session/notebook/test_serializer.py | Adds unit + round-trip tests for .ipynb serializer and routing. |
| tests/_session/notebook/test_app_file_manager.py | Tests rename-driven format conversion .py ↔ .ipynb. |
| tests/_server/test_directory_scanner.py | Adds .ipynb discovery/detection tests for scanner + OS filesystem. |
| tests/_cli/test_file_path.py | Expands CLI validation tests for .ipynb (missing/invalid/valid). |
| pyproject.toml | Adds nbformat to test dependencies for ipynb round-trip tests. |
| marimo/_utils/marimo_path.py | Treats .ipynb as a valid marimo path type. |
| marimo/_session/notebook/serializer.py | Adds marimo detection API + new .ipynb serializer + registry update. |
| marimo/_server/files/os_file_system.py | Enables creating new .ipynb notebooks from IR. |
| marimo/_server/files/directory_scanner.py | Delegates marimo-file detection to serializers and includes .ipynb in scans. |
| marimo/_server/asgi.py | Adds TODO notes for .ipynb routing/caching gaps. |
| marimo/_server/api/endpoints/editing.py | Makes formatting treat all non-.py as Python for formatter context. |
| marimo/_convert/ipynb/to_ir.py | Propagates filepath into IR (filename). |
| marimo/_convert/ipynb/from_ir.py | Adds ir_to_ipynb to serialize without requiring an InternalApp. |
| marimo/_cli/files/file_path.py | Accepts .ipynb in readers and validates local .ipynb inputs. |
| frontend/src/components/pages/home-page.tsx | Adds TODO for .ipynb icon/badge handling. |
| frontend/src/components/editor/header/filename-input.tsx | Adds .ipynb to allowed extension suggestions (kept in sync note). |
Comment on lines
+56
to
+65
| def is_marimo_notebook(self, path: Path) -> bool: | ||
| """Check if a file is a marimo notebook. | ||
|
|
||
| Args: | ||
| path: File path to check | ||
|
|
||
| Returns: | ||
| True if the file is a marimo notebook, False otherwise | ||
| """ | ||
| ... |
Comment on lines
+215
to
+223
| import json | ||
|
|
||
| try: | ||
| with open(path, "r", encoding="utf-8") as f: | ||
| data = json.load(f) | ||
| metadata = data.get("metadata", {}) | ||
| return "marimo" in metadata | ||
| except Exception: | ||
| return False |
|
|
||
| def is_valid(self) -> bool: | ||
| return self.is_python() or self.is_markdown() | ||
| return self.is_python() or self.is_markdown() or self.is_ipynb() |
Comment on lines
+56
to
58
| if not path_parts[-1].endswith((".py", ".md", ".ipynb")): | ||
| raise ValueError("No python or markdown files found in the Gist") | ||
| return url |
Comment on lines
319
to
+344
| if path.suffix == ".ipynb": | ||
| prefix = str(path)[: -len(".ipynb")] | ||
| raise click.ClickException( | ||
| f"Invalid NAME - {name} is not a Python file.\n\n" | ||
| f" {green('Tip:')} Convert {name} to a marimo notebook with" | ||
| "\n\n" | ||
| f" marimo convert {name} -o {prefix}.py\n\n" | ||
| f" then open with marimo edit {prefix}.py" | ||
| if not path.exists(): | ||
| if self.allow_new_file: | ||
| return name, None | ||
| raise click.ClickException( | ||
| f"Invalid NAME - {name} does not exist" | ||
| ) | ||
| # Verify the ipynb can be loaded by the registered serializer | ||
| from marimo._session.notebook.serializer import ( | ||
| IpynbNotebookSerializer, | ||
| ) | ||
|
|
||
| try: | ||
| IpynbNotebookSerializer().deserialize( | ||
| path.read_text(encoding="utf-8") | ||
| ) | ||
| except Exception: | ||
| prefix = str(path)[: -len(".ipynb")] | ||
| raise click.ClickException( | ||
| f"Invalid NAME - {name} is not a valid Jupyter notebook.\n\n" | ||
| f" {green('Tip:')} Convert {name} to a marimo notebook with" | ||
| "\n\n" | ||
| f" marimo convert {name} -o {prefix}.py\n\n" | ||
| f" then open with marimo edit {prefix}.py" | ||
| ) | ||
| return name, None |
Comment on lines
+115
to
+123
| # Verify the .ipynb content is valid JSON with marimo metadata | ||
| with open(ipynb_path, "r", encoding="utf-8") as f: | ||
| ipynb_data = json.load(f) | ||
| assert "cells" in ipynb_data | ||
| assert "metadata" in ipynb_data | ||
| assert "marimo" in ipynb_data["metadata"] | ||
| assert "marimo_version" in ipynb_data["metadata"]["marimo"] | ||
| # Verify cell content is preserved | ||
| assert any("x = 1" in cell["source"] for cell in ipynb_data["cells"]) |
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📝 Summary
From: #5318
edit/run.Goal: Enable Marimo to support .ipynb as a primary file format in addition to marimo-script and markdown formats.
This allows teams to progressively adopt Marimo while maintaining compatibility with existing Jupyter-based tool stacks.
The primary motivating factor for this work is direct integration with .ipynb support in IDE assisted editing workflows.
Marimo is an excellent viewer application for AI-assisted editing, but tools like VSCode Copilot can not reliably edit marimo .py format files; breaking workflows where both code and notebook files are being updated in a single AI-assisted session.
Many toolstacks already natively support .ipynb notebook (e.g. Pyrefly checking, Copilot assistance, etc...).
Extending marimo to directly edit .ipynb files allows seamless integration with existing tooling.
Current Architecture Summary
NotebookSerializer marimo/_session/notebook/serializer.py
Marimo already has a pluggable serializer architecture in place.
This is semi-trivially extended with additional extension-specific format handlers.
Integration Points
AppFileManagerclass_save_file()method usesget_notebook_serializer()to serialize line 242_load_app()method uses handler to deserialize line 194get_notebook_status()functionget_notebook_serializer()to deserialize line 108.ipynbConversionMarimo already implements isomorphic conversion between Marimo .py and .ipynb formats.
Note however, not every .ipynb is a valid marimo notebook, as Marimo requires dag-like cell ordering and unique varible definitions.
Valid marimo-style .ipynb files can be trivially serialized to/from the .ipynb and .py formats using existing conversion code.
Notebooks which are not valid marimo notebooks can be converted to marimo-compatible forms via
marimo convert.Approach Desision:
There are two approaches:
(a) Implicit Conversion — As most .ipynb can be converted to a marimo-compatible format,
allow editing any .ipynb file by applying the automatic .ipynb conversion heuristics.
This may fail for some notebooks and will semi-transparently change notebook formats,
but will make onboarding marimo in .ipynb workflows trivial.
(b) Explicit Converstion — Require an explicit conversion call for .ipynb notebooks to inject marimo metadata.
This means any newly created or explicitly converted notebook will be editable via marimo, but requires manual import flow.
Decision for PR
.ipynbsupport will be explicit opt-in, rather than automatically opening and converting existing.ipynbnotebooks.The
marimo editandmarimo runcommands and file tree will only show notebooks that have explicit marimo metadata.This should be reconsidered later if the feature proves useful and UI patterns are established to support .ipynb formats.
Existing .ipynb Conversion Code
convert_from_ipynb_to_notebook_ir()marimo/_convert/ipynb/to_ir.pyconvert_from_ir_to_ipynb()marimo/_convert/ipynb/from_ir.py.pyand.md|.qmdHardcoded ExtensionsMarimo has a long-running assumption that all valid notebook files are either python or markdown formatted.
The Marimo codebase has multiple locations which explicitly check for these file extensions.
While server-side components could all be refactored to rely on a dynamic serializer registry,
this may require adding undesirable circular dependencies between modules.
Additionally, there are limited number of frontend components which also rely on explicit extension detection.
Approach Decision:
There are two approaches:
.ipynbto each list as needed. Simple, but every newformat requires touching many files.
DEFAULT_NOTEBOOK_SERIALIZERSat runtime.More effort upfront but stays in sync automatically.
Decision for PR Use approach (a). Add
.ipynbto all hardcoded extension lists.Add TODO comments at locations that need a more fundamental refactor.
📋 Pre-Review Checklist
✅ Merge Checklist