Skip to content

Commit 1067a06

Browse files
authored
fix: track contents to avoid reloading on our own writes (#7099)
This PR track contents that we wrong to avoid reloading on our own writes when file watching.
1 parent 0659a19 commit 1067a06

File tree

3 files changed

+96
-3
lines changed

3 files changed

+96
-3
lines changed

marimo/_server/file_manager.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ def __init__(
4747
)
4848
self._default_sql_output: SqlOutputType | None = default_sql_output
4949
self.app = self._load_app(self.path)
50+
# Track the last saved content to avoid reloading our own writes
51+
self._last_saved_content: Optional[str] = None
5052

5153
@staticmethod
5254
def from_app(app: InternalApp) -> AppFileManager:
@@ -128,6 +130,7 @@ def _rename_file(self, new_filename: str) -> None:
128130
def _save_file(
129131
self,
130132
filename: str,
133+
*,
131134
notebook: NotebookSerializationV1,
132135
# Whether or not to persist the app to the file system
133136
persist: bool,
@@ -180,6 +183,8 @@ def _save_file(
180183

181184
if persist:
182185
self._create_file(filename, contents)
186+
# Record the last saved content to avoid reloading our own writes
187+
self._last_saved_content = contents.strip()
183188

184189
if self._is_unnamed():
185190
self.rename(filename)
@@ -241,7 +246,7 @@ def rename(self, new_filename: str) -> None:
241246
if needs_save:
242247
self._save_file(
243248
self.filename,
244-
self.app.to_ir(),
249+
notebook=self.app.to_ir(),
245250
persist=True,
246251
previous_filename=previous_filename,
247252
)
@@ -286,7 +291,7 @@ def save_app_config(self, config: dict[str, Any]) -> str:
286291
if self.filename is not None:
287292
return self._save_file(
288293
self.filename,
289-
self.app.to_ir(),
294+
notebook=self.app.to_ir(),
290295
persist=True,
291296
)
292297
return ""
@@ -330,7 +335,7 @@ def save(self, request: SaveNotebookRequest) -> str:
330335
self.app.update_config({"layout_file": None})
331336
return self._save_file(
332337
filename,
333-
self.app.to_ir(),
338+
notebook=self.app.to_ir(),
334339
persist=request.persist,
335340
)
336341

@@ -360,6 +365,26 @@ def read_file(self) -> str:
360365
)
361366
return Path(self.filename).read_text(encoding="utf-8")
362367

368+
def file_content_matches_last_save(self) -> bool:
369+
"""Check if the current file content matches the last saved content.
370+
371+
This is used to avoid reloading the file when we detect our own writes.
372+
373+
Returns:
374+
bool: True if the file content matches the last save, False otherwise.
375+
"""
376+
if self.filename is None or self._last_saved_content is None:
377+
return False
378+
379+
try:
380+
current_content = Path(self.filename).read_text(encoding="utf-8")
381+
return current_content.strip() == self._last_saved_content
382+
except Exception as e:
383+
LOGGER.debug(
384+
f"Error reading file to check if content matches: {e}"
385+
)
386+
return False
387+
363388

364389
def read_css_file(css_file: str, filename: Optional[str]) -> Optional[str]:
365390
"""Read the contents of a CSS file.

marimo/_server/sessions.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,6 +1186,14 @@ def _handle_file_change(
11861186
) -> None:
11871187
LOGGER.debug(f"{file_path} was modified, handling {session}")
11881188

1189+
# Check if the file content matches the last save
1190+
# to avoid reloading our own writes
1191+
if session.app_file_manager.file_content_matches_last_save():
1192+
LOGGER.debug(
1193+
f"File {file_path} content matches last save, skipping reload"
1194+
)
1195+
return
1196+
11891197
# Reload the file manager to get the latest code
11901198
try:
11911199
changed_cell_ids = session.app_file_manager.reload()

tests/_server/test_file_manager.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -793,3 +793,63 @@ def cell2():
793793
deleted_cell_ids = set(original_cell_ids) - set(reloaded_cell_ids)
794794
assert len(deleted_cell_ids) == 2
795795
assert deleted_cell_ids.issubset(changed_cell_ids)
796+
797+
798+
def test_file_content_matches_last_save(tmp_path: Path) -> None:
799+
"""Test that file_content_matches_last_save correctly identifies own writes."""
800+
# Create a temporary file
801+
temp_file = tmp_path / "test_match_save.py"
802+
temp_file.write_text(
803+
"""
804+
import marimo
805+
app = marimo.App()
806+
807+
@app.cell
808+
def cell1():
809+
x = 1
810+
return x
811+
812+
if __name__ == "__main__":
813+
app.run()
814+
"""
815+
)
816+
817+
# Initialize AppFileManager
818+
manager = AppFileManager(filename=str(temp_file))
819+
820+
# Initially, no save has been made by the manager
821+
assert manager.file_content_matches_last_save() is False
822+
823+
# Save the file
824+
manager.save(
825+
SaveNotebookRequest(
826+
cell_ids=[CellId_t("1")],
827+
filename=str(temp_file),
828+
codes=["x = 1"],
829+
names=["cell1"],
830+
configs=[CellConfig()],
831+
persist=True,
832+
)
833+
)
834+
835+
# Now the file should match the last save
836+
assert manager.file_content_matches_last_save() is True
837+
838+
# Externally modify the file
839+
temp_file.write_text(
840+
"""
841+
import marimo
842+
app = marimo.App()
843+
844+
@app.cell
845+
def cell1():
846+
x = 2 # Changed
847+
return x
848+
849+
if __name__ == "__main__":
850+
app.run()
851+
"""
852+
)
853+
854+
# Now the file should not match the last save
855+
assert manager.file_content_matches_last_save() is False

0 commit comments

Comments
 (0)