diff --git a/marimo/_server/file_manager.py b/marimo/_server/file_manager.py index 2e94595859f..d7fc6f212b7 100644 --- a/marimo/_server/file_manager.py +++ b/marimo/_server/file_manager.py @@ -2,6 +2,7 @@ from __future__ import annotations import os +import pathlib from typing import Any, Dict, Optional from marimo import _loggers @@ -58,11 +59,18 @@ def _assert_path_is_the_same(self, filename: str) -> None: detail="Save handler cannot rename files.", ) + def _create_parent_directories(self, filename: str) -> None: + try: + pathlib.Path(filename).parent.mkdir(parents=True, exist_ok=True) + except Exception: + pass + def _create_file( self, filename: str, contents: str = "", ) -> None: + self._create_parent_directories(filename) try: with open(filename, "w", encoding="utf-8") as f: f.write(contents) @@ -74,6 +82,7 @@ def _create_file( def _rename_file(self, new_filename: str) -> None: assert self.filename is not None + self._create_parent_directories(new_filename) try: os.rename(self.filename, new_filename) except Exception as err: diff --git a/marimo/_server/files/os_file_system.py b/marimo/_server/files/os_file_system.py index 8bd8b377554..c88a53d77da 100644 --- a/marimo/_server/files/os_file_system.py +++ b/marimo/_server/files/os_file_system.py @@ -32,31 +32,34 @@ def get_root(self) -> str: def list_files(self, path: str) -> List[FileInfo]: files: List[FileInfo] = [] folders: List[FileInfo] = [] - with os.scandir(path) as it: - for entry in it: - if entry.name in IGNORE_LIST: - continue - try: - is_directory = entry.is_dir() - entry_stat = entry.stat() - except OSError: - # do not include files that fail to read - # (e.g. recursive/broken symlinks) - continue - - info = FileInfo( - id=entry.path, - path=entry.path, - name=entry.name, - is_directory=is_directory, - is_marimo_file=not is_directory - and self._is_marimo_file(entry.path), - last_modified=entry_stat.st_mtime, - ) - if is_directory: - folders.append(info) - else: - files.append(info) + try: + with os.scandir(path) as it: + for entry in it: + if entry.name in IGNORE_LIST: + continue + try: + is_directory = entry.is_dir() + entry_stat = entry.stat() + except OSError: + # do not include files that fail to read + # (e.g. recursive/broken symlinks) + continue + + info = FileInfo( + id=entry.path, + path=entry.path, + name=entry.name, + is_directory=is_directory, + is_marimo_file=not is_directory + and self._is_marimo_file(entry.path), + last_modified=entry_stat.st_mtime, + ) + if is_directory: + folders.append(info) + else: + files.append(info) + except OSError: + pass return sorted(folders, key=natural_sort_file) + sorted( files, key=natural_sort_file diff --git a/tests/_server/test_file_manager.py b/tests/_server/test_file_manager.py index 7dd0277cf78..839d26f7ecd 100644 --- a/tests/_server/test_file_manager.py +++ b/tests/_server/test_file_manager.py @@ -106,6 +106,24 @@ def test_rename_create_new_file(app_file_manager: AppFileManager) -> None: os.remove(new_filename) +def test_rename_create_new_directory_file( + app_file_manager: AppFileManager, +) -> None: + app_file_manager.filename = None + new_directory = "new_directory" + new_filename = os.path.join(new_directory, "new_file.py") + if os.path.exists(new_filename): + os.remove(new_filename) + if os.path.exists(new_directory): + os.rmdir(new_directory) + try: + app_file_manager.rename(new_filename) + assert os.path.exists(new_filename) + finally: + os.remove(new_filename) + os.rmdir(new_directory) + + def test_rename_different_filetype(app_file_manager: AppFileManager) -> None: initial_filename = app_file_manager.filename assert initial_filename