nova-trame 0.20.0__py3-none-any.whl → 0.20.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -54,23 +54,17 @@ INSTRUMENTS = {
54
54
  }
55
55
 
56
56
 
57
- def get_facilities() -> List[str]:
58
- return list(INSTRUMENTS.keys())
59
-
60
-
61
- def get_instruments(facility: str) -> List[str]:
62
- return list(INSTRUMENTS.get(facility, {}).keys())
63
-
64
-
65
57
  class DataSelectorState(BaseModel, validate_assignment=True):
66
58
  """Selection state for identifying datafiles."""
67
59
 
68
60
  facility: str = Field(default="", title="Facility")
69
61
  instrument: str = Field(default="", title="Instrument")
70
62
  experiment: str = Field(default="", title="Experiment")
63
+ user_directory: str = Field(default="", title="User Directory")
71
64
  directory: str = Field(default="")
72
65
  extensions: List[str] = Field(default=[])
73
66
  prefix: str = Field(default="")
67
+ show_user_directories: bool = Field(default=False)
74
68
 
75
69
  @field_validator("experiment", mode="after")
76
70
  @classmethod
@@ -81,11 +75,11 @@ class DataSelectorState(BaseModel, validate_assignment=True):
81
75
 
82
76
  @model_validator(mode="after")
83
77
  def validate_state(self) -> Self:
84
- valid_facilities = get_facilities()
78
+ valid_facilities = self.get_facilities()
85
79
  if self.facility and self.facility not in valid_facilities:
86
80
  warn(f"Facility '{self.facility}' could not be found. Valid options: {valid_facilities}", stacklevel=1)
87
81
 
88
- valid_instruments = get_instruments(self.facility)
82
+ valid_instruments = self.get_instruments()
89
83
  if self.instrument and self.instrument not in valid_instruments:
90
84
  warn(
91
85
  (
@@ -98,25 +92,37 @@ class DataSelectorState(BaseModel, validate_assignment=True):
98
92
 
99
93
  return self
100
94
 
95
+ def get_facilities(self) -> List[str]:
96
+ facilities = list(INSTRUMENTS.keys())
97
+ if self.show_user_directories:
98
+ facilities.append("User Directory")
99
+ return facilities
100
+
101
+ def get_instruments(self) -> List[str]:
102
+ return list(INSTRUMENTS.get(self.facility, {}).keys())
103
+
101
104
 
102
105
  class DataSelectorModel:
103
106
  """Manages file system interactions for the DataSelector widget."""
104
107
 
105
- def __init__(self, facility: str, instrument: str, extensions: List[str], prefix: str) -> None:
108
+ def __init__(
109
+ self, facility: str, instrument: str, extensions: List[str], prefix: str, show_user_directories: bool
110
+ ) -> None:
106
111
  self.state = DataSelectorState()
107
112
  self.state.facility = facility
108
113
  self.state.instrument = instrument
109
114
  self.state.extensions = extensions
110
115
  self.state.prefix = prefix
116
+ self.state.show_user_directories = show_user_directories
111
117
 
112
118
  def get_facilities(self) -> List[str]:
113
- return sorted(get_facilities())
119
+ return sorted(self.state.get_facilities())
114
120
 
115
121
  def get_instrument_dir(self) -> str:
116
122
  return INSTRUMENTS.get(self.state.facility, {}).get(self.state.instrument, "")
117
123
 
118
124
  def get_instruments(self) -> List[str]:
119
- return sorted(get_instruments(self.state.facility))
125
+ return sorted(self.state.get_instruments())
120
126
 
121
127
  def get_experiments(self) -> List[str]:
122
128
  experiments = []
@@ -142,17 +148,32 @@ class DataSelectorModel:
142
148
 
143
149
  return sorted_dirs
144
150
 
145
- def get_directories(self) -> List[Any]:
151
+ def get_experiment_directory_path(self) -> Optional[Path]:
146
152
  if not self.state.experiment:
153
+ return None
154
+
155
+ return Path("/") / self.state.facility / self.get_instrument_dir() / self.state.experiment
156
+
157
+ def get_user_directory_path(self) -> Optional[Path]:
158
+ if not self.state.user_directory:
159
+ return None
160
+
161
+ return Path("/SNS/users") / self.state.user_directory
162
+
163
+ def get_directories(self) -> List[str]:
164
+ if self.state.facility == "User Directory":
165
+ base_path = self.get_user_directory_path()
166
+ else:
167
+ base_path = self.get_experiment_directory_path()
168
+
169
+ if not base_path:
147
170
  return []
148
171
 
149
172
  directories = []
150
-
151
- experiment_path = Path("/") / self.state.facility / self.get_instrument_dir() / self.state.experiment
152
173
  try:
153
- for dirpath, _, _ in os.walk(experiment_path):
174
+ for dirpath, _, _ in os.walk(base_path):
154
175
  # Get the relative path from the start path
155
- path_parts = os.path.relpath(dirpath, experiment_path).split(os.sep)
176
+ path_parts = os.path.relpath(dirpath, base_path).split(os.sep)
156
177
 
157
178
  # Only create a new entry for top-level directories
158
179
  if len(path_parts) == 1 and path_parts[0] != ".": # This indicates a top-level directory
@@ -1,6 +1,17 @@
1
1
  from .data_selector import DataSelector
2
+ from .execution_buttons import ExecutionButtons
2
3
  from .file_upload import FileUpload
3
4
  from .input_field import InputField
5
+ from .progress_bar import ProgressBar
4
6
  from .remote_file_input import RemoteFileInput
7
+ from .tool_outputs import ToolOutputWindows
5
8
 
6
- __all__ = ["DataSelector", "FileUpload", "InputField", "RemoteFileInput"]
9
+ __all__ = [
10
+ "DataSelector",
11
+ "ExecutionButtons",
12
+ "FileUpload",
13
+ "InputField",
14
+ "ProgressBar",
15
+ "RemoteFileInput",
16
+ "ToolOutputWindows",
17
+ ]
@@ -1,6 +1,7 @@
1
1
  """View Implementation for DataSelector."""
2
2
 
3
3
  from typing import Any, List, Optional, cast
4
+ from warnings import warn
4
5
 
5
6
  from trame.app import get_server
6
7
  from trame.widgets import client, html
@@ -27,6 +28,7 @@ class DataSelector(vuetify.VDataTableVirtual):
27
28
  extensions: Optional[List[str]] = None,
28
29
  prefix: str = "",
29
30
  select_strategy: str = "all",
31
+ show_user_directories: bool = False,
30
32
  **kwargs: Any,
31
33
  ) -> None:
32
34
  """Constructor for DataSelector.
@@ -48,6 +50,9 @@ class DataSelector(vuetify.VDataTableVirtual):
48
50
  select_strategy : str, optional
49
51
  The selection strategy to pass to the `VDataTable component <https://trame.readthedocs.io/en/latest/trame.widgets.vuetify3.html#trame.widgets.vuetify3.VDataTable>`__.
50
52
  If unset, the `all` strategy will be used.
53
+ show_user_directories : bool, optional
54
+ Whether or not to allow users to select data files from user directories. Ignored if the facility parameter
55
+ is set.
51
56
  **kwargs
52
57
  All other arguments will be passed to the underlying
53
58
  `VDataTable component <https://trame.readthedocs.io/en/latest/trame.widgets.vuetify3.html#trame.widgets.vuetify3.VDataTable>`_.
@@ -64,10 +69,15 @@ class DataSelector(vuetify.VDataTableVirtual):
64
69
  else:
65
70
  self._label = None
66
71
 
72
+ if facility and show_user_directories:
73
+ warn("show_user_directories will be ignored since the facility parameter is set.", stacklevel=1)
74
+
67
75
  self._v_model = v_model
76
+ self._v_model_name_in_state = v_model.split(".")[0]
68
77
  self._extensions = extensions if extensions is not None else []
69
78
  self._prefix = prefix
70
79
  self._select_strategy = select_strategy
80
+ self._show_user_directories = show_user_directories
71
81
 
72
82
  self._state_name = f"nova__dataselector_{self._next_id}_state"
73
83
  self._facilities_name = f"nova__dataselector_{self._next_id}_facilities"
@@ -76,7 +86,7 @@ class DataSelector(vuetify.VDataTableVirtual):
76
86
  self._directories_name = f"nova__dataselector_{self._next_id}_directories"
77
87
  self._datafiles_name = f"nova__dataselector_{self._next_id}_datafiles"
78
88
 
79
- self._flush_state = f"flushState('{self._v_model.split('.')[0]}');"
89
+ self._flush_state = f"flushState('{self._v_model_name_in_state}');"
80
90
  self._reset_state = client.JSEval(exec=f"{self._v_model} = []; {self._flush_state}").exec
81
91
 
82
92
  self.create_model(facility, instrument)
@@ -96,14 +106,19 @@ class DataSelector(vuetify.VDataTableVirtual):
96
106
  if instrument == "":
97
107
  columns -= 1
98
108
  InputField(
99
- v_model=f"{self._state_name}.instrument", items=(self._instruments_name,), type="autocomplete"
109
+ v_if=f"{self._state_name}.facility !== 'User Directory'",
110
+ v_model=f"{self._state_name}.instrument",
111
+ items=(self._instruments_name,),
112
+ type="autocomplete",
100
113
  )
101
114
  InputField(
115
+ v_if=f"{self._state_name}.facility !== 'User Directory'",
102
116
  v_model=f"{self._state_name}.experiment",
103
117
  column_span=columns,
104
118
  items=(self._experiments_name,),
105
119
  type="autocomplete",
106
120
  )
121
+ InputField(v_else=True, v_model=f"{self._state_name}.user_directory", column_span=2)
107
122
 
108
123
  with GridLayout(columns=2, classes="flex-1-0 h-0", valign="start"):
109
124
  if not self._prefix:
@@ -155,7 +170,9 @@ class DataSelector(vuetify.VDataTableVirtual):
155
170
  )
156
171
 
157
172
  def create_model(self, facility: str, instrument: str) -> None:
158
- self._model = DataSelectorModel(facility, instrument, self._extensions, self._prefix)
173
+ self._model = DataSelectorModel(
174
+ facility, instrument, self._extensions, self._prefix, self._show_user_directories
175
+ )
159
176
 
160
177
  def create_viewmodel(self) -> None:
161
178
  server = get_server(None, client_type="vue3")
@@ -10,7 +10,10 @@ from nova.trame.view_model.execution_buttons import ExecutionButtonsViewModel
10
10
 
11
11
 
12
12
  class ExecutionButtons:
13
- """Execution buttons class. Adds Run/Stop/Cancel/Download buttons to the view."""
13
+ """Execution buttons class. Adds Run/Stop/Cancel/Download buttons to the view.
14
+
15
+ This is intended to be used with the `nova-galaxy ToolRunner <https://nova-application-development.readthedocs.io/projects/nova-galaxy/en/latest/core_concepts/tool_runner.html>`__.
16
+ """
14
17
 
15
18
  def __init__(self, id: str, stop_btn: bool = False, download_btn: bool = False) -> None:
16
19
  """Constructor for ExecutionButtons.
@@ -18,7 +21,7 @@ class ExecutionButtons:
18
21
  Parameters
19
22
  ----------
20
23
  id : str
21
- Component id. Should be used consistently with ToolRunner and other components
24
+ Component id. Should be used consistently with ToolRunner and other components.
22
25
  stop_btn: bool
23
26
  Display stop button.
24
27
  download_btn : bool
@@ -9,7 +9,10 @@ from nova.trame.view_model.progress_bar import ProgressBarViewModel
9
9
 
10
10
 
11
11
  class ProgressBar:
12
- """Progress bar class. Adds progress bar that displays job status to the view."""
12
+ """Progress bar class. Adds progress bar that displays job status to the view.
13
+
14
+ This is intended to be used with the `nova-galaxy ToolRunner <https://nova-application-development.readthedocs.io/projects/nova-galaxy/en/latest/core_concepts/tool_runner.html>`__.
15
+ """
13
16
 
14
17
  def __init__(self, id: str) -> None:
15
18
  """Constructor for ProgressBar.
@@ -10,7 +10,10 @@ from nova.trame.view_model.tool_outputs import ToolOutputsViewModel
10
10
 
11
11
 
12
12
  class ToolOutputWindows:
13
- """Tool outputs class. Displays windows with tool stdout/stderr."""
13
+ """Tool outputs class. Displays windows with tool stdout/stderr.
14
+
15
+ This is intended to be used with the `nova-galaxy ToolRunner <https://nova-application-development.readthedocs.io/projects/nova-galaxy/en/latest/core_concepts/tool_runner.html>`__.
16
+ """
14
17
 
15
18
  def __init__(self, id: str) -> None:
16
19
  """Constructor for ToolOutputWindows.
@@ -3,7 +3,7 @@
3
3
  import json
4
4
  import os
5
5
  import socketserver
6
- from asyncio import FIRST_COMPLETED, new_event_loop, set_event_loop, wait
6
+ from asyncio import FIRST_COMPLETED, create_task, new_event_loop, set_event_loop, wait
7
7
  from io import BytesIO
8
8
  from mimetypes import types_map
9
9
  from pathlib import Path
@@ -188,9 +188,9 @@ class MatplotlibFigure(matplotlib.Figure):
188
188
  raise ValueError("unexpected message type: %s", print(msg))
189
189
 
190
190
  # Forward websocket data in both directions
191
- await wait(
192
- [ws_forward(ws_server, ws_client), ws_forward(ws_client, ws_server)], return_when=FIRST_COMPLETED
193
- )
191
+ server_to_client = create_task(ws_forward(ws_server, ws_client))
192
+ client_to_server = create_task(ws_forward(ws_client, ws_server))
193
+ await wait([server_to_client, client_to_server], return_when=FIRST_COMPLETED)
194
194
  await client_session.close() # Ensure the connection is cleaned up when the Trame client disconnects.
195
195
 
196
196
  return ws_server
@@ -29,17 +29,23 @@ class DataSelectorViewModel:
29
29
  self.model.set_state(facility, instrument, experiment)
30
30
  self.update_view()
31
31
 
32
+ def reset(self) -> None:
33
+ self.model.set_directory("")
34
+ self.reset_bind.update_in_view(None)
35
+
32
36
  def on_state_updated(self, results: Dict[str, Any]) -> None:
33
37
  for update in results.get("updated", []):
34
38
  match update:
35
39
  case "facility":
36
40
  self.model.set_state(facility=None, instrument="", experiment="")
37
- self.model.set_directory("")
38
- self.reset_bind.update_in_view(None)
41
+ self.reset()
39
42
  case "instrument":
40
43
  self.model.set_state(facility=None, instrument=None, experiment="")
41
- self.model.set_directory("")
42
- self.reset_bind.update_in_view(None)
44
+ self.reset()
45
+ case "experiment":
46
+ self.reset()
47
+ case "user_directory":
48
+ self.reset()
43
49
  self.update_view()
44
50
 
45
51
  def update_view(self) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: nova-trame
3
- Version: 0.20.0
3
+ Version: 0.20.2
4
4
  Summary: A Python Package for injecting curated themes and custom components into Trame applications
5
5
  License: MIT
6
6
  Keywords: NDIP,Python,Trame,Vuetify
@@ -1,18 +1,18 @@
1
1
  nova/__init__.py,sha256=ED6jHcYiuYpr_0vjGz0zx2lrrmJT9sDJCzIljoDfmlM,65
2
2
  nova/trame/__init__.py,sha256=gFrAg1qva5PIqR5TjvPzAxLx103IKipJLqp3XXvrQL8,59
3
- nova/trame/model/data_selector.py,sha256=sCLU0YIMCccC1BH8dKZPmajaft6WgwuM_zOr3NPk2bg,7552
3
+ nova/trame/model/data_selector.py,sha256=bKWMk9bVhC16DTuEI7o656idMxzLbUi7RBdiaGO6TZY,8377
4
4
  nova/trame/model/remote_file_input.py,sha256=9KAf31ZHzpsh_aXUrNcF81Q5jvUZDWCzW1QATKls-Jk,3675
5
- nova/trame/view/components/__init__.py,sha256=u8yzshFp_TmuC1g9TRxKjy_BdGWMIzPQouI52hzcr2U,234
6
- nova/trame/view/components/data_selector.py,sha256=UFQriSH25wk3F4s6EnP5Dfrt3MFbisJTrzmRxH0ME8U,8831
7
- nova/trame/view/components/execution_buttons.py,sha256=wXpgF6osuAivKKnby8yCevoFa4PbRlCtUgEymjiugzE,3857
5
+ nova/trame/view/components/__init__.py,sha256=60BeS69aOrFnkptjuD17rfPE1f4Z35iBH56TRmW5MW8,451
6
+ nova/trame/view/components/data_selector.py,sha256=IQipVqf6fG7qVRa4CJSr4jGz7Sru_lYU6K-qczJdstg,9727
7
+ nova/trame/view/components/execution_buttons.py,sha256=fIkrWKI3jFZqk3GHhtmYh3nK2c-HOXpD3D3zd_TUpi0,4049
8
8
  nova/trame/view/components/file_upload.py,sha256=7VcpfA6zmiqMDLkwVPlb35Tf0IUTBN1xsHpoUFnSr1w,3111
9
9
  nova/trame/view/components/input_field.py,sha256=q6WQ_N-BOlimUL9zgazDlsDfK28FrrKjH4he8e_HzRA,16088
10
- nova/trame/view/components/progress_bar.py,sha256=vva_blYZwF1xJt72sCBSbJB5HloyTy39UoVOP_ixs_E,1995
10
+ nova/trame/view/components/progress_bar.py,sha256=Sh5cOPaMWrFq8KTWEDui1dIbK53BPtGG2RZOSKEaoJ4,2186
11
11
  nova/trame/view/components/remote_file_input.py,sha256=ByrBFj8svyWezcardCWrS_4Ag3fgTYNg_11lDW1FIA8,9669
12
- nova/trame/view/components/tool_outputs.py,sha256=IlpvJJuVZ6o9WMXW1Hpsvca7Xw1AxO83fdr_JFcYnbA,2146
12
+ nova/trame/view/components/tool_outputs.py,sha256=-6pDURd2l_FK_8EWa9BI3KhU_KJXJ6uyJ_rW4nQVc08,2337
13
13
  nova/trame/view/components/visualization/__init__.py,sha256=reqkkbhD5uSksHHlhVMy1qNUCwSekS5HlXk6wCREYxU,152
14
14
  nova/trame/view/components/visualization/interactive_2d_plot.py,sha256=foZCMoqbuahT5dtqIQvm8C4ZJcY9P211eJEcpQJltmM,3421
15
- nova/trame/view/components/visualization/matplotlib_figure.py,sha256=0iWCXB8i7Tut1gA66hY9cGrhZPaHC7p-XdADDNy_UVY,12042
15
+ nova/trame/view/components/visualization/matplotlib_figure.py,sha256=GGH2cx-dQFkMAOTnlCrzMGDb2TN451I9J3gAS8tx2cs,12147
16
16
  nova/trame/view/layouts/__init__.py,sha256=cMrlB5YMUoK8EGB83b34UU0kPTVrH8AxsYvKRtpUNEc,141
17
17
  nova/trame/view/layouts/grid.py,sha256=BYoylq-VN1l55BXBWMJ_7zvHcQYmfOo811nzD72IBOQ,5522
18
18
  nova/trame/view/layouts/hbox.py,sha256=qlOMp_iOropIkC9Jxa6D89b7OPv0pNvJ73tUEzddyhQ,3513
@@ -27,13 +27,13 @@ nova/trame/view/theme/assets/js/lodash.throttle.min.js,sha256=9csqjX-M-LVGJnF3z4
27
27
  nova/trame/view/theme/assets/vuetify_config.json,sha256=a0FSgpLYWGFlRGSMhMq61MyDFBEBwvz55G4qjkM08cs,5627
28
28
  nova/trame/view/theme/theme.py,sha256=HUeuVfzEgeYW65W-LcvXzfYNRHu6aQibGwwgHGyh3OA,11765
29
29
  nova/trame/view/utilities/local_storage.py,sha256=vD8f2VZIpxhIKjZwEaD7siiPCTZO4cw9AfhwdawwYLY,3218
30
- nova/trame/view_model/data_selector.py,sha256=FM1Xe-f-gi1jVwA9nDf2KE1UDvsAvmMKlp78slIpX58,2418
30
+ nova/trame/view_model/data_selector.py,sha256=WrdvCE8J_sye19srGBpBZbheu_YzBEEEo_u098WLh9g,2524
31
31
  nova/trame/view_model/execution_buttons.py,sha256=MfKSp95D92EqpD48C15cBo6dLO0Yld4FeRZMJNxJf7Y,3551
32
32
  nova/trame/view_model/progress_bar.py,sha256=L7ED6TDn5v2142iu-qt3i-jUg_5JEhLyC476t2OtohU,2467
33
33
  nova/trame/view_model/remote_file_input.py,sha256=ojEOJ8ZPkajpbAaZi9VLj7g-uBjhb8BMrTdMmwf_J6A,3367
34
34
  nova/trame/view_model/tool_outputs.py,sha256=ev6LY7fJ0H2xAJn9f5ww28c8Kpom2SYc2FbvFcoN4zg,829
35
- nova_trame-0.20.0.dist-info/LICENSE,sha256=Iu5QiDbwNbREg75iYaxIJ_V-zppuv4QFuBhAW-qiAlM,1061
36
- nova_trame-0.20.0.dist-info/METADATA,sha256=0URjV5ll_dfW8d0kB_EnpCa0ttbNEObIsk0J8HevEu0,1523
37
- nova_trame-0.20.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
38
- nova_trame-0.20.0.dist-info/entry_points.txt,sha256=J2AmeSwiTYZ4ZqHHp9HO6v4MaYQTTBPbNh6WtoqOT58,42
39
- nova_trame-0.20.0.dist-info/RECORD,,
35
+ nova_trame-0.20.2.dist-info/LICENSE,sha256=Iu5QiDbwNbREg75iYaxIJ_V-zppuv4QFuBhAW-qiAlM,1061
36
+ nova_trame-0.20.2.dist-info/METADATA,sha256=i1PstjeVf8uot5fcZJEFQEjonqmUPlrRASpnl7safhY,1523
37
+ nova_trame-0.20.2.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
38
+ nova_trame-0.20.2.dist-info/entry_points.txt,sha256=J2AmeSwiTYZ4ZqHHp9HO6v4MaYQTTBPbNh6WtoqOT58,42
39
+ nova_trame-0.20.2.dist-info/RECORD,,