nova-trame 0.25.5__py3-none-any.whl → 0.26.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/ornl/analysis_data_selector.py +166 -0
- nova/trame/model/ornl/neutron_data_selector.py +8 -129
- nova/trame/model/ornl/oncat_data_selector.py +131 -0
- nova/trame/view/components/data_selector.py +10 -5
- nova/trame/view/components/file_upload.py +5 -0
- nova/trame/view/components/ornl/neutron_data_selector.py +78 -12
- nova/trame/view/components/remote_file_input.py +17 -5
- nova/trame/view/theme/assets/js/revo_grid.js +1 -1
- nova/trame/view_model/data_selector.py +4 -3
- nova/trame/view_model/ornl/neutron_data_selector.py +5 -1
- {nova_trame-0.25.5.dist-info → nova_trame-0.26.2.dist-info}/METADATA +2 -1
- {nova_trame-0.25.5.dist-info → nova_trame-0.26.2.dist-info}/RECORD +15 -13
- {nova_trame-0.25.5.dist-info → nova_trame-0.26.2.dist-info}/LICENSE +0 -0
- {nova_trame-0.25.5.dist-info → nova_trame-0.26.2.dist-info}/WHEEL +0 -0
- {nova_trame-0.25.5.dist-info → nova_trame-0.26.2.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,166 @@
|
|
1
|
+
"""Analysis cluster filesystem backend for NeutronDataSelector."""
|
2
|
+
|
3
|
+
import os
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import Any, Dict, List, Optional
|
6
|
+
from warnings import warn
|
7
|
+
|
8
|
+
from natsort import natsorted
|
9
|
+
from pydantic import Field, model_validator
|
10
|
+
from typing_extensions import Self
|
11
|
+
|
12
|
+
from .neutron_data_selector import NeutronDataSelectorModel, NeutronDataSelectorState
|
13
|
+
|
14
|
+
CUSTOM_DIRECTORIES_LABEL = "Custom Directory"
|
15
|
+
|
16
|
+
INSTRUMENTS = {
|
17
|
+
"HFIR": {
|
18
|
+
"CG-1A": "CG1A",
|
19
|
+
"DEV BEAM": "CG1B",
|
20
|
+
"MARS": "CG1D",
|
21
|
+
"GP-SANS": "CG2",
|
22
|
+
"BIO-SANS": "CG3",
|
23
|
+
"CNPDB": "CG4B",
|
24
|
+
"CTAX": "CG4C",
|
25
|
+
"IMAGINE": "CG4D",
|
26
|
+
"PTAX": "HB1",
|
27
|
+
"VERITAS": "HB1A",
|
28
|
+
"POWDER": "HB2A",
|
29
|
+
"HIDRA": "HB2B",
|
30
|
+
"WAND²": "HB2C",
|
31
|
+
"TAX": "HB3",
|
32
|
+
"DEMAND": "HB3A",
|
33
|
+
"NOWG": "NOWG",
|
34
|
+
"NOWV": "NOWV",
|
35
|
+
},
|
36
|
+
"SNS": {
|
37
|
+
"ARCS": "ARCS",
|
38
|
+
"BL-0": "BL0",
|
39
|
+
"BASIS": "BSS",
|
40
|
+
"CNCS": "CNCS",
|
41
|
+
"CORELLI": "CORELLI",
|
42
|
+
"EQ-SANS": "EQSANS",
|
43
|
+
"HYSPEC": "HYS",
|
44
|
+
"MANDI": "MANDI",
|
45
|
+
"NOMAD": "NOM",
|
46
|
+
"NOWB": "NOWB",
|
47
|
+
"NOWD": "NOWD",
|
48
|
+
"NSE": "NSE",
|
49
|
+
"POWGEN": "PG3",
|
50
|
+
"LIQREF": "REF_L",
|
51
|
+
"MAGREF": "REF_M",
|
52
|
+
"SEQUOIA": "SEQ",
|
53
|
+
"SNAP": "SNAP",
|
54
|
+
"TOPAZ": "TOPAZ",
|
55
|
+
"USANS": "USANS",
|
56
|
+
"VENUS": "VENUS",
|
57
|
+
"VISION": "VIS",
|
58
|
+
"VULCAN": "VULCAN",
|
59
|
+
},
|
60
|
+
}
|
61
|
+
|
62
|
+
|
63
|
+
class AnalysisDataSelectorState(NeutronDataSelectorState):
|
64
|
+
"""Selection state for identifying datafiles."""
|
65
|
+
|
66
|
+
allow_custom_directories: bool = Field(default=False)
|
67
|
+
custom_directory: str = Field(default="", title="Custom Directory")
|
68
|
+
|
69
|
+
@model_validator(mode="after")
|
70
|
+
def validate_state(self) -> Self:
|
71
|
+
valid_facilities = self.get_facilities()
|
72
|
+
if self.facility and self.facility not in valid_facilities:
|
73
|
+
warn(
|
74
|
+
f"Facility '{self.facility}' could not be found. Valid options: {valid_facilities}",
|
75
|
+
stacklevel=1,
|
76
|
+
)
|
77
|
+
|
78
|
+
valid_instruments = self.get_instruments()
|
79
|
+
if self.instrument and self.facility != CUSTOM_DIRECTORIES_LABEL and self.instrument not in valid_instruments:
|
80
|
+
warn(
|
81
|
+
(
|
82
|
+
f"Instrument '{self.instrument}' could not be found in '{self.facility}'. "
|
83
|
+
f"Valid options: {valid_instruments}"
|
84
|
+
),
|
85
|
+
stacklevel=1,
|
86
|
+
)
|
87
|
+
# Validating the experiment is expensive and will fail in our CI due to the filesystem not being mounted there.
|
88
|
+
|
89
|
+
return self
|
90
|
+
|
91
|
+
def get_facilities(self) -> List[str]:
|
92
|
+
facilities = list(INSTRUMENTS.keys())
|
93
|
+
if self.allow_custom_directories:
|
94
|
+
facilities.append(CUSTOM_DIRECTORIES_LABEL)
|
95
|
+
return facilities
|
96
|
+
|
97
|
+
def get_instruments(self) -> List[str]:
|
98
|
+
return list(INSTRUMENTS.get(self.facility, {}).keys())
|
99
|
+
|
100
|
+
|
101
|
+
class AnalysisDataSelectorModel(NeutronDataSelectorModel):
|
102
|
+
"""Analysis cluster filesystem backend for NeutronDataSelector."""
|
103
|
+
|
104
|
+
def __init__(self, state: AnalysisDataSelectorState) -> None:
|
105
|
+
super().__init__(state)
|
106
|
+
self.state: AnalysisDataSelectorState = state
|
107
|
+
|
108
|
+
def set_binding_parameters(self, **kwargs: Any) -> None:
|
109
|
+
super().set_binding_parameters(**kwargs)
|
110
|
+
|
111
|
+
if "allow_custom_directories" in kwargs:
|
112
|
+
self.state.allow_custom_directories = kwargs["allow_custom_directories"]
|
113
|
+
|
114
|
+
def get_custom_directory_path(self) -> Optional[Path]:
|
115
|
+
# Don't expose the full file system
|
116
|
+
if not self.state.custom_directory:
|
117
|
+
return None
|
118
|
+
|
119
|
+
return Path(self.state.custom_directory)
|
120
|
+
|
121
|
+
def get_experiment_directory_path(self) -> Optional[Path]:
|
122
|
+
if not self.state.experiment:
|
123
|
+
return None
|
124
|
+
|
125
|
+
return Path("/") / self.state.facility / self.get_instrument_dir() / self.state.experiment
|
126
|
+
|
127
|
+
def get_instrument_dir(self) -> str:
|
128
|
+
return INSTRUMENTS.get(self.state.facility, {}).get(self.state.instrument, "")
|
129
|
+
|
130
|
+
def get_experiments(self) -> List[str]:
|
131
|
+
experiments = []
|
132
|
+
|
133
|
+
instrument_path = Path("/") / self.state.facility / self.get_instrument_dir()
|
134
|
+
try:
|
135
|
+
for dirname in os.listdir(instrument_path):
|
136
|
+
if dirname.startswith("IPTS-") and os.access(instrument_path / dirname, mode=os.R_OK):
|
137
|
+
experiments.append(dirname)
|
138
|
+
except OSError:
|
139
|
+
pass
|
140
|
+
|
141
|
+
return natsorted(experiments)
|
142
|
+
|
143
|
+
def get_directories(self, base_path: Optional[Path] = None) -> List[Dict[str, Any]]:
|
144
|
+
using_custom_directory = self.state.facility == CUSTOM_DIRECTORIES_LABEL
|
145
|
+
if base_path:
|
146
|
+
pass
|
147
|
+
elif using_custom_directory:
|
148
|
+
base_path = self.get_custom_directory_path()
|
149
|
+
else:
|
150
|
+
base_path = self.get_experiment_directory_path()
|
151
|
+
|
152
|
+
if not base_path:
|
153
|
+
return []
|
154
|
+
|
155
|
+
return self.get_directories_from_path(base_path)
|
156
|
+
|
157
|
+
def get_datafiles(self, *args: Any, **kwargs: Any) -> List[Any]:
|
158
|
+
using_custom_directory = self.state.facility == CUSTOM_DIRECTORIES_LABEL
|
159
|
+
if self.state.experiment:
|
160
|
+
base_path = Path("/") / self.state.facility / self.get_instrument_dir() / self.state.experiment
|
161
|
+
elif using_custom_directory and self.state.custom_directory:
|
162
|
+
base_path = Path(self.state.custom_directory)
|
163
|
+
else:
|
164
|
+
return []
|
165
|
+
|
166
|
+
return [{"path": path} for path in self.get_datafiles_from_path(base_path)]
|
@@ -1,72 +1,20 @@
|
|
1
1
|
"""Model implementation for DataSelector."""
|
2
2
|
|
3
|
-
import os
|
4
3
|
from pathlib import Path
|
5
4
|
from typing import Any, Dict, List, Optional
|
6
|
-
from warnings import warn
|
7
5
|
|
8
6
|
from natsort import natsorted
|
9
|
-
from pydantic import Field, field_validator
|
10
|
-
from typing_extensions import Self
|
7
|
+
from pydantic import Field, field_validator
|
11
8
|
|
12
9
|
from ..data_selector import DataSelectorModel, DataSelectorState
|
13
10
|
|
14
|
-
CUSTOM_DIRECTORIES_LABEL = "Custom Directory"
|
15
|
-
|
16
|
-
INSTRUMENTS = {
|
17
|
-
"HFIR": {
|
18
|
-
"CG-1A": "CG1A",
|
19
|
-
"CG-1B": "CG1B",
|
20
|
-
"CG-1D": "CG1D",
|
21
|
-
"CG-2": "CG2",
|
22
|
-
"CG-3": "CG3",
|
23
|
-
"CG-4B": "CG4B",
|
24
|
-
"CG-4C": "CG4C",
|
25
|
-
"CG-4D": "CG4D",
|
26
|
-
"HB-1": "HB1",
|
27
|
-
"HB-1A": "HB1A",
|
28
|
-
"HB-2A": "HB2A",
|
29
|
-
"HB-2B": "HB2B",
|
30
|
-
"HB-2C": "HB2C",
|
31
|
-
"HB-3": "HB3",
|
32
|
-
"HB-3A": "HB3A",
|
33
|
-
"NOW-G": "NOWG",
|
34
|
-
"NOW-V": "NOWV",
|
35
|
-
},
|
36
|
-
"SNS": {
|
37
|
-
"BL-18": "ARCS",
|
38
|
-
"BL-0": "BL0",
|
39
|
-
"BL-2": "BSS",
|
40
|
-
"BL-5": "CNCS",
|
41
|
-
"BL-9": "CORELLI",
|
42
|
-
"BL-6": "EQSANS",
|
43
|
-
"BL-14B": "HYS",
|
44
|
-
"BL-11B": "MANDI",
|
45
|
-
"BL-1B": "NOM",
|
46
|
-
"NOW-G": "NOWG",
|
47
|
-
"BL-15": "NSE",
|
48
|
-
"BL-11A": "PG3",
|
49
|
-
"BL-4B": "REF_L",
|
50
|
-
"BL-4A": "REF_M",
|
51
|
-
"BL-17": "SEQ",
|
52
|
-
"BL-3": "SNAP",
|
53
|
-
"BL-12": "TOPAZ",
|
54
|
-
"BL-1A": "USANS",
|
55
|
-
"BL-10": "VENUS",
|
56
|
-
"BL-16B": "VIS",
|
57
|
-
"BL-7": "VULCAN",
|
58
|
-
},
|
59
|
-
}
|
60
|
-
|
61
11
|
|
62
12
|
class NeutronDataSelectorState(DataSelectorState):
|
63
13
|
"""Selection state for identifying datafiles."""
|
64
14
|
|
65
|
-
allow_custom_directories: bool = Field(default=False)
|
66
15
|
facility: str = Field(default="", title="Facility")
|
67
16
|
instrument: str = Field(default="", title="Instrument")
|
68
17
|
experiment: str = Field(default="", title="Experiment")
|
69
|
-
custom_directory: str = Field(default="", title="Custom Directory")
|
70
18
|
|
71
19
|
@field_validator("experiment", mode="after")
|
72
20
|
@classmethod
|
@@ -75,33 +23,11 @@ class NeutronDataSelectorState(DataSelectorState):
|
|
75
23
|
raise ValueError("experiment must begin with IPTS-")
|
76
24
|
return experiment
|
77
25
|
|
78
|
-
@model_validator(mode="after")
|
79
|
-
def validate_state(self) -> Self:
|
80
|
-
valid_facilities = self.get_facilities()
|
81
|
-
if self.facility and self.facility not in valid_facilities:
|
82
|
-
warn(f"Facility '{self.facility}' could not be found. Valid options: {valid_facilities}", stacklevel=1)
|
83
|
-
|
84
|
-
valid_instruments = self.get_instruments()
|
85
|
-
if self.instrument and self.facility != CUSTOM_DIRECTORIES_LABEL and self.instrument not in valid_instruments:
|
86
|
-
warn(
|
87
|
-
(
|
88
|
-
f"Instrument '{self.instrument}' could not be found in '{self.facility}'. "
|
89
|
-
f"Valid options: {valid_instruments}"
|
90
|
-
),
|
91
|
-
stacklevel=1,
|
92
|
-
)
|
93
|
-
# Validating the experiment is expensive and will fail in our CI due to the filesystem not being mounted there.
|
94
|
-
|
95
|
-
return self
|
96
|
-
|
97
26
|
def get_facilities(self) -> List[str]:
|
98
|
-
|
99
|
-
if self.allow_custom_directories:
|
100
|
-
facilities.append(CUSTOM_DIRECTORIES_LABEL)
|
101
|
-
return facilities
|
27
|
+
raise NotImplementedError()
|
102
28
|
|
103
29
|
def get_instruments(self) -> List[str]:
|
104
|
-
|
30
|
+
raise NotImplementedError()
|
105
31
|
|
106
32
|
|
107
33
|
class NeutronDataSelectorModel(DataSelectorModel):
|
@@ -120,65 +46,18 @@ class NeutronDataSelectorModel(DataSelectorModel):
|
|
120
46
|
self.state.instrument = kwargs["instrument"]
|
121
47
|
if "experiment" in kwargs:
|
122
48
|
self.state.experiment = kwargs["experiment"]
|
123
|
-
if "allow_custom_directories" in kwargs:
|
124
|
-
self.state.allow_custom_directories = kwargs["allow_custom_directories"]
|
125
49
|
|
126
50
|
def get_facilities(self) -> List[str]:
|
127
51
|
return natsorted(self.state.get_facilities())
|
128
52
|
|
129
|
-
def get_instrument_dir(self) -> str:
|
130
|
-
return INSTRUMENTS.get(self.state.facility, {}).get(self.state.instrument, "")
|
131
|
-
|
132
53
|
def get_instruments(self) -> List[str]:
|
133
54
|
return natsorted(self.state.get_instruments())
|
134
55
|
|
135
56
|
def get_experiments(self) -> List[str]:
|
136
|
-
|
137
|
-
|
138
|
-
instrument_path = Path("/") / self.state.facility / self.get_instrument_dir()
|
139
|
-
try:
|
140
|
-
for dirname in os.listdir(instrument_path):
|
141
|
-
if dirname.startswith("IPTS-") and os.access(instrument_path / dirname, mode=os.R_OK):
|
142
|
-
experiments.append(dirname)
|
143
|
-
except OSError:
|
144
|
-
pass
|
145
|
-
|
146
|
-
return natsorted(experiments)
|
147
|
-
|
148
|
-
def get_experiment_directory_path(self) -> Optional[Path]:
|
149
|
-
if not self.state.experiment:
|
150
|
-
return None
|
151
|
-
|
152
|
-
return Path("/") / self.state.facility / self.get_instrument_dir() / self.state.experiment
|
153
|
-
|
154
|
-
def get_custom_directory_path(self) -> Optional[Path]:
|
155
|
-
# Don't expose the full file system
|
156
|
-
if not self.state.custom_directory:
|
157
|
-
return None
|
158
|
-
|
159
|
-
return Path(self.state.custom_directory)
|
57
|
+
raise NotImplementedError()
|
160
58
|
|
161
59
|
def get_directories(self, base_path: Optional[Path] = None) -> List[Dict[str, Any]]:
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
base_path = self.get_custom_directory_path()
|
167
|
-
else:
|
168
|
-
base_path = self.get_experiment_directory_path()
|
169
|
-
|
170
|
-
if not base_path:
|
171
|
-
return []
|
172
|
-
|
173
|
-
return self.get_directories_from_path(base_path)
|
174
|
-
|
175
|
-
def get_datafiles(self) -> List[str]:
|
176
|
-
using_custom_directory = self.state.facility == CUSTOM_DIRECTORIES_LABEL
|
177
|
-
if self.state.experiment:
|
178
|
-
base_path = Path("/") / self.state.facility / self.get_instrument_dir() / self.state.experiment
|
179
|
-
elif using_custom_directory and self.state.custom_directory:
|
180
|
-
base_path = Path(self.state.custom_directory)
|
181
|
-
else:
|
182
|
-
return []
|
183
|
-
|
184
|
-
return self.get_datafiles_from_path(base_path)
|
60
|
+
raise NotImplementedError()
|
61
|
+
|
62
|
+
def get_datafiles(self, *args: Any, **kwargs: Any) -> List[str]:
|
63
|
+
raise NotImplementedError()
|
@@ -0,0 +1,131 @@
|
|
1
|
+
"""ONCat backend for NeutronDataSelector."""
|
2
|
+
|
3
|
+
import os
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import Any, Dict, List, Optional
|
6
|
+
|
7
|
+
from natsort import natsorted
|
8
|
+
from pydantic import Field
|
9
|
+
from pyoncat import CLIENT_CREDENTIALS_FLOW, ONCat
|
10
|
+
|
11
|
+
from .neutron_data_selector import NeutronDataSelectorModel, NeutronDataSelectorState
|
12
|
+
|
13
|
+
TOKEN_VARNAME = "USER_OIDC_TOKEN"
|
14
|
+
ID_VARNAME = "ONCAT_CLIENT_ID"
|
15
|
+
SECRET_VARNAME = "ONCAT_CLIENT_SECRET"
|
16
|
+
|
17
|
+
|
18
|
+
class ONCatDataSelectorState(NeutronDataSelectorState):
|
19
|
+
"""Selection state for identifying datafiles."""
|
20
|
+
|
21
|
+
instrument_mapping: Dict[str, str] = Field(default={})
|
22
|
+
projection: List[str] = Field(default=[])
|
23
|
+
|
24
|
+
|
25
|
+
class ONCatDataSelectorModel(NeutronDataSelectorModel):
|
26
|
+
"""ONCat backend for NeutronDataSelector."""
|
27
|
+
|
28
|
+
def __init__(self, state: ONCatDataSelectorState) -> None:
|
29
|
+
super().__init__(state)
|
30
|
+
self.state: ONCatDataSelectorState = state
|
31
|
+
|
32
|
+
user_token = os.environ.get(TOKEN_VARNAME, "")
|
33
|
+
client_id = os.environ.get(ID_VARNAME, "")
|
34
|
+
client_secret = os.environ.get(SECRET_VARNAME, "")
|
35
|
+
if user_token:
|
36
|
+
self.oncat_client = ONCat(url="https://calvera-test.ornl.gov/oncat", api_token=user_token)
|
37
|
+
elif client_id and client_secret:
|
38
|
+
self.oncat_client = ONCat(
|
39
|
+
url="https://oncat.ornl.gov",
|
40
|
+
client_id=client_id,
|
41
|
+
client_secret=client_secret,
|
42
|
+
flow=CLIENT_CREDENTIALS_FLOW,
|
43
|
+
)
|
44
|
+
else:
|
45
|
+
raise EnvironmentError(
|
46
|
+
f"In order to use the ONCat backend for NeutronDataSelector, you must set either {TOKEN_VARNAME} or "
|
47
|
+
f"both {ID_VARNAME} and {SECRET_VARNAME} in your environment."
|
48
|
+
)
|
49
|
+
|
50
|
+
def set_binding_parameters(self, **kwargs: Any) -> None:
|
51
|
+
super().set_binding_parameters(**kwargs)
|
52
|
+
|
53
|
+
if "projection" in kwargs:
|
54
|
+
self.state.projection = kwargs["projection"]
|
55
|
+
|
56
|
+
def get_facilities(self) -> List[str]:
|
57
|
+
facilities = []
|
58
|
+
for facility_data in self.oncat_client.Facility.list(projection=["name"]):
|
59
|
+
facilities.append(facility_data.name)
|
60
|
+
return natsorted(facilities)
|
61
|
+
|
62
|
+
def get_instruments(self) -> List[str]:
|
63
|
+
if not self.state.facility:
|
64
|
+
return []
|
65
|
+
|
66
|
+
self.state.instrument_mapping = {}
|
67
|
+
instruments = []
|
68
|
+
for instrument_data in self.oncat_client.Instrument.list(
|
69
|
+
facility=self.state.facility, projection=["short_name"]
|
70
|
+
):
|
71
|
+
self.state.instrument_mapping[instrument_data.short_name] = instrument_data.id
|
72
|
+
instruments.append(instrument_data.short_name)
|
73
|
+
return natsorted(instruments)
|
74
|
+
|
75
|
+
def get_experiments(self) -> List[str]:
|
76
|
+
if not self.state.facility or not self.state.instrument:
|
77
|
+
return []
|
78
|
+
|
79
|
+
experiments = []
|
80
|
+
for experiment_data in self.oncat_client.Experiment.list(
|
81
|
+
facility=self.state.facility,
|
82
|
+
instrument=self.state.instrument_mapping[self.state.instrument],
|
83
|
+
projection=["name"],
|
84
|
+
):
|
85
|
+
experiments.append(experiment_data.name)
|
86
|
+
return natsorted(experiments)
|
87
|
+
|
88
|
+
def get_directories(self, _: Optional[Path] = None) -> List[Dict[str, Any]]:
|
89
|
+
return []
|
90
|
+
|
91
|
+
def create_datafile_obj(self, data: Dict[str, Any], projection: List[str]) -> Dict[str, str]:
|
92
|
+
new_obj = {"path": data["location"]}
|
93
|
+
|
94
|
+
for key in projection:
|
95
|
+
value: Any = data
|
96
|
+
|
97
|
+
if key == "location":
|
98
|
+
continue
|
99
|
+
|
100
|
+
for part in key.split("."):
|
101
|
+
try:
|
102
|
+
value = value[part]
|
103
|
+
except KeyError:
|
104
|
+
value = ""
|
105
|
+
break
|
106
|
+
|
107
|
+
new_obj[key] = value
|
108
|
+
|
109
|
+
return new_obj
|
110
|
+
|
111
|
+
def get_datafiles(self, *args: Any, **kwargs: Any) -> List[Any]:
|
112
|
+
if not self.state.facility or not self.state.instrument or not self.state.experiment:
|
113
|
+
return []
|
114
|
+
|
115
|
+
projection = ["location"] + self.state.projection
|
116
|
+
|
117
|
+
datafiles = []
|
118
|
+
for datafile_data in self.oncat_client.Datafile.list(
|
119
|
+
facility=self.state.facility,
|
120
|
+
instrument=self.state.instrument_mapping[self.state.instrument],
|
121
|
+
experiment=self.state.experiment,
|
122
|
+
projection=projection,
|
123
|
+
):
|
124
|
+
path = datafile_data.location
|
125
|
+
if self.state.extensions:
|
126
|
+
for extension in self.state.extensions:
|
127
|
+
if path.lower().endswith(extension):
|
128
|
+
datafiles.append(self.create_datafile_obj(datafile_data, projection))
|
129
|
+
else:
|
130
|
+
datafiles.append(self.create_datafile_obj(datafile_data, projection))
|
131
|
+
return natsorted(datafiles, key=lambda d: d["path"])
|
@@ -155,10 +155,10 @@ class DataSelector(datagrid.VGrid):
|
|
155
155
|
)
|
156
156
|
vuetify.VListItem("No directories found", classes="flex-0-1 text-center", v_else=True)
|
157
157
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
columns=(
|
158
|
+
if "columns" in kwargs:
|
159
|
+
columns = kwargs.pop("columns")
|
160
|
+
else:
|
161
|
+
columns = (
|
162
162
|
"[{"
|
163
163
|
" cellTemplate: (createElement, props) =>"
|
164
164
|
f" window.grid_manager.get('{self._revogrid_id}').cellTemplate(createElement, props),"
|
@@ -167,7 +167,12 @@ class DataSelector(datagrid.VGrid):
|
|
167
167
|
" name: 'Available Datafiles',"
|
168
168
|
" prop: 'title',"
|
169
169
|
"}]",
|
170
|
-
)
|
170
|
+
)
|
171
|
+
|
172
|
+
super().__init__(
|
173
|
+
v_model=self._v_model,
|
174
|
+
can_focus=False,
|
175
|
+
columns=columns,
|
171
176
|
column_span=1 if isinstance(self._subdirectory, tuple) or not self._subdirectory else 2,
|
172
177
|
frame_size=10,
|
173
178
|
hide_attribution=True,
|
@@ -21,6 +21,7 @@ class FileUpload(vuetify.VBtn):
|
|
21
21
|
extensions: Union[List[str], Tuple, None] = None,
|
22
22
|
label: str = "",
|
23
23
|
return_contents: Union[bool, Tuple] = True,
|
24
|
+
use_bytes: Union[bool, Tuple] = False,
|
24
25
|
**kwargs: Any,
|
25
26
|
) -> None:
|
26
27
|
"""Constructor for FileUpload.
|
@@ -40,6 +41,8 @@ class FileUpload(vuetify.VBtn):
|
|
40
41
|
return_contents : Union[bool, Tuple], optional
|
41
42
|
If true, the file contents will be stored in v_model. If false, a file path will be stored in v_model.
|
42
43
|
Defaults to true.
|
44
|
+
use_bytes : Union[bool, Tuple], optional
|
45
|
+
If true, then files uploaded from the local machine will contain bytes rather than text.
|
43
46
|
**kwargs
|
44
47
|
All other arguments will be passed to the underlying
|
45
48
|
`Button component <https://trame.readthedocs.io/en/latest/trame.widgets.vuetify3.html#trame.widgets.vuetify3.VBtn>`_.
|
@@ -54,6 +57,7 @@ class FileUpload(vuetify.VBtn):
|
|
54
57
|
self._base_paths = base_paths if base_paths else ["/"]
|
55
58
|
self._extensions = extensions if extensions else []
|
56
59
|
self._return_contents = return_contents
|
60
|
+
self._use_bytes = use_bytes
|
57
61
|
self._ref_name = f"nova__fileupload_{self._next_id}"
|
58
62
|
|
59
63
|
super().__init__(label, **kwargs)
|
@@ -83,6 +87,7 @@ class FileUpload(vuetify.VBtn):
|
|
83
87
|
extensions=self._extensions,
|
84
88
|
input_props={"classes": "d-none"},
|
85
89
|
return_contents=self._return_contents,
|
90
|
+
use_bytes=self._use_bytes,
|
86
91
|
)
|
87
92
|
|
88
93
|
with self:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
"""View Implementation for DataSelector."""
|
2
2
|
|
3
|
-
from typing import Any, List, Tuple, Union
|
3
|
+
from typing import Any, List, Literal, Tuple, Union
|
4
4
|
from warnings import warn
|
5
5
|
|
6
6
|
from trame.app import get_server
|
@@ -8,11 +8,13 @@ from trame.widgets import vuetify3 as vuetify
|
|
8
8
|
|
9
9
|
from nova.mvvm._internal.utils import rgetdictvalue
|
10
10
|
from nova.mvvm.trame_binding import TrameBinding
|
11
|
-
from nova.trame.model.ornl.
|
11
|
+
from nova.trame.model.ornl.analysis_data_selector import (
|
12
12
|
CUSTOM_DIRECTORIES_LABEL,
|
13
|
-
|
14
|
-
|
13
|
+
AnalysisDataSelectorModel,
|
14
|
+
AnalysisDataSelectorState,
|
15
15
|
)
|
16
|
+
from nova.trame.model.ornl.neutron_data_selector import NeutronDataSelectorModel
|
17
|
+
from nova.trame.model.ornl.oncat_data_selector import ONCatDataSelectorModel, ONCatDataSelectorState
|
16
18
|
from nova.trame.view.layouts import GridLayout
|
17
19
|
from nova.trame.view_model.ornl.neutron_data_selector import NeutronDataSelectorViewModel
|
18
20
|
|
@@ -29,10 +31,12 @@ class NeutronDataSelector(DataSelector):
|
|
29
31
|
self,
|
30
32
|
v_model: Union[str, Tuple],
|
31
33
|
allow_custom_directories: Union[bool, Tuple] = False,
|
34
|
+
data_source: Literal["filesystem", "oncat"] = "filesystem",
|
32
35
|
facility: Union[str, Tuple] = "",
|
33
36
|
instrument: Union[str, Tuple] = "",
|
34
37
|
experiment: Union[str, Tuple] = "",
|
35
38
|
extensions: Union[List[str], Tuple, None] = None,
|
39
|
+
projection: Union[List[str], Tuple, None] = None,
|
36
40
|
subdirectory: Union[str, Tuple] = "",
|
37
41
|
refresh_rate: Union[int, Tuple] = 30,
|
38
42
|
select_strategy: Union[str, Tuple] = "all",
|
@@ -48,6 +52,11 @@ class NeutronDataSelector(DataSelector):
|
|
48
52
|
allow_custom_directories : Union[bool, Tuple], optional
|
49
53
|
Whether or not to allow users to provide their own directories to search for datafiles in. Ignored if the
|
50
54
|
facility parameter is set.
|
55
|
+
data_source : Literal["filesystem", "oncat"], optional
|
56
|
+
The source from which to pull datafiles. Defaults to "filesystem". If using ONCat, you will need to set the
|
57
|
+
following environment variables for local development: `ONCAT_CLIENT_ID` and `ONCAT_CLIENT_SECRET`. Note
|
58
|
+
that this parameter does not supporting Trame bindings. If you need to swap between the options, please
|
59
|
+
create two instances of this class and switch between them using a v_if or a v_show.
|
51
60
|
facility : Union[str, Tuple], optional
|
52
61
|
The facility to restrict data selection to. Options: HFIR, SNS
|
53
62
|
instrument : Union[str, Tuple], optional
|
@@ -56,6 +65,9 @@ class NeutronDataSelector(DataSelector):
|
|
56
65
|
The experiment to restrict data selection to.
|
57
66
|
extensions : Union[List[str], Tuple], optional
|
58
67
|
A list of file extensions to restrict selection to. If unset, then all files will be shown.
|
68
|
+
projection : Union[List[str], Tuple], optional
|
69
|
+
Sets the projection argument when pulling data files via pyoncat. Please refer to the ONCat documentation
|
70
|
+
for how to use this. This should only be used with `data_source="oncat"`.
|
59
71
|
subdirectory : Union[str, Tuple], optional
|
60
72
|
A subdirectory within the user's chosen experiment to show files. If not specified as a string, the user
|
61
73
|
will be shown a folder browser and will be able to see all files in the experiment that they have access to.
|
@@ -73,6 +85,12 @@ class NeutronDataSelector(DataSelector):
|
|
73
85
|
-------
|
74
86
|
None
|
75
87
|
"""
|
88
|
+
if data_source == "oncat" and allow_custom_directories:
|
89
|
+
warn("allow_custom_directories will be ignored since data will be pulled from ONCat.", stacklevel=1)
|
90
|
+
|
91
|
+
if data_source == "oncat" and subdirectory:
|
92
|
+
warn("subdirectory will be ignored since data will be pulled from ONCat.", stacklevel=1)
|
93
|
+
|
76
94
|
if isinstance(facility, str) and allow_custom_directories:
|
77
95
|
warn("allow_custom_directories will be ignored since the facility parameter is fixed.", stacklevel=1)
|
78
96
|
|
@@ -81,6 +99,8 @@ class NeutronDataSelector(DataSelector):
|
|
81
99
|
self._experiment = experiment
|
82
100
|
self._allow_custom_directories = allow_custom_directories
|
83
101
|
self._last_allow_custom_directories = self._allow_custom_directories
|
102
|
+
self._data_source = data_source
|
103
|
+
self._projection = projection
|
84
104
|
|
85
105
|
self._state_name = f"nova__dataselector_{self._next_id}_state"
|
86
106
|
self._facilities_name = f"nova__neutrondataselector_{self._next_id}_facilities"
|
@@ -100,19 +120,43 @@ class NeutronDataSelector(DataSelector):
|
|
100
120
|
v_model,
|
101
121
|
"",
|
102
122
|
extensions=extensions,
|
103
|
-
subdirectory=subdirectory,
|
123
|
+
subdirectory=subdirectory if data_source == "filesystem" else "oncat",
|
104
124
|
refresh_rate=refresh_rate,
|
105
125
|
select_strategy=select_strategy,
|
106
126
|
**kwargs,
|
107
127
|
)
|
108
128
|
|
129
|
+
def create_projection_column_title(self, key: str) -> str:
|
130
|
+
return key.split(".")[-1].replace("_", " ").title()
|
131
|
+
|
109
132
|
def create_ui(self, **kwargs: Any) -> None:
|
110
|
-
|
133
|
+
if self._data_source == "oncat":
|
134
|
+
columns = (
|
135
|
+
"["
|
136
|
+
" {"
|
137
|
+
" cellTemplate: (createElement, props) =>"
|
138
|
+
f" window.grid_manager.get('{self._revogrid_id}').cellTemplate(createElement, props),"
|
139
|
+
" columnTemplate: (createElement) =>"
|
140
|
+
f" window.grid_manager.get('{self._revogrid_id}').columnTemplate(createElement),"
|
141
|
+
" name: 'Available Datafiles',"
|
142
|
+
" prop: 'title',"
|
143
|
+
" size: 150,"
|
144
|
+
" },"
|
145
|
+
)
|
146
|
+
if self._projection:
|
147
|
+
for key in self._projection:
|
148
|
+
columns += f"{{name: '{self.create_projection_column_title(key)}', prop: '{key}', size: 150}},"
|
149
|
+
columns += "]"
|
150
|
+
|
151
|
+
super().create_ui(columns=(columns,), resize=True, **kwargs)
|
152
|
+
else:
|
153
|
+
super().create_ui(**kwargs)
|
154
|
+
|
111
155
|
with self._layout.filter:
|
112
156
|
with GridLayout(columns=3):
|
113
|
-
|
157
|
+
column_span = 3
|
114
158
|
if isinstance(self._facility, tuple) or not self._facility:
|
115
|
-
|
159
|
+
column_span -= 1
|
116
160
|
InputField(
|
117
161
|
v_model=self._selected_facility_name,
|
118
162
|
items=(self._facilities_name,),
|
@@ -120,7 +164,7 @@ class NeutronDataSelector(DataSelector):
|
|
120
164
|
update_modelValue=(self.update_facility, "[$event]"),
|
121
165
|
)
|
122
166
|
if isinstance(self._instrument, tuple) or not self._instrument:
|
123
|
-
|
167
|
+
column_span -= 1
|
124
168
|
InputField(
|
125
169
|
v_if=f"{self._selected_facility_name} !== '{CUSTOM_DIRECTORIES_LABEL}'",
|
126
170
|
v_model=self._selected_instrument_name,
|
@@ -131,7 +175,7 @@ class NeutronDataSelector(DataSelector):
|
|
131
175
|
InputField(
|
132
176
|
v_if=f"{self._selected_facility_name} !== '{CUSTOM_DIRECTORIES_LABEL}'",
|
133
177
|
v_model=self._selected_experiment_name,
|
134
|
-
column_span=
|
178
|
+
column_span=column_span,
|
135
179
|
items=(self._experiments_name,),
|
136
180
|
type="autocomplete",
|
137
181
|
update_modelValue=(self.update_experiment, "[$event]"),
|
@@ -139,8 +183,11 @@ class NeutronDataSelector(DataSelector):
|
|
139
183
|
InputField(v_else=True, v_model=f"{self._state_name}.custom_directory", column_span=2)
|
140
184
|
|
141
185
|
def _create_model(self) -> None:
|
142
|
-
|
143
|
-
self.
|
186
|
+
self._model: NeutronDataSelectorModel
|
187
|
+
if self._data_source == "oncat":
|
188
|
+
self._model = ONCatDataSelectorModel(ONCatDataSelectorState())
|
189
|
+
else:
|
190
|
+
self._model = AnalysisDataSelectorModel(AnalysisDataSelectorState())
|
144
191
|
|
145
192
|
def _create_viewmodel(self) -> None:
|
146
193
|
server = get_server(None, client_type="vue3")
|
@@ -167,6 +214,7 @@ class NeutronDataSelector(DataSelector):
|
|
167
214
|
set_state_param(self.state, self._instrument)
|
168
215
|
set_state_param(self.state, self._experiment)
|
169
216
|
set_state_param(self.state, self._allow_custom_directories)
|
217
|
+
set_state_param(self.state, self._projection)
|
170
218
|
self._last_facility = get_state_param(self.state, self._facility)
|
171
219
|
self._last_instrument = get_state_param(self.state, self._instrument)
|
172
220
|
self._last_experiment = get_state_param(self.state, self._experiment)
|
@@ -175,6 +223,7 @@ class NeutronDataSelector(DataSelector):
|
|
175
223
|
instrument=get_state_param(self.state, self._instrument),
|
176
224
|
experiment=get_state_param(self.state, self._experiment),
|
177
225
|
allow_custom_directories=get_state_param(self.state, self._allow_custom_directories),
|
226
|
+
projection=get_state_param(self.state, self._projection),
|
178
227
|
)
|
179
228
|
|
180
229
|
# Now we set up the change listeners for all bound parameters. These are responsible for updating the component
|
@@ -232,6 +281,17 @@ class NeutronDataSelector(DataSelector):
|
|
232
281
|
)
|
233
282
|
)
|
234
283
|
|
284
|
+
if isinstance(self._projection, tuple):
|
285
|
+
|
286
|
+
@self.state.change(self._projection[0].split(".")[0])
|
287
|
+
def on_projection_change(**kwargs: Any) -> None:
|
288
|
+
projection = rgetdictvalue(kwargs, self._projection[0]) # type: ignore
|
289
|
+
if projection != self._projection:
|
290
|
+
self._projection = projection
|
291
|
+
self._vm.set_binding_parameters(
|
292
|
+
projection=set_state_param(self.state, self._projection, projection)
|
293
|
+
)
|
294
|
+
|
235
295
|
# These update methods notify the rest of the application when the component changes bound parameters.
|
236
296
|
def update_facility(self, facility: str) -> None:
|
237
297
|
self._vm.set_binding_parameters(
|
@@ -257,3 +317,9 @@ class NeutronDataSelector(DataSelector):
|
|
257
317
|
experiment=set_state_param(self.state, (self._selected_experiment_name,), experiment),
|
258
318
|
)
|
259
319
|
self._vm.reset()
|
320
|
+
|
321
|
+
def set_state(self, *args: Any, **kwargs: Any) -> None:
|
322
|
+
raise TypeError(
|
323
|
+
"The set_state method has been removed. Please use update_facility, update_instrument, and "
|
324
|
+
"update_experiment instead."
|
325
|
+
)
|
@@ -12,7 +12,7 @@ from trame_server.core import State
|
|
12
12
|
|
13
13
|
from nova.mvvm._internal.utils import rgetdictvalue
|
14
14
|
from nova.mvvm.trame_binding import TrameBinding
|
15
|
-
from nova.trame._internal.utils import get_state_name, set_state_param
|
15
|
+
from nova.trame._internal.utils import get_state_name, get_state_param, set_state_param
|
16
16
|
from nova.trame.model.remote_file_input import RemoteFileInputModel
|
17
17
|
from nova.trame.view_model.remote_file_input import RemoteFileInputViewModel
|
18
18
|
|
@@ -35,6 +35,7 @@ class RemoteFileInput:
|
|
35
35
|
extensions: Union[List[str], Tuple, None] = None,
|
36
36
|
input_props: Optional[dict[str, Any]] = None,
|
37
37
|
return_contents: Union[bool, Tuple] = False,
|
38
|
+
use_bytes: Union[bool, Tuple] = False,
|
38
39
|
) -> None:
|
39
40
|
"""Constructor for RemoteFileInput.
|
40
41
|
|
@@ -58,6 +59,8 @@ class RemoteFileInput:
|
|
58
59
|
return_contents : Union[bool, Tuple], optional
|
59
60
|
If true, then the v_model will contain the contents of the file. If false, then the v_model will contain the
|
60
61
|
path of the file. Defaults to false.
|
62
|
+
use_bytes : Union[bool, Tuple], optional
|
63
|
+
If true, then the file contents will be treated as bytestreams when calling decode_file.
|
61
64
|
|
62
65
|
Returns
|
63
66
|
-------
|
@@ -73,6 +76,7 @@ class RemoteFileInput:
|
|
73
76
|
self.extensions = extensions if extensions else []
|
74
77
|
self.input_props = dict(input_props) if input_props else {}
|
75
78
|
self.return_contents = return_contents
|
79
|
+
self.use_bytes = use_bytes
|
76
80
|
|
77
81
|
if "__events" not in self.input_props:
|
78
82
|
self.input_props["__events"] = []
|
@@ -286,14 +290,22 @@ class RemoteFileInput:
|
|
286
290
|
self.decode_file(file.read())
|
287
291
|
|
288
292
|
def decode_file(self, bytestream: bytes, set_contents: bool = False) -> None:
|
293
|
+
use_bytes = get_state_param(self.state, self.use_bytes)
|
294
|
+
|
289
295
|
decoded_content = bytestream.decode("latin1")
|
290
296
|
if set_contents:
|
291
297
|
self.set_v_model(decoded_content)
|
292
298
|
else:
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
299
|
+
if use_bytes:
|
300
|
+
with NamedTemporaryFile(mode="wb", delete=False) as temp_file:
|
301
|
+
temp_file.write(bytestream)
|
302
|
+
temp_file.flush()
|
303
|
+
self.set_v_model(temp_file.name)
|
304
|
+
else:
|
305
|
+
with NamedTemporaryFile(mode="w", delete=False, encoding="utf-8") as temp_file:
|
306
|
+
temp_file.write(decoded_content)
|
307
|
+
temp_file.flush()
|
308
|
+
self.set_v_model(temp_file.name)
|
297
309
|
|
298
310
|
def select_file(self, value: str) -> None:
|
299
311
|
"""Programmatically set the v_model value."""
|
@@ -21,7 +21,7 @@ class RevoGrid {
|
|
21
21
|
const modelValue = _.get(trameState, this.modelKey)
|
22
22
|
const availableData = _.get(trameState, this.dataKey)
|
23
23
|
const selectAllCheckbox = this.grid.querySelector(".header-content input")
|
24
|
-
const rowCheckboxes = this.grid.querySelectorAll(".rgCell")
|
24
|
+
const rowCheckboxes = this.grid.querySelectorAll(".rgCell:first-child")
|
25
25
|
|
26
26
|
if (selectAllCheckbox === null) {
|
27
27
|
return
|
@@ -66,6 +66,9 @@ class DataSelectorViewModel:
|
|
66
66
|
self.model.set_subdirectory(subdirectory_path)
|
67
67
|
self.update_view()
|
68
68
|
|
69
|
+
def transform_datafiles(self, datafiles: List[Any]) -> List[Dict[str, str]]:
|
70
|
+
return [{"path": datafile, "title": os.path.basename(datafile)} for datafile in datafiles]
|
71
|
+
|
69
72
|
def update_view(self, refresh_directories: bool = False) -> None:
|
70
73
|
self.state_bind.update_in_view(self.model.state)
|
71
74
|
if not self.directories or refresh_directories:
|
@@ -73,8 +76,6 @@ class DataSelectorViewModel:
|
|
73
76
|
self.reexpand_directories()
|
74
77
|
self.directories_bind.update_in_view(self.directories)
|
75
78
|
|
76
|
-
self.datafiles =
|
77
|
-
{"path": datafile, "title": os.path.basename(datafile)} for datafile in self.model.get_datafiles()
|
78
|
-
]
|
79
|
+
self.datafiles = self.transform_datafiles(self.model.get_datafiles())
|
79
80
|
self.datafiles_bind.update_in_view(self.datafiles)
|
80
81
|
self.reset_grid_bind.update_in_view(None)
|
@@ -1,6 +1,7 @@
|
|
1
1
|
"""View model implementation for the DataSelector widget."""
|
2
2
|
|
3
|
-
|
3
|
+
import os
|
4
|
+
from typing import Any, Dict, List
|
4
5
|
|
5
6
|
from nova.mvvm.interface import BindingInterface
|
6
7
|
from nova.trame.model.ornl.neutron_data_selector import NeutronDataSelectorModel
|
@@ -33,6 +34,9 @@ class NeutronDataSelectorViewModel(DataSelectorViewModel):
|
|
33
34
|
self.reset()
|
34
35
|
self.update_view()
|
35
36
|
|
37
|
+
def transform_datafiles(self, datafiles: List[Any]) -> List[Dict[str, str]]:
|
38
|
+
return [{"title": os.path.basename(datafile["path"]), **datafile} for datafile in datafiles]
|
39
|
+
|
36
40
|
def update_view(self, refresh_directories: bool = False) -> None:
|
37
41
|
self.facilities_bind.update_in_view(self.model.get_facilities())
|
38
42
|
self.instruments_bind.update_in_view(self.model.get_instruments())
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: nova-trame
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.26.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
|
@@ -22,6 +22,7 @@ Requires-Dist: natsort (>=8.4.0,<9.0.0)
|
|
22
22
|
Requires-Dist: nova-common (>=0.2.2)
|
23
23
|
Requires-Dist: nova-mvvm
|
24
24
|
Requires-Dist: pydantic
|
25
|
+
Requires-Dist: pyoncat (>=2.1,<3.0)
|
25
26
|
Requires-Dist: tomli
|
26
27
|
Requires-Dist: tornado (>=6.5.0)
|
27
28
|
Requires-Dist: trame
|
@@ -2,17 +2,19 @@ nova/__init__.py,sha256=ED6jHcYiuYpr_0vjGz0zx2lrrmJT9sDJCzIljoDfmlM,65
|
|
2
2
|
nova/trame/__init__.py,sha256=gFrAg1qva5PIqR5TjvPzAxLx103IKipJLqp3XXvrQL8,59
|
3
3
|
nova/trame/_internal/utils.py,sha256=lTTJnfqbbIe21Tg2buf5MXqKUEUop7Va5PZgpWMzRkI,1381
|
4
4
|
nova/trame/model/data_selector.py,sha256=hPPk1wiJc52jMXP_7XlEur38_vSi9tRUNKIa_gNmblc,4501
|
5
|
-
nova/trame/model/ornl/
|
5
|
+
nova/trame/model/ornl/analysis_data_selector.py,sha256=P7IEJdqCAUsEOCof4c2JantPXd9vz0EtvhryEKvscbw,5544
|
6
|
+
nova/trame/model/ornl/neutron_data_selector.py,sha256=YMoNEpDKgjP_y18oYj-N9IjkxtqwHz9JYMlURQA4BCE,2148
|
7
|
+
nova/trame/model/ornl/oncat_data_selector.py,sha256=3JEkWGMU-esWA9DUTglju9hEP9LyZ7EUXLj1yO5BIDs,4755
|
6
8
|
nova/trame/model/remote_file_input.py,sha256=eAk7ZsFgNKcnpJ6KmOQDhiI6pPZpcrr1GMKkRLEWht8,4338
|
7
9
|
nova/trame/view/components/__init__.py,sha256=60BeS69aOrFnkptjuD17rfPE1f4Z35iBH56TRmW5MW8,451
|
8
|
-
nova/trame/view/components/data_selector.py,sha256=
|
10
|
+
nova/trame/view/components/data_selector.py,sha256=T5CFtWPio3YwiPvPEBrWn7vgRo-3l0GqdGKzonc_cwc,14970
|
9
11
|
nova/trame/view/components/execution_buttons.py,sha256=Br6uAmE5bY67TTYc5ZTHECNJ_RJqKmv17HAKPpQtbeg,4576
|
10
|
-
nova/trame/view/components/file_upload.py,sha256=
|
12
|
+
nova/trame/view/components/file_upload.py,sha256=WOaFXeNNwN0DYZJr-W6vWdBiTpr7m-lq3WKJaHmeMe8,4560
|
11
13
|
nova/trame/view/components/input_field.py,sha256=xzCmNEoB4ljGx99-gGgTV0UwriwtS8ce22zPA4QneZw,17372
|
12
14
|
nova/trame/view/components/ornl/__init__.py,sha256=HnxzzSsxw0vQSDCVFfWsAxx1n3HnU37LMuQkfiewmSU,90
|
13
|
-
nova/trame/view/components/ornl/neutron_data_selector.py,sha256=
|
15
|
+
nova/trame/view/components/ornl/neutron_data_selector.py,sha256=wVSLmRXiGOv2l-nZ0DgDwjrgTMZDu9e9ECm3k824k3o,16749
|
14
16
|
nova/trame/view/components/progress_bar.py,sha256=zhbJwPy_HPQ8YL-ISN8sCRUQ7qY6qqo9wiV59BmvL8I,3038
|
15
|
-
nova/trame/view/components/remote_file_input.py,sha256=
|
17
|
+
nova/trame/view/components/remote_file_input.py,sha256=mcz_bmI2rD8gdmIOKLhlzfj-XoWBwC99T9ZgQORaKqE,14674
|
16
18
|
nova/trame/view/components/tool_outputs.py,sha256=IbYV4VjrkWAE354Bh5KH76SPsxGLIkOXChijS4-ce_Y,2408
|
17
19
|
nova/trame/view/components/visualization/__init__.py,sha256=reqkkbhD5uSksHHlhVMy1qNUCwSekS5HlXk6wCREYxU,152
|
18
20
|
nova/trame/view/components/visualization/interactive_2d_plot.py,sha256=z2s1janxAclpMEdDJk3z-CQ6r3KPNoR_SXPx9ppWnuQ,3481
|
@@ -27,19 +29,19 @@ nova/trame/view/theme/assets/core_style.scss,sha256=3-3qMc5gpaDhfuVWAF_psBH5alxw
|
|
27
29
|
nova/trame/view/theme/assets/favicon.png,sha256=Xbp1nUmhcBDeObjsebEbEAraPDZ_M163M_ZLtm5AbQc,1927
|
28
30
|
nova/trame/view/theme/assets/js/delay_manager.js,sha256=BN4OL88QsyZG4XQ1sTorHpN1rwD4GnWoVKHvl5F5ydo,776
|
29
31
|
nova/trame/view/theme/assets/js/lodash.min.js,sha256=KCyAYJ-fsqtp_HMwbjhy6IKjlA5lrVrtWt1JdMsC57k,73016
|
30
|
-
nova/trame/view/theme/assets/js/revo_grid.js,sha256=
|
32
|
+
nova/trame/view/theme/assets/js/revo_grid.js,sha256=fbuEWO8etw-xgo9tjJGjJXdd5wL8qpgabPmrnU6Jp8k,4081
|
31
33
|
nova/trame/view/theme/assets/vuetify_config.json,sha256=a0FSgpLYWGFlRGSMhMq61MyDFBEBwvz55G4qjkM08cs,5627
|
32
34
|
nova/trame/view/theme/exit_button.py,sha256=Kqv1GVJZGrSsj6_JFjGU3vm3iNuMolLC2T1x2IsdmV0,3094
|
33
35
|
nova/trame/view/theme/theme.py,sha256=8JqSrEbhxK1SccXE1_jUdel9Wtc2QNObVEwtbVWG_QY,13146
|
34
36
|
nova/trame/view/utilities/local_storage.py,sha256=vD8f2VZIpxhIKjZwEaD7siiPCTZO4cw9AfhwdawwYLY,3218
|
35
|
-
nova/trame/view_model/data_selector.py,sha256=
|
37
|
+
nova/trame/view_model/data_selector.py,sha256=jAtq5hpohQ6YiLBbgLJfNUzWZBpN2bjCG_c_FCJu2ns,3186
|
36
38
|
nova/trame/view_model/execution_buttons.py,sha256=MfKSp95D92EqpD48C15cBo6dLO0Yld4FeRZMJNxJf7Y,3551
|
37
|
-
nova/trame/view_model/ornl/neutron_data_selector.py,sha256=
|
39
|
+
nova/trame/view_model/ornl/neutron_data_selector.py,sha256=PIKQyzcHpwu81DNk3d8AfgobDbxbdb9ppRLpEvdPgpw,1778
|
38
40
|
nova/trame/view_model/progress_bar.py,sha256=6AUKHF3hfzbdsHqNEnmHRgDcBKY5TT8ywDx9S6ovnsc,2854
|
39
41
|
nova/trame/view_model/remote_file_input.py,sha256=zWOflmCDJYYR_pacHphwzricV667GSRokh-mlxpBAOo,3646
|
40
42
|
nova/trame/view_model/tool_outputs.py,sha256=ev6LY7fJ0H2xAJn9f5ww28c8Kpom2SYc2FbvFcoN4zg,829
|
41
|
-
nova_trame-0.
|
42
|
-
nova_trame-0.
|
43
|
-
nova_trame-0.
|
44
|
-
nova_trame-0.
|
45
|
-
nova_trame-0.
|
43
|
+
nova_trame-0.26.2.dist-info/LICENSE,sha256=Iu5QiDbwNbREg75iYaxIJ_V-zppuv4QFuBhAW-qiAlM,1061
|
44
|
+
nova_trame-0.26.2.dist-info/METADATA,sha256=mDqDXOmyd5tFAnh7-VtPdiYu1ij7ajZcanzkgC6Ojps,1763
|
45
|
+
nova_trame-0.26.2.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
46
|
+
nova_trame-0.26.2.dist-info/entry_points.txt,sha256=J2AmeSwiTYZ4ZqHHp9HO6v4MaYQTTBPbNh6WtoqOT58,42
|
47
|
+
nova_trame-0.26.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|