Skip to content

Commit 0f2f471

Browse files
committed
fix: do not stub markdown when mo is not defined
1 parent a0781ba commit 0f2f471

File tree

2 files changed

+99
-9
lines changed

2 files changed

+99
-9
lines changed

marimo/_runtime/runtime.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2037,15 +2037,33 @@ def _handle_markdown_cells_on_instantiate(self) -> None:
20372037
4. Marks the cells as completed (not stale)
20382038
5. Removes them from uninstantiated requests
20392039
2040+
If 'mo' is not available in the graph definitions, all cells are marked as stale.
20402041
Regular cells are marked as stale as usual.
20412042
"""
2043+
# If 'mo' is not available in the graph, mark all cells as stale
2044+
if "mo" not in self.graph.definitions:
2045+
for cid in self._uninstantiated_execution_requests:
2046+
CellOp.broadcast_stale(cell_id=cid, stale=True)
2047+
return
2048+
20422049
markdown_cells_to_remove = set()
20432050
for cid, er in self._uninstantiated_execution_requests.items():
2044-
# Try to compile the cell to check if it has markdown
2045-
cell, error = self._try_compiling_cell(cid, er.code, [])
2046-
if error is None and cell is not None and cell.markdown is not None:
2051+
# Check if cell already exists in graph (to avoid recompilation)
2052+
cell = self.graph.cells.get(cid)
2053+
2054+
# If cell doesn't exist in graph, try to compile it
2055+
if cell is None:
2056+
cell, error = self._try_compiling_cell(cid, er.code, [])
2057+
if error is not None:
2058+
# Compilation error - mark as stale
2059+
CellOp.broadcast_stale(cell_id=cid, stale=True)
2060+
continue
2061+
2062+
# Check if this is a markdown cell
2063+
if cell is not None and cell.markdown is not None:
20472064
# This is a markdown cell - render and send its output immediately
20482065
from marimo._output.md import md
2066+
20492067
html_obj = md(cell.markdown)
20502068
mimetype, html_content = html_obj._mime_()
20512069

tests/_runtime/test_runtime.py

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3139,7 +3139,9 @@ async def test_markdown_cells_rendered_on_instantiate(
31393139
stream = mocked_kernel.stream
31403140

31413141
# Create execution requests with markdown and regular cells
3142-
markdown_cell_code = 'mo.md("# Hello World\\n\\nThis is **markdown**.")'
3142+
markdown_cell_code = (
3143+
'mo.md("# Hello World\\n\\nThis is **markdown**.")'
3144+
)
31433145
regular_cell_code = "x = 1"
31443146

31453147
execution_requests = [
@@ -3160,6 +3162,10 @@ async def test_markdown_cells_rendered_on_instantiate(
31603162
# Clear stream before instantiate
31613163
stream.messages.clear()
31623164

3165+
# First, manually add mo to the graph definitions to simulate it being available
3166+
# This simulates the scenario where mo has been imported in a previous cell
3167+
k.graph.definitions["mo"] = {"import_mo"}
3168+
31633169
# Instantiate the kernel
31643170
await k.instantiate(creation_request)
31653171

@@ -3195,8 +3201,12 @@ async def test_markdown_cells_rendered_on_instantiate(
31953201
assert stale_ops[0].stale_inputs is False
31963202

31973203
# Check that regular cell was marked as stale
3198-
regular_cell_ops = [op for op in cell_ops if op.cell_id == "regular_cell"]
3199-
regular_stale_ops = [op for op in regular_cell_ops if op.stale_inputs is not None]
3204+
regular_cell_ops = [
3205+
op for op in cell_ops if op.cell_id == "regular_cell"
3206+
]
3207+
regular_stale_ops = [
3208+
op for op in regular_cell_ops if op.stale_inputs is not None
3209+
]
32003210
assert len(regular_stale_ops) == 1
32013211
assert regular_stale_ops[0].stale_inputs is True
32023212

@@ -3235,7 +3245,9 @@ async def test_non_markdown_cells_not_affected(
32353245

32363246
for cell_id in ["cell1", "cell2"]:
32373247
cell_ops_for_id = [op for op in cell_ops if op.cell_id == cell_id]
3238-
stale_ops = [op for op in cell_ops_for_id if op.stale_inputs is not None]
3248+
stale_ops = [
3249+
op for op in cell_ops_for_id if op.stale_inputs is not None
3250+
]
32393251
assert len(stale_ops) == 1
32403252
assert stale_ops[0].stale_inputs is True
32413253

@@ -3248,7 +3260,9 @@ async def test_malformed_markdown_cells_marked_stale(
32483260

32493261
# Create execution requests with malformed markdown cell
32503262
execution_requests = [
3251-
ExecutionRequest(cell_id="bad_cell", code="mo.md("), # Syntax error
3263+
ExecutionRequest(
3264+
cell_id="bad_cell", code="mo.md("
3265+
), # Syntax error
32523266
ExecutionRequest(cell_id="good_md", code='mo.md("# Good")'),
32533267
]
32543268

@@ -3262,6 +3276,11 @@ async def test_malformed_markdown_cells_marked_stale(
32623276
)
32633277

32643278
stream.messages.clear()
3279+
3280+
# First, manually add mo to the graph definitions to simulate it being available
3281+
# This simulates the scenario where mo has been imported in a previous cell
3282+
k.graph.definitions["mo"] = {"import_mo"}
3283+
32653284
await k.instantiate(creation_request)
32663285

32673286
# Bad cell should remain in uninstantiated requests
@@ -3275,7 +3294,9 @@ async def test_malformed_markdown_cells_marked_stale(
32753294

32763295
# Bad cell should be marked as stale
32773296
bad_cell_ops = [op for op in cell_ops if op.cell_id == "bad_cell"]
3278-
bad_stale_ops = [op for op in bad_cell_ops if op.stale_inputs is not None]
3297+
bad_stale_ops = [
3298+
op for op in bad_cell_ops if op.stale_inputs is not None
3299+
]
32793300
assert len(bad_stale_ops) == 1
32803301
assert bad_stale_ops[0].stale_inputs is True
32813302

@@ -3285,6 +3306,57 @@ async def test_malformed_markdown_cells_marked_stale(
32853306
assert len(good_output_ops) == 1
32863307
assert "Good" in good_output_ops[0].output.data
32873308

3309+
async def test_no_mo_available_all_cells_stale(
3310+
self, mocked_kernel: MockedKernel
3311+
) -> None:
3312+
"""Test that when 'mo' is not available, all cells are marked as stale."""
3313+
k = mocked_kernel.k
3314+
stream = mocked_kernel.stream
3315+
3316+
# Create execution requests with markdown cells
3317+
execution_requests = [
3318+
ExecutionRequest(cell_id="md_cell1", code='mo.md("# Hello")'),
3319+
ExecutionRequest(cell_id="md_cell2", code='mo.md("## World")'),
3320+
ExecutionRequest(cell_id="regular_cell", code="x = 1"),
3321+
]
3322+
3323+
creation_request = CreationRequest(
3324+
execution_requests=execution_requests,
3325+
auto_run=False,
3326+
set_ui_element_value_request=SetUIElementValueRequest(
3327+
object_ids=[],
3328+
values=[],
3329+
),
3330+
)
3331+
3332+
stream.messages.clear()
3333+
3334+
# Ensure 'mo' is not in graph definitions by starting with empty graph
3335+
assert "mo" not in k.graph.definitions
3336+
3337+
await k.instantiate(creation_request)
3338+
3339+
# All cells should remain in uninstantiated requests since mo is not available
3340+
assert "md_cell1" in k._uninstantiated_execution_requests
3341+
assert "md_cell2" in k._uninstantiated_execution_requests
3342+
assert "regular_cell" in k._uninstantiated_execution_requests
3343+
3344+
# Check that all cells were marked as stale
3345+
cell_ops = [deserialize_kernel_message(msg) for msg in stream.messages]
3346+
cell_ops = [op for op in cell_ops if isinstance(op, CellOp)]
3347+
3348+
for cell_id in ["md_cell1", "md_cell2", "regular_cell"]:
3349+
cell_ops_for_id = [op for op in cell_ops if op.cell_id == cell_id]
3350+
stale_ops = [
3351+
op for op in cell_ops_for_id if op.stale_inputs is not None
3352+
]
3353+
assert len(stale_ops) == 1
3354+
assert stale_ops[0].stale_inputs is True
3355+
3356+
# No cells should have output operations
3357+
output_ops = [op for op in cell_ops if op.output is not None]
3358+
assert len(output_ops) == 0
3359+
32883360

32893361
def _parse_error_output(cell_op: CellOp) -> list[Error]:
32903362
error_output = cell_op.output

0 commit comments

Comments
 (0)