Loading src/nova/trame/model/data_selector.py +21 −14 Original line number Diff line number Diff line Loading @@ -20,8 +20,9 @@ class DataSelectorState(BaseModel, validate_assignment=True): class DataSelectorModel: """Manages file system interactions for the DataSelector widget.""" def __init__(self, directory: str, extensions: List[str], prefix: str) -> None: self.state = DataSelectorState() def __init__(self, state: DataSelectorState, directory: str, extensions: List[str], prefix: str) -> None: self.state: DataSelectorState = state self.state.directory = directory self.state.extensions = extensions self.state.prefix = prefix Loading @@ -37,15 +38,7 @@ class DataSelectorModel: return sorted_dirs def get_directories(self, base_path: Optional[Path] = None) -> List[Dict[str, Any]]: if base_path: pass else: base_path = Path(self.state.directory) if not base_path: return [] def get_directories_from_path(self, base_path: Path) -> List[Dict[str, Any]]: directories = [] try: for dirpath, dirs, _ in os.walk(base_path): Loading Loading @@ -78,10 +71,19 @@ class DataSelectorModel: return self.sort_directories(directories) def get_datafiles(self) -> List[str]: datafiles = [] def get_directories(self, base_path: Optional[Path] = None) -> List[Dict[str, Any]]: if base_path: pass else: base_path = Path(self.state.directory) if not base_path: return [] return self.get_directories_from_path(base_path) def get_datafiles_from_path(self, base_path: Path) -> List[str]: datafiles = [] try: if self.state.prefix: datafile_path = base_path / self.state.prefix Loading @@ -101,5 +103,10 @@ class DataSelectorModel: return natsorted(datafiles) def get_datafiles(self) -> List[str]: base_path = Path(self.state.directory) return self.get_datafiles_from_path(base_path) def set_subdirectory(self, subdirectory_path: str) -> None: self.state.subdirectory = subdirectory_path src/nova/trame/model/ornl/neutron_data_selector.py +17 −73 Original line number Diff line number Diff line Loading @@ -6,9 +6,11 @@ from typing import Any, Dict, List, Optional from warnings import warn from natsort import natsorted from pydantic import BaseModel, Field, field_validator, model_validator from pydantic import Field, field_validator, model_validator from typing_extensions import Self from ..data_selector import DataSelectorModel, DataSelectorState CUSTOM_DIRECTORIES_LABEL = "Custom Directory" INSTRUMENTS = { Loading Loading @@ -57,7 +59,7 @@ INSTRUMENTS = { } class NeutronDataSelectorState(BaseModel, validate_assignment=True): class NeutronDataSelectorState(DataSelectorState): """Selection state for identifying datafiles.""" allow_custom_directories: bool = Field(default=False) Loading @@ -65,9 +67,6 @@ class NeutronDataSelectorState(BaseModel, validate_assignment=True): instrument: str = Field(default="", title="Instrument") experiment: str = Field(default="", title="Experiment") custom_directory: str = Field(default="", title="Custom Directory") directory: str = Field(default="") extensions: List[str] = Field(default=[]) prefix: str = Field(default="") @field_validator("experiment", mode="after") @classmethod Loading Loading @@ -105,13 +104,21 @@ class NeutronDataSelectorState(BaseModel, validate_assignment=True): return list(INSTRUMENTS.get(self.facility, {}).keys()) class NeutronDataSelectorModel: class NeutronDataSelectorModel(DataSelectorModel): """Manages file system interactions for the DataSelector widget.""" def __init__( self, facility: str, instrument: str, extensions: List[str], prefix: str, allow_custom_directories: bool self, state: NeutronDataSelectorState, facility: str, instrument: str, extensions: List[str], prefix: str, allow_custom_directories: bool, ) -> None: self.state = NeutronDataSelectorState() super().__init__(state, "", extensions, prefix) self.state: NeutronDataSelectorState = state self.state.facility = facility self.state.instrument = instrument self.state.extensions = extensions Loading Loading @@ -140,17 +147,6 @@ class NeutronDataSelectorModel: return natsorted(experiments) def sort_directories(self, directories: List[Dict[str, Any]]) -> List[Dict[str, Any]]: # Sort the current level of dictionaries sorted_dirs = natsorted(directories, key=lambda x: x["title"]) # Process each sorted item to sort their children for item in sorted_dirs: if "children" in item and isinstance(item["children"], list): item["children"] = self.sort_directories(item["children"]) return sorted_dirs def get_experiment_directory_path(self) -> Optional[Path]: if not self.state.experiment: return None Loading @@ -176,41 +172,9 @@ class NeutronDataSelectorModel: if not base_path: return [] directories = [] try: for dirpath, dirs, _ in os.walk(base_path): # Get the relative path from the start path path_parts = os.path.relpath(dirpath, base_path).split(os.sep) if len(path_parts) > 1: dirs.clear() # Only create a new entry for top-level directories if len(path_parts) == 1 and path_parts[0] != ".": # This indicates a top-level directory current_dir = {"path": dirpath, "title": path_parts[0]} directories.append(current_dir) # Add subdirectories to the corresponding parent directory elif len(path_parts) > 1: current_level: Any = directories for part in path_parts[:-1]: # Parent directories for item in current_level: if item["title"] == part: if "children" not in item: item["children"] = [] current_level = item["children"] break # Add the last part (current directory) as a child current_level.append({"path": dirpath, "title": path_parts[-1]}) except OSError: pass return self.sort_directories(directories) return self.get_directories_from_path(base_path) def get_datafiles(self) -> List[str]: datafiles = [] if self.state.experiment: base_path = Path("/") / self.state.facility / self.get_instrument_dir() / self.state.experiment elif self.state.custom_directory: Loading @@ -218,27 +182,7 @@ class NeutronDataSelectorModel: else: return [] try: if self.state.prefix: datafile_path = str(base_path / self.state.prefix) else: datafile_path = str(base_path / self.state.directory) for entry in os.scandir(datafile_path): if entry.is_file(): if self.state.extensions: for extension in self.state.extensions: if entry.path.lower().endswith(extension): datafiles.append(entry.path) else: datafiles.append(entry.path) except OSError: pass return natsorted(datafiles) def set_directory(self, directory_path: str) -> None: self.state.directory = directory_path return self.get_datafiles_from_path(base_path) def set_state(self, facility: Optional[str], instrument: Optional[str], experiment: Optional[str]) -> None: if facility is not None: Loading src/nova/trame/view/components/data_selector.py +3 −2 Original line number Diff line number Diff line Loading @@ -7,7 +7,7 @@ from trame.widgets import client, datagrid, html from trame.widgets import vuetify3 as vuetify from nova.mvvm.trame_binding import TrameBinding from nova.trame.model.data_selector import DataSelectorModel from nova.trame.model.data_selector import DataSelectorModel, DataSelectorState from nova.trame.view.layouts import GridLayout, VBoxLayout from nova.trame.view_model.data_selector import DataSelectorViewModel Loading Loading @@ -163,7 +163,8 @@ class DataSelector(datagrid.VGrid): ) def create_model(self, directory: str) -> None: self._model = DataSelectorModel(directory, self._extensions, self._prefix) state = DataSelectorState() self._model = DataSelectorModel(state, directory, self._extensions, self._prefix) def create_viewmodel(self) -> None: server = get_server(None, client_type="vue3") Loading src/nova/trame/view/components/ornl/neutron_data_selector.py +8 −3 Original line number Diff line number Diff line Loading @@ -8,7 +8,11 @@ from trame.widgets import client, datagrid, html from trame.widgets import vuetify3 as vuetify from nova.mvvm.trame_binding import TrameBinding from nova.trame.model.ornl.neutron_data_selector import CUSTOM_DIRECTORIES_LABEL, NeutronDataSelectorModel from nova.trame.model.ornl.neutron_data_selector import ( CUSTOM_DIRECTORIES_LABEL, NeutronDataSelectorModel, NeutronDataSelectorState, ) from nova.trame.view.layouts import GridLayout, VBoxLayout from nova.trame.view_model.ornl.neutron_data_selector import NeutronDataSelectorViewModel Loading Loading @@ -137,7 +141,7 @@ class NeutronDataSelector(datagrid.VGrid): item_value="path", items=(self._directories_name,), click_open=(self._vm.expand_directory, "[$event.path]"), update_activated=(self._vm.set_directory, "$event"), update_activated=(self._vm.set_subdirectory, "$event"), ) vuetify.VListItem("No directories found", classes="flex-0-1 text-center", v_else=True) Loading Loading @@ -199,8 +203,9 @@ class NeutronDataSelector(datagrid.VGrid): ) def create_model(self, facility: str, instrument: str) -> None: state = NeutronDataSelectorState() self._model = NeutronDataSelectorModel( facility, instrument, self._extensions, self._prefix, self._allow_custom_directories state, facility, instrument, self._extensions, self._prefix, self._allow_custom_directories ) def create_viewmodel(self) -> None: Loading src/nova/trame/view_model/ornl/neutron_data_selector.py +3 −3 Original line number Diff line number Diff line Loading @@ -50,8 +50,8 @@ class NeutronDataSelectorViewModel: self.expanded.append(paths[-1]) self.directories_bind.update_in_view(self.directories) def set_directory(self, directory_path: str = "") -> None: self.model.set_directory(directory_path) def set_subdirectory(self, subdirectory_path: str = "") -> None: self.model.set_subdirectory(subdirectory_path) self.update_view() def set_state(self, facility: Optional[str], instrument: Optional[str], experiment: Optional[str]) -> None: Loading @@ -59,7 +59,7 @@ class NeutronDataSelectorViewModel: self.update_view() def reset(self) -> None: self.model.set_directory("") self.model.set_subdirectory("") self.directories = self.model.get_directories() self.expanded = [] self.reset_bind.update_in_view(None) Loading Loading
src/nova/trame/model/data_selector.py +21 −14 Original line number Diff line number Diff line Loading @@ -20,8 +20,9 @@ class DataSelectorState(BaseModel, validate_assignment=True): class DataSelectorModel: """Manages file system interactions for the DataSelector widget.""" def __init__(self, directory: str, extensions: List[str], prefix: str) -> None: self.state = DataSelectorState() def __init__(self, state: DataSelectorState, directory: str, extensions: List[str], prefix: str) -> None: self.state: DataSelectorState = state self.state.directory = directory self.state.extensions = extensions self.state.prefix = prefix Loading @@ -37,15 +38,7 @@ class DataSelectorModel: return sorted_dirs def get_directories(self, base_path: Optional[Path] = None) -> List[Dict[str, Any]]: if base_path: pass else: base_path = Path(self.state.directory) if not base_path: return [] def get_directories_from_path(self, base_path: Path) -> List[Dict[str, Any]]: directories = [] try: for dirpath, dirs, _ in os.walk(base_path): Loading Loading @@ -78,10 +71,19 @@ class DataSelectorModel: return self.sort_directories(directories) def get_datafiles(self) -> List[str]: datafiles = [] def get_directories(self, base_path: Optional[Path] = None) -> List[Dict[str, Any]]: if base_path: pass else: base_path = Path(self.state.directory) if not base_path: return [] return self.get_directories_from_path(base_path) def get_datafiles_from_path(self, base_path: Path) -> List[str]: datafiles = [] try: if self.state.prefix: datafile_path = base_path / self.state.prefix Loading @@ -101,5 +103,10 @@ class DataSelectorModel: return natsorted(datafiles) def get_datafiles(self) -> List[str]: base_path = Path(self.state.directory) return self.get_datafiles_from_path(base_path) def set_subdirectory(self, subdirectory_path: str) -> None: self.state.subdirectory = subdirectory_path
src/nova/trame/model/ornl/neutron_data_selector.py +17 −73 Original line number Diff line number Diff line Loading @@ -6,9 +6,11 @@ from typing import Any, Dict, List, Optional from warnings import warn from natsort import natsorted from pydantic import BaseModel, Field, field_validator, model_validator from pydantic import Field, field_validator, model_validator from typing_extensions import Self from ..data_selector import DataSelectorModel, DataSelectorState CUSTOM_DIRECTORIES_LABEL = "Custom Directory" INSTRUMENTS = { Loading Loading @@ -57,7 +59,7 @@ INSTRUMENTS = { } class NeutronDataSelectorState(BaseModel, validate_assignment=True): class NeutronDataSelectorState(DataSelectorState): """Selection state for identifying datafiles.""" allow_custom_directories: bool = Field(default=False) Loading @@ -65,9 +67,6 @@ class NeutronDataSelectorState(BaseModel, validate_assignment=True): instrument: str = Field(default="", title="Instrument") experiment: str = Field(default="", title="Experiment") custom_directory: str = Field(default="", title="Custom Directory") directory: str = Field(default="") extensions: List[str] = Field(default=[]) prefix: str = Field(default="") @field_validator("experiment", mode="after") @classmethod Loading Loading @@ -105,13 +104,21 @@ class NeutronDataSelectorState(BaseModel, validate_assignment=True): return list(INSTRUMENTS.get(self.facility, {}).keys()) class NeutronDataSelectorModel: class NeutronDataSelectorModel(DataSelectorModel): """Manages file system interactions for the DataSelector widget.""" def __init__( self, facility: str, instrument: str, extensions: List[str], prefix: str, allow_custom_directories: bool self, state: NeutronDataSelectorState, facility: str, instrument: str, extensions: List[str], prefix: str, allow_custom_directories: bool, ) -> None: self.state = NeutronDataSelectorState() super().__init__(state, "", extensions, prefix) self.state: NeutronDataSelectorState = state self.state.facility = facility self.state.instrument = instrument self.state.extensions = extensions Loading Loading @@ -140,17 +147,6 @@ class NeutronDataSelectorModel: return natsorted(experiments) def sort_directories(self, directories: List[Dict[str, Any]]) -> List[Dict[str, Any]]: # Sort the current level of dictionaries sorted_dirs = natsorted(directories, key=lambda x: x["title"]) # Process each sorted item to sort their children for item in sorted_dirs: if "children" in item and isinstance(item["children"], list): item["children"] = self.sort_directories(item["children"]) return sorted_dirs def get_experiment_directory_path(self) -> Optional[Path]: if not self.state.experiment: return None Loading @@ -176,41 +172,9 @@ class NeutronDataSelectorModel: if not base_path: return [] directories = [] try: for dirpath, dirs, _ in os.walk(base_path): # Get the relative path from the start path path_parts = os.path.relpath(dirpath, base_path).split(os.sep) if len(path_parts) > 1: dirs.clear() # Only create a new entry for top-level directories if len(path_parts) == 1 and path_parts[0] != ".": # This indicates a top-level directory current_dir = {"path": dirpath, "title": path_parts[0]} directories.append(current_dir) # Add subdirectories to the corresponding parent directory elif len(path_parts) > 1: current_level: Any = directories for part in path_parts[:-1]: # Parent directories for item in current_level: if item["title"] == part: if "children" not in item: item["children"] = [] current_level = item["children"] break # Add the last part (current directory) as a child current_level.append({"path": dirpath, "title": path_parts[-1]}) except OSError: pass return self.sort_directories(directories) return self.get_directories_from_path(base_path) def get_datafiles(self) -> List[str]: datafiles = [] if self.state.experiment: base_path = Path("/") / self.state.facility / self.get_instrument_dir() / self.state.experiment elif self.state.custom_directory: Loading @@ -218,27 +182,7 @@ class NeutronDataSelectorModel: else: return [] try: if self.state.prefix: datafile_path = str(base_path / self.state.prefix) else: datafile_path = str(base_path / self.state.directory) for entry in os.scandir(datafile_path): if entry.is_file(): if self.state.extensions: for extension in self.state.extensions: if entry.path.lower().endswith(extension): datafiles.append(entry.path) else: datafiles.append(entry.path) except OSError: pass return natsorted(datafiles) def set_directory(self, directory_path: str) -> None: self.state.directory = directory_path return self.get_datafiles_from_path(base_path) def set_state(self, facility: Optional[str], instrument: Optional[str], experiment: Optional[str]) -> None: if facility is not None: Loading
src/nova/trame/view/components/data_selector.py +3 −2 Original line number Diff line number Diff line Loading @@ -7,7 +7,7 @@ from trame.widgets import client, datagrid, html from trame.widgets import vuetify3 as vuetify from nova.mvvm.trame_binding import TrameBinding from nova.trame.model.data_selector import DataSelectorModel from nova.trame.model.data_selector import DataSelectorModel, DataSelectorState from nova.trame.view.layouts import GridLayout, VBoxLayout from nova.trame.view_model.data_selector import DataSelectorViewModel Loading Loading @@ -163,7 +163,8 @@ class DataSelector(datagrid.VGrid): ) def create_model(self, directory: str) -> None: self._model = DataSelectorModel(directory, self._extensions, self._prefix) state = DataSelectorState() self._model = DataSelectorModel(state, directory, self._extensions, self._prefix) def create_viewmodel(self) -> None: server = get_server(None, client_type="vue3") Loading
src/nova/trame/view/components/ornl/neutron_data_selector.py +8 −3 Original line number Diff line number Diff line Loading @@ -8,7 +8,11 @@ from trame.widgets import client, datagrid, html from trame.widgets import vuetify3 as vuetify from nova.mvvm.trame_binding import TrameBinding from nova.trame.model.ornl.neutron_data_selector import CUSTOM_DIRECTORIES_LABEL, NeutronDataSelectorModel from nova.trame.model.ornl.neutron_data_selector import ( CUSTOM_DIRECTORIES_LABEL, NeutronDataSelectorModel, NeutronDataSelectorState, ) from nova.trame.view.layouts import GridLayout, VBoxLayout from nova.trame.view_model.ornl.neutron_data_selector import NeutronDataSelectorViewModel Loading Loading @@ -137,7 +141,7 @@ class NeutronDataSelector(datagrid.VGrid): item_value="path", items=(self._directories_name,), click_open=(self._vm.expand_directory, "[$event.path]"), update_activated=(self._vm.set_directory, "$event"), update_activated=(self._vm.set_subdirectory, "$event"), ) vuetify.VListItem("No directories found", classes="flex-0-1 text-center", v_else=True) Loading Loading @@ -199,8 +203,9 @@ class NeutronDataSelector(datagrid.VGrid): ) def create_model(self, facility: str, instrument: str) -> None: state = NeutronDataSelectorState() self._model = NeutronDataSelectorModel( facility, instrument, self._extensions, self._prefix, self._allow_custom_directories state, facility, instrument, self._extensions, self._prefix, self._allow_custom_directories ) def create_viewmodel(self) -> None: Loading
src/nova/trame/view_model/ornl/neutron_data_selector.py +3 −3 Original line number Diff line number Diff line Loading @@ -50,8 +50,8 @@ class NeutronDataSelectorViewModel: self.expanded.append(paths[-1]) self.directories_bind.update_in_view(self.directories) def set_directory(self, directory_path: str = "") -> None: self.model.set_directory(directory_path) def set_subdirectory(self, subdirectory_path: str = "") -> None: self.model.set_subdirectory(subdirectory_path) self.update_view() def set_state(self, facility: Optional[str], instrument: Optional[str], experiment: Optional[str]) -> None: Loading @@ -59,7 +59,7 @@ class NeutronDataSelectorViewModel: self.update_view() def reset(self) -> None: self.model.set_directory("") self.model.set_subdirectory("") self.directories = self.model.get_directories() self.expanded = [] self.reset_bind.update_in_view(None) Loading