Skip to content

Commit e94fd87

Browse files
mscolnickdmadisettipre-commit-ci[bot]
authored
tests: idempotnent ipynb (marimo-team#7939)
Co-authored-by: dmadisetti <dmadisetti@coreweave.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent d303573 commit e94fd87

30 files changed

Lines changed: 1306 additions & 22 deletions

marimo/_convert/common/format.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@
99
if TYPE_CHECKING:
1010
from marimo._ast.cell import Cell, CellImpl
1111

12+
DEFAULT_MARKDOWN_PREFIX = "r"
1213

13-
def markdown_to_marimo(source: str) -> str:
14+
15+
def markdown_to_marimo(
16+
source: str, prefix: str = DEFAULT_MARKDOWN_PREFIX
17+
) -> str:
1418
# NB. This should be kept in sync with the logic in
1519
# frontend/src/core/codemirror/language/languages/markdown.ts
1620
# ::transformOut
@@ -19,7 +23,7 @@ def markdown_to_marimo(source: str) -> str:
1923
# 6 quotes in a row breaks
2024
if not source:
2125
source = " "
22-
return codegen.construct_markdown_call(source, '"""', "r")
26+
return codegen.construct_markdown_call(source, '"""', prefix)
2327

2428

2529
def sql_to_marimo(

marimo/_convert/ipynb/from_ir.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,17 @@
3232

3333
# Note: We intentionally omit "version" as it would vary across environments
3434
# and break reproducibility. The marimo_version in metadata is sufficient.
35+
_MD_PREFIX_RE = re.compile(r'mo\.md\(([fFrR]*)(?:"""|\'\'\'|"|\')')
36+
37+
38+
def _extract_markdown_prefix(code: str) -> str:
39+
"""Extract the string prefix from a mo.md() call (e.g. '', 'r', 'f', 'fr')."""
40+
m = _MD_PREFIX_RE.search(code)
41+
if m:
42+
return m.group(1).lower()
43+
return ""
44+
45+
3546
DEFAULT_LANGUAGE_INFO = {
3647
"codemirror_mode": {"name": "ipython", "version": 3},
3748
"file_extension": ".py",
@@ -162,7 +173,9 @@ def _create_ipynb_cell(
162173
nbformat.NotebookNode,
163174
nbformat.v4.new_markdown_cell(markdown_string, id=cell_id), # type: ignore[no-untyped-call]
164175
)
165-
_add_marimo_metadata(node, name, config)
176+
_add_marimo_metadata(
177+
node, name, config, md_prefix=_extract_markdown_prefix(code)
178+
)
166179
return node
167180

168181
node = cast(
@@ -176,14 +189,21 @@ def _create_ipynb_cell(
176189

177190

178191
def _add_marimo_metadata(
179-
node: NotebookNode, name: str, config: CellConfig
192+
node: NotebookNode,
193+
name: str,
194+
config: CellConfig,
195+
md_prefix: Optional[str] = None,
180196
) -> None:
181197
"""Add marimo-specific metadata to a notebook cell."""
182198
marimo_metadata: dict[str, Any] = {}
183199
if config.is_different_from_default():
184200
marimo_metadata["config"] = config.asdict_without_defaults()
185201
if not is_internal_cell_name(name):
186202
marimo_metadata["name"] = name
203+
if md_prefix is not None:
204+
# Always store prefix for markdown cells so importer knows the original
205+
# prefix and can distinguish marimo-created cells from foreign ipynb cells
206+
marimo_metadata["md_prefix"] = md_prefix
187207
if marimo_metadata:
188208
node["metadata"]["marimo"] = marimo_metadata
189209

marimo/_convert/ipynb/to_ir.py

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@
1111

1212
from marimo._ast.cell import CellConfig
1313
from marimo._ast.compiler import compile_cell
14+
from marimo._ast.names import DEFAULT_CELL_NAME
1415
from marimo._ast.transformers import NameTransformer, RemoveImportTransformer
1516
from marimo._ast.variables import is_local
1617
from marimo._ast.visitor import Block, NamedNode, ScopedVisitor
17-
from marimo._convert.common.format import markdown_to_marimo
18+
from marimo._convert.common.format import (
19+
DEFAULT_MARKDOWN_PREFIX,
20+
markdown_to_marimo,
21+
)
1822
from marimo._runtime.dataflow import DirectedGraph
1923
from marimo._schemas.serialization import (
2024
AppInstantiation,
@@ -31,6 +35,7 @@
3135
@dataclass
3236
class CodeCell:
3337
source: str
38+
name: str = field(default_factory=lambda: DEFAULT_CELL_NAME)
3439
config: CellConfig = field(default_factory=CellConfig)
3540

3641

@@ -1169,24 +1174,30 @@ def bind_cell_metadata(
11691174
cells: list[CodeCell] = []
11701175
for source, meta, hide_code in zip(sources, metadata, hide_flags):
11711176
tags: set[str] = set(meta.get("tags", []))
1172-
if "hide-cell" in tags:
1173-
tags.discard("hide-cell")
1174-
hide_code = True
1175-
if tags:
1176-
source = f"# Cell tags: {', '.join(sorted(tags))}\n{source}"
11771177

11781178
# Extract marimo-specific cell config if present
11791179
marimo_meta = meta.get("marimo", {})
11801180
marimo_config = marimo_meta.get("config", {})
1181+
name = marimo_meta.get("name", DEFAULT_CELL_NAME)
11811182

1182-
# Merge marimo config with existing flags
1183-
# marimo config takes precedence for hide_code if present
1183+
# Determine hide_code with priority: marimo config > tags > hide_flags default
11841184
if "hide_code" in marimo_config:
11851185
hide_code = marimo_config["hide_code"]
1186+
elif "hide-cell" in tags:
1187+
tags.discard("hide-cell")
1188+
hide_code = True
1189+
elif "marimo" in meta:
1190+
# Cell was created by marimo; marimo would have stored hide_code=True
1191+
# explicitly if needed, so default to False instead of is_markdown
1192+
hide_code = False
1193+
1194+
if tags:
1195+
source = f"# Cell tags: {', '.join(sorted(tags))}\n{source}"
11861196

11871197
cells.append(
11881198
CodeCell(
11891199
source=source,
1200+
name=name,
11901201
config=CellConfig(
11911202
hide_code=hide_code,
11921203
column=marimo_config.get("column"),
@@ -1393,7 +1404,11 @@ def convert_from_ipynb_to_notebook_ir(
13931404
)
13941405
is_markdown: bool = cell["cell_type"] == "markdown"
13951406
if is_markdown:
1396-
source = markdown_to_marimo(source)
1407+
cell_meta = cell.get("metadata", {})
1408+
md_prefix = cell_meta.get("marimo", {}).get(
1409+
"md_prefix", DEFAULT_MARKDOWN_PREFIX
1410+
)
1411+
source = markdown_to_marimo(source, prefix=md_prefix)
13971412
elif inline_meta is None:
13981413
# Eagerly find PEP 723 metadata, first match wins
13991414
inline_meta, source = extract_inline_meta(source)
@@ -1420,6 +1435,7 @@ def convert_from_ipynb_to_notebook_ir(
14201435
cells=[
14211436
CellDef(
14221437
code=cell.source,
1438+
name=cell.name,
14231439
options=cell.config.asdict(),
14241440
)
14251441
for cell in transformed_cells

tests/_cli/snapshots/export/ipynb/ipynb.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@
3535
{
3636
"cell_type": "markdown",
3737
"id": "vblA",
38-
"metadata": {},
38+
"metadata": {
39+
"marimo": {
40+
"md_prefix": ""
41+
}
42+
},
3943
"source": [
4044
"plain markdown"
4145
]

tests/_cli/snapshots/export/ipynb/ipynb_topdown.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@
2525
{
2626
"cell_type": "markdown",
2727
"id": "vblA",
28-
"metadata": {},
28+
"metadata": {
29+
"marimo": {
30+
"md_prefix": ""
31+
}
32+
},
2933
"source": [
3034
"plain markdown"
3135
]

tests/_cli/snapshots/export/ipynb/ipynb_with_media_outputs.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"id": "MJUe",
2828
"metadata": {
2929
"marimo": {
30+
"md_prefix": "",
3031
"name": "pure_markdown_cell"
3132
}
3233
},

tests/_cli/snapshots/export/ipynb/ipynb_with_outputs.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,11 @@
5555
{
5656
"cell_type": "markdown",
5757
"id": "vblA",
58-
"metadata": {},
58+
"metadata": {
59+
"marimo": {
60+
"md_prefix": ""
61+
}
62+
},
5963
"source": [
6064
"plain markdown"
6165
]
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import marimo
2+
3+
__generated_with = "0.0.0"
4+
app = marimo.App()
5+
6+
7+
@app.cell
8+
def _():
9+
import marimo as mo
10+
11+
return (mo,)
12+
13+
14+
@app.cell
15+
def _():
16+
x = 1
17+
return (x,)
18+
19+
20+
@app.cell(hide_code=True)
21+
def _(mo):
22+
mo.md("""
23+
# This cell is hidden
24+
""")
25+
return
26+
27+
28+
@app.cell(disabled=True)
29+
def _(x):
30+
y = x + 1
31+
return (y,)
32+
33+
34+
@app.cell
35+
def _(y):
36+
print(y)
37+
return
38+
39+
40+
if __name__ == "__main__":
41+
app.run()
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import marimo
2+
3+
__generated_with = "0.0.0"
4+
app = marimo.App()
5+
6+
7+
@app.cell
8+
def my_imports():
9+
import os
10+
import sys
11+
12+
return os, sys
13+
14+
15+
@app.cell
16+
def compute(os, sys):
17+
result = os.getcwd() + sys.platform
18+
return (result,)
19+
20+
21+
@app.cell
22+
def display(result):
23+
print(result)
24+
return
25+
26+
27+
@app.function
28+
def add(a, b):
29+
return a + b
30+
31+
32+
@app.function(hide_code=True)
33+
def subtract(a, b):
34+
return a - b
35+
36+
37+
@app.class_definition
38+
class MyClass:
39+
def __init__(self, val):
40+
self.val = val
41+
def double(self):
42+
return self.val * 2
43+
44+
45+
@app.class_definition(hide_code=True)
46+
class HiddenClass:
47+
def __init__(self, val):
48+
self.val = val
49+
50+
51+
if __name__ == "__main__":
52+
app.run()

tests/_convert/ipynb/fixtures/py/complex_file_format.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
import marimo
1212

13-
__generated_with = "0.19.2"
13+
__generated_with = "0.0.0"
1414
app = marimo.App(width="medium", auto_download=["html"], sql_output="native")
1515

1616
with app.setup:
@@ -36,6 +36,7 @@ def imports():
3636
"""Named cell with imports."""
3737
import pandas as pd
3838
import numpy as np
39+
3940
return np, pd
4041

4142

0 commit comments

Comments
 (0)