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.
- nova/trame/model/data_selector.py +39 -18
- nova/trame/view/components/__init__.py +12 -1
- nova/trame/view/components/data_selector.py +20 -3
- nova/trame/view/components/execution_buttons.py +5 -2
- nova/trame/view/components/progress_bar.py +4 -1
- nova/trame/view/components/tool_outputs.py +4 -1
- nova/trame/view/components/visualization/matplotlib_figure.py +4 -4
- nova/trame/view_model/data_selector.py +10 -4
- {nova_trame-0.20.0.dist-info → nova_trame-0.20.2.dist-info}/METADATA +1 -1
- {nova_trame-0.20.0.dist-info → nova_trame-0.20.2.dist-info}/RECORD +13 -13
- {nova_trame-0.20.0.dist-info → nova_trame-0.20.2.dist-info}/LICENSE +0 -0
- {nova_trame-0.20.0.dist-info → nova_trame-0.20.2.dist-info}/WHEEL +0 -0
- {nova_trame-0.20.0.dist-info → nova_trame-0.20.2.dist-info}/entry_points.txt +0 -0
@@ -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(
|
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__(
|
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(
|
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
|
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(
|
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,
|
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__ = [
|
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.
|
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
|
-
|
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(
|
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
|
-
|
192
|
-
|
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.
|
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.
|
42
|
-
|
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,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=
|
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=
|
6
|
-
nova/trame/view/components/data_selector.py,sha256=
|
7
|
-
nova/trame/view/components/execution_buttons.py,sha256=
|
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=
|
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
|
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=
|
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=
|
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.
|
36
|
-
nova_trame-0.20.
|
37
|
-
nova_trame-0.20.
|
38
|
-
nova_trame-0.20.
|
39
|
-
nova_trame-0.20.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|