sapiopycommons 2025.4.8a473__py3-none-any.whl → 2025.4.9a150__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.
Potentially problematic release.
This version of sapiopycommons might be problematic. Click here for more details.
- sapiopycommons/callbacks/callback_util.py +392 -1262
- sapiopycommons/callbacks/field_builder.py +0 -2
- sapiopycommons/chem/Molecules.py +2 -0
- sapiopycommons/customreport/term_builder.py +1 -1
- sapiopycommons/datatype/attachment_util.py +2 -4
- sapiopycommons/datatype/data_fields.py +1 -23
- sapiopycommons/eln/experiment_handler.py +279 -933
- sapiopycommons/eln/experiment_report_util.py +10 -15
- sapiopycommons/eln/plate_designer.py +59 -159
- sapiopycommons/files/file_bridge.py +0 -76
- sapiopycommons/files/file_bridge_handler.py +110 -325
- sapiopycommons/files/file_data_handler.py +2 -2
- sapiopycommons/files/file_util.py +15 -40
- sapiopycommons/files/file_validator.py +5 -6
- sapiopycommons/files/file_writer.py +1 -1
- sapiopycommons/flowcyto/flow_cyto.py +1 -1
- sapiopycommons/general/accession_service.py +3 -3
- sapiopycommons/general/aliases.py +28 -51
- sapiopycommons/general/audit_log.py +2 -2
- sapiopycommons/general/custom_report_util.py +1 -24
- sapiopycommons/general/exceptions.py +2 -41
- sapiopycommons/general/popup_util.py +2 -2
- sapiopycommons/multimodal/multimodal.py +0 -1
- sapiopycommons/processtracking/custom_workflow_handler.py +30 -46
- sapiopycommons/recordmodel/record_handler.py +159 -547
- sapiopycommons/rules/eln_rule_handler.py +30 -41
- sapiopycommons/rules/on_save_rule_handler.py +30 -41
- sapiopycommons/webhook/webhook_handlers.py +55 -448
- sapiopycommons/webhook/webservice_handlers.py +2 -2
- {sapiopycommons-2025.4.8a473.dist-info → sapiopycommons-2025.4.9a150.dist-info}/METADATA +1 -1
- sapiopycommons-2025.4.9a150.dist-info/RECORD +59 -0
- sapiopycommons/customreport/auto_pagers.py +0 -281
- sapiopycommons/eln/experiment_cache.py +0 -173
- sapiopycommons/eln/experiment_step_factory.py +0 -474
- sapiopycommons/eln/experiment_tags.py +0 -7
- sapiopycommons/eln/step_creation.py +0 -235
- sapiopycommons/general/data_structure_util.py +0 -115
- sapiopycommons/general/directive_util.py +0 -86
- sapiopycommons/samples/aliquot.py +0 -48
- sapiopycommons-2025.4.8a473.dist-info/RECORD +0 -67
- {sapiopycommons-2025.4.8a473.dist-info → sapiopycommons-2025.4.9a150.dist-info}/WHEEL +0 -0
- {sapiopycommons-2025.4.8a473.dist-info → sapiopycommons-2025.4.9a150.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,35 +1,19 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import time
|
|
4
4
|
from collections.abc import Mapping, Iterable
|
|
5
|
-
from typing import TypeAlias
|
|
6
5
|
from weakref import WeakValueDictionary
|
|
7
6
|
|
|
8
|
-
from sapiopycommons.eln.experiment_cache import ExperimentCacheManager
|
|
9
|
-
from sapiopycommons.eln.experiment_report_util import ExperimentReportUtil
|
|
10
|
-
from sapiopycommons.general.aliases import AliasUtil, SapioRecord, ExperimentIdentifier, UserIdentifier, \
|
|
11
|
-
DataTypeIdentifier, RecordModel, ExperimentEntryIdentifier, TabIdentifier
|
|
12
|
-
from sapiopycommons.general.exceptions import SapioException
|
|
13
|
-
from sapiopycommons.general.time_util import TimeUtil
|
|
14
|
-
from sapiopycommons.recordmodel.record_handler import RecordHandler
|
|
15
7
|
from sapiopylib.rest.DataMgmtService import DataMgmtServer
|
|
16
8
|
from sapiopylib.rest.ELNService import ElnManager
|
|
17
9
|
from sapiopylib.rest.User import SapioUser
|
|
18
10
|
from sapiopylib.rest.pojo.DataRecord import DataRecord
|
|
19
|
-
from sapiopylib.rest.pojo.eln.ElnEntryPosition import ElnEntryPosition
|
|
20
11
|
from sapiopylib.rest.pojo.eln.ElnExperiment import ElnExperiment, TemplateExperimentQueryPojo, ElnTemplate, \
|
|
21
12
|
InitializeNotebookExperimentPojo, ElnExperimentUpdateCriteria
|
|
22
|
-
from sapiopylib.rest.pojo.eln.ExperimentEntry import ExperimentEntry
|
|
23
|
-
|
|
24
|
-
ExperimentTempDataEntry
|
|
25
|
-
from sapiopylib.rest.pojo.eln.ExperimentEntryCriteria import AbstractElnEntryUpdateCriteria, \
|
|
26
|
-
ElnTableEntryUpdateCriteria, ElnFormEntryUpdateCriteria, ElnAttachmentEntryUpdateCriteria, \
|
|
27
|
-
ElnPluginEntryUpdateCriteria, ElnDashboardEntryUpdateCriteria, ElnTextEntryUpdateCriteria, \
|
|
28
|
-
ElnTempDataEntryUpdateCriteria
|
|
13
|
+
from sapiopylib.rest.pojo.eln.ExperimentEntry import ExperimentEntry
|
|
14
|
+
from sapiopylib.rest.pojo.eln.ExperimentEntryCriteria import AbstractElnEntryUpdateCriteria
|
|
29
15
|
from sapiopylib.rest.pojo.eln.SapioELNEnums import ExperimentEntryStatus, ElnExperimentStatus, ElnEntryType, \
|
|
30
16
|
ElnBaseDataType
|
|
31
|
-
from sapiopylib.rest.pojo.eln.eln_headings import ElnExperimentTab, ElnExperimentTabAddCriteria
|
|
32
|
-
from sapiopylib.rest.pojo.eln.protocol_template import ProtocolTemplateInfo
|
|
33
17
|
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
34
18
|
from sapiopylib.rest.pojo.webhook.WebhookDirective import ElnExperimentDirective
|
|
35
19
|
from sapiopylib.rest.pojo.webhook.WebhookResult import SapioWebhookResult
|
|
@@ -39,7 +23,12 @@ from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelInst
|
|
|
39
23
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
|
|
40
24
|
from sapiopylib.rest.utils.recordmodel.properties import Child
|
|
41
25
|
|
|
42
|
-
|
|
26
|
+
from sapiopycommons.eln.experiment_report_util import ExperimentReportUtil
|
|
27
|
+
from sapiopycommons.general.aliases import AliasUtil, SapioRecord, ExperimentIdentifier, UserIdentifier, \
|
|
28
|
+
DataTypeIdentifier, RecordModel
|
|
29
|
+
from sapiopycommons.general.exceptions import SapioException
|
|
30
|
+
|
|
31
|
+
Step = str | ElnEntryStep
|
|
43
32
|
"""An object representing an identifier to an ElnEntryStep. May be either the name of the step or the ElnEntryStep
|
|
44
33
|
itself."""
|
|
45
34
|
|
|
@@ -48,74 +37,53 @@ itself."""
|
|
|
48
37
|
class ExperimentHandler:
|
|
49
38
|
user: SapioUser
|
|
50
39
|
context: SapioWebhookContext | None
|
|
51
|
-
"""The context that this handler is working from
|
|
40
|
+
"""The context that this handler is working from."""
|
|
52
41
|
|
|
53
|
-
# CR-47485: Made variables protected instead of private.
|
|
54
42
|
# Basic experiment info from the context.
|
|
55
|
-
|
|
43
|
+
__eln_exp: ElnExperiment
|
|
56
44
|
"""The ELN experiment from the context."""
|
|
57
|
-
|
|
45
|
+
__protocol: ElnExperimentProtocol
|
|
58
46
|
"""The ELN experiment as a protocol."""
|
|
59
|
-
|
|
47
|
+
__exp_id: int
|
|
60
48
|
"""The ID of this experiment's notebook. Used for making update webservice calls."""
|
|
61
49
|
|
|
62
50
|
# Managers.
|
|
63
|
-
|
|
51
|
+
__eln_man: ElnManager
|
|
64
52
|
"""The ELN manager. Used for updating the experiment and its steps."""
|
|
65
|
-
|
|
66
|
-
"""The experiment cache manager. Used for caching experiment-related information."""
|
|
67
|
-
_inst_man: RecordModelInstanceManager
|
|
53
|
+
__inst_man: RecordModelInstanceManager
|
|
68
54
|
"""The record model instance manager. Used for wrapping the data records of a step as record models."""
|
|
69
|
-
_rec_handler: RecordHandler
|
|
70
|
-
"""The record handler. Also used for wrapping the data records of a step as record models."""
|
|
71
55
|
|
|
72
56
|
# Only a fraction of the information about the current experiment exists in the context. Much information requires
|
|
73
57
|
# additional queries to obtain, but may also be repeatedly accessed. In such cases, cache the information after it
|
|
74
58
|
# has been requested so that the user doesn't need to worry about caching it themselves.
|
|
75
59
|
# CR-46341: Replace class variables with instance variables.
|
|
76
|
-
|
|
60
|
+
__exp_record: DataRecord | None
|
|
77
61
|
"""The data record for this experiment. Only cached when first accessed."""
|
|
78
|
-
|
|
62
|
+
__exp_template: ElnTemplate | None
|
|
79
63
|
"""The template for this experiment. Only cached when first accessed."""
|
|
80
|
-
|
|
64
|
+
__exp_options: dict[str, str]
|
|
81
65
|
"""Experiment options for this experiment. Only cached when first accessed."""
|
|
82
66
|
|
|
83
|
-
|
|
67
|
+
__queried_all_steps: bool
|
|
84
68
|
"""Whether this ExperimentHandler has queried the system for all steps in the experiment."""
|
|
85
|
-
|
|
86
|
-
"""
|
|
87
|
-
|
|
88
|
-
"""Steps from this experiment by their name. All steps are cached the first time any individual step is accessed."""
|
|
89
|
-
_steps_by_id: dict[int, ElnEntryStep]
|
|
90
|
-
"""Steps from this experiment by their ID. All steps are cached the first time any individual step is accessed."""
|
|
91
|
-
_step_options: dict[int, dict[str, str]]
|
|
69
|
+
__steps: dict[str, ElnEntryStep]
|
|
70
|
+
"""Steps from this experiment. All steps are cached the first time any individual step is accessed."""
|
|
71
|
+
__step_options: dict[int, dict[str, str]]
|
|
92
72
|
"""Entry options for each step in this experiment. All entry options are cached the first time any individual step's
|
|
93
73
|
options are queried. The cache is updated whenever the entry options for a step are changed by this handler."""
|
|
94
74
|
|
|
95
|
-
_step_updates: dict[int, AbstractElnEntryUpdateCriteria]
|
|
96
|
-
"""A dictionary of entry updates that have been made by this handler. Used to batch update entries."""
|
|
97
|
-
|
|
98
|
-
_queried_all_tabs: bool
|
|
99
|
-
"""Whether this ExperimentHandler has queried the system for all tabs in the experiment."""
|
|
100
|
-
_tabs: list[ElnExperimentTab]
|
|
101
|
-
"""The sorted tabs for this experiment. Only cached when first accessed."""
|
|
102
|
-
_tabs_by_id: dict[int, ElnExperimentTab]
|
|
103
|
-
"""The tabs for this experiment by their ID. Only cached when first accessed."""
|
|
104
|
-
_tabs_by_name: dict[str, ElnExperimentTab]
|
|
105
|
-
"""The tabs for this experiment by their name. Only cached when first accessed."""
|
|
106
|
-
|
|
107
75
|
# Constants
|
|
108
|
-
|
|
76
|
+
__ENTRY_COMPLETE_STATUSES = [ExperimentEntryStatus.Completed, ExperimentEntryStatus.CompletedApproved]
|
|
109
77
|
"""The set of statuses that an ELN entry could have and be considered completed/submitted."""
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
78
|
+
__ENTRY_LOCKED_STATUSES = [ExperimentEntryStatus.Completed, ExperimentEntryStatus.CompletedApproved,
|
|
79
|
+
ExperimentEntryStatus.Disabled, ExperimentEntryStatus.LockedAwaitingApproval,
|
|
80
|
+
ExperimentEntryStatus.LockedRejected]
|
|
113
81
|
"""The set of statuses that an ELN entry could have and be considered locked."""
|
|
114
|
-
|
|
82
|
+
__EXPERIMENT_COMPLETE_STATUSES = [ElnExperimentStatus.Completed, ElnExperimentStatus.CompletedApproved]
|
|
115
83
|
"""The set of statuses that an ELN experiment could have and be considered completed."""
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
84
|
+
__EXPERIMENT_LOCKED_STATUSES = [ElnExperimentStatus.Completed, ElnExperimentStatus.CompletedApproved,
|
|
85
|
+
ElnExperimentStatus.LockedRejected, ElnExperimentStatus.LockedAwaitingApproval,
|
|
86
|
+
ElnExperimentStatus.Canceled]
|
|
119
87
|
"""The set of statuses that an ELN experiment could have and be considered locked."""
|
|
120
88
|
|
|
121
89
|
__instances: WeakValueDictionary[str, ExperimentHandler] = WeakValueDictionary()
|
|
@@ -155,44 +123,27 @@ class ExperimentHandler:
|
|
|
155
123
|
experiment = param_results[2]
|
|
156
124
|
|
|
157
125
|
# Get the basic information about this experiment that already exists in the context and is often used.
|
|
158
|
-
self.
|
|
159
|
-
self.
|
|
160
|
-
self.
|
|
126
|
+
self.__eln_exp = experiment
|
|
127
|
+
self.__protocol = ElnExperimentProtocol(experiment, self.user)
|
|
128
|
+
self.__exp_id = self.__protocol.get_id()
|
|
161
129
|
|
|
162
130
|
# Grab various managers that may be used.
|
|
163
|
-
self.
|
|
164
|
-
self.
|
|
165
|
-
self._inst_man = RecordModelManager(self.user).instance_manager
|
|
166
|
-
self._rec_handler = RecordHandler(self.user)
|
|
131
|
+
self.__eln_man = DataMgmtServer.get_eln_manager(self.user)
|
|
132
|
+
self.__inst_man = RecordModelManager(self.user).instance_manager
|
|
167
133
|
|
|
168
134
|
# Create empty caches to fill when necessary.
|
|
169
|
-
self.
|
|
170
|
-
self.
|
|
171
|
-
self._steps_by_id = {}
|
|
172
|
-
self._steps = []
|
|
173
|
-
self._step_options = {}
|
|
174
|
-
self._step_updates = {}
|
|
175
|
-
|
|
176
|
-
self._tabs = []
|
|
177
|
-
self._tabs_by_id = {}
|
|
178
|
-
self._tabs_by_name = {}
|
|
179
|
-
|
|
180
|
-
self._queried_all_tabs = False
|
|
181
|
-
|
|
135
|
+
self.__steps = {}
|
|
136
|
+
self.__step_options = {}
|
|
182
137
|
# CR-46330: Cache any experiment entry information that might already exist in the context.
|
|
138
|
+
self.__queried_all_steps = False
|
|
183
139
|
# We can only trust the entries in the context if the experiment that this handler is for is the same as the
|
|
184
140
|
# one from the context.
|
|
185
141
|
if self.context is not None and self.context.eln_experiment == experiment:
|
|
186
|
-
cache_steps: list[ElnEntryStep] = []
|
|
187
142
|
if self.context.experiment_entry is not None:
|
|
188
|
-
|
|
143
|
+
self.__steps.update({self.context.active_step.get_name(): self.context.active_step})
|
|
189
144
|
if self.context.experiment_entry_list is not None:
|
|
190
145
|
for entry in self.context.experiment_entry_list:
|
|
191
|
-
|
|
192
|
-
for step in cache_steps:
|
|
193
|
-
self._steps.append(step)
|
|
194
|
-
self._steps_by_name.update({step.get_name(): step})
|
|
195
|
-
self._steps_by_id.update({step.get_id(): step})
|
|
146
|
+
self.__steps.update({entry.entry_name: ElnEntryStep(self.__protocol, entry)})
|
|
196
147
|
|
|
197
148
|
@staticmethod
|
|
198
149
|
def __parse_params(context: UserIdentifier, experiment: ExperimentIdentifier | SapioRecord | None = None) \
|
|
@@ -229,99 +180,10 @@ class ExperimentHandler:
|
|
|
229
180
|
if not experiment:
|
|
230
181
|
raise SapioException(f"No experiment with record ID {record_id} located in the system.")
|
|
231
182
|
if experiment is None:
|
|
232
|
-
raise SapioException("Cannot initialize ExperimentHandler. No ELN Experiment found in the provided "
|
|
233
|
-
"parameters.")
|
|
183
|
+
raise SapioException("Cannot initialize ExperimentHandler. No ELN Experiment found in the provided parameters.")
|
|
234
184
|
|
|
235
185
|
return user, context, experiment
|
|
236
186
|
|
|
237
|
-
@property
|
|
238
|
-
def protocol(self) -> ElnExperimentProtocol:
|
|
239
|
-
"""
|
|
240
|
-
The ELN experiment that this handler is for as a protocol object.
|
|
241
|
-
"""
|
|
242
|
-
return self._protocol
|
|
243
|
-
|
|
244
|
-
# CR-47485: Add methods for clearing and updating the caches of this ExperimentHandler.
|
|
245
|
-
def clear_all_caches(self) -> None:
|
|
246
|
-
"""
|
|
247
|
-
Clear all caches that this ExperimentHandler uses.
|
|
248
|
-
"""
|
|
249
|
-
self.clear_step_caches()
|
|
250
|
-
self.clear_experiment_caches()
|
|
251
|
-
self.clear_tab_caches()
|
|
252
|
-
|
|
253
|
-
def clear_step_caches(self) -> None:
|
|
254
|
-
"""
|
|
255
|
-
Clear the step caches that this ExperimentHandler uses.
|
|
256
|
-
"""
|
|
257
|
-
self._queried_all_steps = False
|
|
258
|
-
self._steps.clear()
|
|
259
|
-
self._steps_by_name.clear()
|
|
260
|
-
self._steps_by_id.clear()
|
|
261
|
-
self._step_options.clear()
|
|
262
|
-
self._step_updates.clear()
|
|
263
|
-
|
|
264
|
-
def clear_experiment_caches(self) -> None:
|
|
265
|
-
"""
|
|
266
|
-
Clear the experiment information caches that this ExperimentHandler uses.
|
|
267
|
-
"""
|
|
268
|
-
self._exp_record = None
|
|
269
|
-
self._exp_template = None
|
|
270
|
-
self._exp_options = {}
|
|
271
|
-
|
|
272
|
-
def clear_tab_caches(self) -> None:
|
|
273
|
-
"""
|
|
274
|
-
Clear the tab caches that this ExperimentHandler uses.
|
|
275
|
-
"""
|
|
276
|
-
self._queried_all_tabs = False
|
|
277
|
-
self._tabs.clear()
|
|
278
|
-
self._tabs_by_id.clear()
|
|
279
|
-
self._tabs_by_name.clear()
|
|
280
|
-
|
|
281
|
-
def add_entry_to_caches(self, entry: ExperimentEntry | ElnEntryStep) -> ElnEntryStep:
|
|
282
|
-
"""
|
|
283
|
-
Add the given entry to the cache of steps for this experiment. This is necessary in order for certain methods to
|
|
284
|
-
work. You should only need to do this if you have created a new entry in your code using a method outside
|
|
285
|
-
of this ExperimentHandler.
|
|
286
|
-
|
|
287
|
-
:param entry: The entry to add to the cache.
|
|
288
|
-
:return: The entry that was added to the cache as an ElnEntryStep.
|
|
289
|
-
"""
|
|
290
|
-
if isinstance(entry, ExperimentEntry):
|
|
291
|
-
entry = ElnEntryStep(self._protocol, entry)
|
|
292
|
-
self._steps.append(entry)
|
|
293
|
-
self._steps_by_name.update({entry.get_name(): entry})
|
|
294
|
-
self._steps_by_id.update({entry.get_id(): entry})
|
|
295
|
-
# Skipping the options cache. The get_step_options method will update the cache when necessary.
|
|
296
|
-
return entry
|
|
297
|
-
|
|
298
|
-
def add_entries_to_caches(self, entries: list[ExperimentEntry | ElnEntryStep]) -> list[ElnEntryStep]:
|
|
299
|
-
"""
|
|
300
|
-
Add the given entries to the cache of steps for this experiment. This is necessary in order for certain methods
|
|
301
|
-
to work. You should only need to do this if you have created a new entry in your code using a method outside
|
|
302
|
-
of this ExperimentHandler.
|
|
303
|
-
|
|
304
|
-
:param entries: The entries to add to the cache.
|
|
305
|
-
:return: The entries that were added to the cache as ElnEntrySteps.
|
|
306
|
-
"""
|
|
307
|
-
new_entries: list[ElnEntryStep] = []
|
|
308
|
-
for entry in entries:
|
|
309
|
-
new_entries.append(self.add_entry_to_caches(entry))
|
|
310
|
-
return new_entries
|
|
311
|
-
|
|
312
|
-
def add_tab_to_cache(self, tab: ElnExperimentTab) -> None:
|
|
313
|
-
"""
|
|
314
|
-
Add the given tab to the cache of tabs for this experiment. This is necessary in order for certain methods
|
|
315
|
-
to work properly. You should only need to do this if you have created a new tab in your code using a method
|
|
316
|
-
outside of this ExperimentHandler.
|
|
317
|
-
|
|
318
|
-
:param tab: The tab to add to the cache.
|
|
319
|
-
"""
|
|
320
|
-
self._tabs.append(tab)
|
|
321
|
-
self._tabs.sort(key=lambda t: t.tab_order)
|
|
322
|
-
self._tabs_by_id[tab.tab_id] = tab
|
|
323
|
-
self._tabs_by_name[tab.tab_name] = tab
|
|
324
|
-
|
|
325
187
|
# FR-46495: Split the creation of the experiment in launch_experiment into a create_experiment function.
|
|
326
188
|
@staticmethod
|
|
327
189
|
def create_experiment(context: SapioWebhookContext,
|
|
@@ -349,10 +211,22 @@ class ExperimentHandler:
|
|
|
349
211
|
:param active_templates_only: Whether only active templates should be queried for.
|
|
350
212
|
:return: The newly created experiment.
|
|
351
213
|
"""
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
214
|
+
template_query = TemplateExperimentQueryPojo(latest_version_only=(template_version is None),
|
|
215
|
+
active_templates_only=active_templates_only)
|
|
216
|
+
templates: list[ElnTemplate] = context.eln_manager.get_template_experiment_list(template_query)
|
|
217
|
+
launch_template: ElnTemplate | None = None
|
|
218
|
+
for template in templates:
|
|
219
|
+
if template.template_name != template_name:
|
|
220
|
+
continue
|
|
221
|
+
if template_version is not None and template.template_version != template_version:
|
|
222
|
+
continue
|
|
223
|
+
launch_template = template
|
|
224
|
+
break
|
|
225
|
+
if launch_template is None:
|
|
226
|
+
raise SapioException(f"No template with the name \"{template_name}\"" +
|
|
227
|
+
("" if template_version is None else f" and the version {template_version}") +
|
|
228
|
+
f" found.")
|
|
229
|
+
|
|
356
230
|
if experiment_name is None:
|
|
357
231
|
experiment_name: str = launch_template.display_name
|
|
358
232
|
if parent_record is not None:
|
|
@@ -365,8 +239,7 @@ class ExperimentHandler:
|
|
|
365
239
|
template_name: str,
|
|
366
240
|
experiment_name: str | None = None,
|
|
367
241
|
parent_record: SapioRecord | None = None, *,
|
|
368
|
-
template_version: int | None = None,
|
|
369
|
-
active_templates_only: bool = True) -> SapioWebhookResult:
|
|
242
|
+
template_version: int | None = None, active_templates_only: bool = True) -> SapioWebhookResult:
|
|
370
243
|
"""
|
|
371
244
|
Create a SapioWebhookResult that, when returned by a webhook handler, sends the user to a new experiment of the
|
|
372
245
|
input template name.
|
|
@@ -402,24 +275,24 @@ class ExperimentHandler:
|
|
|
402
275
|
when the experiment template doesn't exist.
|
|
403
276
|
:return: This experiment's template. None if it has no template.
|
|
404
277
|
"""
|
|
405
|
-
template_id: int | None = self.
|
|
278
|
+
template_id: int | None = self.__eln_exp.template_id
|
|
406
279
|
if template_id is None:
|
|
407
|
-
self.
|
|
280
|
+
self.__exp_template = None
|
|
408
281
|
if exception_on_none:
|
|
409
|
-
raise SapioException(f"Experiment with ID {self.
|
|
282
|
+
raise SapioException(f"Experiment with ID {self.__exp_id} has no template ID.")
|
|
410
283
|
return None
|
|
411
284
|
|
|
412
|
-
if not hasattr(self, "
|
|
285
|
+
if not hasattr(self, "_ExperimentHandler__exp_template"):
|
|
413
286
|
# PR-46504: Allow inactive and non-latest version templates to be queried.
|
|
414
287
|
query = TemplateExperimentQueryPojo(template_id_white_list=[template_id],
|
|
415
288
|
active_templates_only=False,
|
|
416
289
|
latest_version_only=False)
|
|
417
|
-
templates: list[ElnTemplate] = self.
|
|
290
|
+
templates: list[ElnTemplate] = self.__eln_man.get_template_experiment_list(query)
|
|
418
291
|
# PR-46504: Set the exp_template to None if there are no results.
|
|
419
|
-
self.
|
|
420
|
-
if self.
|
|
421
|
-
raise SapioException(f"Experiment template not found for experiment with ID {self.
|
|
422
|
-
return self.
|
|
292
|
+
self.__exp_template = templates[0] if templates else None
|
|
293
|
+
if self.__exp_template is None and exception_on_none:
|
|
294
|
+
raise SapioException(f"Experiment template not found for experiment with ID {self.__exp_id}.")
|
|
295
|
+
return self.__exp_template
|
|
423
296
|
|
|
424
297
|
# CR-46104: Change get_template_name to behave like NotebookProtocolImpl.getTemplateName (i.e. first see if the
|
|
425
298
|
# experiment template exists, and if not, see if the experiment record exists, instead of only checking the
|
|
@@ -436,18 +309,18 @@ class ExperimentHandler:
|
|
|
436
309
|
when the template name doesn't exist.
|
|
437
310
|
:return: The template name of the current experiment. None if it has no template name.
|
|
438
311
|
"""
|
|
439
|
-
if not hasattr(self, "
|
|
312
|
+
if not hasattr(self, "_ExperimentHandler__exp_template"):
|
|
440
313
|
self.get_experiment_template(False)
|
|
441
|
-
if self.
|
|
314
|
+
if self.__exp_template is None and not hasattr(self, "_ExperimentHandler__exp_record"):
|
|
442
315
|
self.get_experiment_record(False)
|
|
443
316
|
|
|
444
317
|
name: str | None = None
|
|
445
|
-
if self.
|
|
446
|
-
name = self.
|
|
447
|
-
elif self.
|
|
448
|
-
name = self.
|
|
318
|
+
if self.__exp_template is not None:
|
|
319
|
+
name = self.__exp_template.template_name
|
|
320
|
+
elif self.__exp_record is not None:
|
|
321
|
+
name = self.__exp_record.get_field_value("TemplateExperimentName")
|
|
449
322
|
if name is None and exception_on_none:
|
|
450
|
-
raise SapioException(f"Template name not found for experiment with ID {self.
|
|
323
|
+
raise SapioException(f"Template name not found for experiment with ID {self.__exp_id}.")
|
|
451
324
|
return name
|
|
452
325
|
|
|
453
326
|
def get_experiment_record(self, exception_on_none: bool = True) -> DataRecord | None:
|
|
@@ -458,23 +331,21 @@ class ExperimentHandler:
|
|
|
458
331
|
when the experiment record doesn't exist.
|
|
459
332
|
:return: The data record for this experiment. None if it has no record.
|
|
460
333
|
"""
|
|
461
|
-
if not hasattr(self, "
|
|
462
|
-
self.
|
|
463
|
-
if self.
|
|
464
|
-
raise SapioException(f"Experiment record not found for experiment with ID {self.
|
|
465
|
-
return self.
|
|
334
|
+
if not hasattr(self, "_ExperimentHandler__exp_record"):
|
|
335
|
+
self.__exp_record = self.__protocol.get_record()
|
|
336
|
+
if self.__exp_record is None and exception_on_none:
|
|
337
|
+
raise SapioException(f"Experiment record not found for experiment with ID {self.__exp_id}.")
|
|
338
|
+
return self.__exp_record
|
|
466
339
|
|
|
467
|
-
|
|
468
|
-
def get_experiment_model(self, wrapper_type: type[WrappedType] | None = None) -> WrappedType | PyRecordModel:
|
|
340
|
+
def get_experiment_model(self, wrapper_type: type[WrappedType]) -> WrappedType:
|
|
469
341
|
"""
|
|
470
342
|
Query for the data record of this experiment and wrap it as a record model with the given wrapper.
|
|
471
343
|
The returned record is cached by the ExperimentHandler.
|
|
472
344
|
|
|
473
|
-
:param wrapper_type: The record model wrapper to use.
|
|
474
|
-
PyRecordModel instead of a WrappedRecordModel.
|
|
345
|
+
:param wrapper_type: The record model wrapper to use.
|
|
475
346
|
:return: The record model for this experiment.
|
|
476
347
|
"""
|
|
477
|
-
return self.
|
|
348
|
+
return self.__inst_man.add_existing_record_of_type(self.get_experiment_record(), wrapper_type)
|
|
478
349
|
|
|
479
350
|
def update_experiment(self,
|
|
480
351
|
experiment_name: str | None = None,
|
|
@@ -496,14 +367,14 @@ class ExperimentHandler:
|
|
|
496
367
|
criteria.new_experiment_name = experiment_name
|
|
497
368
|
criteria.new_experiment_status = experiment_status
|
|
498
369
|
criteria.experiment_option_map = experiment_option_map
|
|
499
|
-
self.
|
|
370
|
+
self.__eln_man.update_notebook_experiment(self.__exp_id, criteria)
|
|
500
371
|
|
|
501
372
|
if experiment_name is not None:
|
|
502
|
-
self.
|
|
373
|
+
self.__eln_exp.notebook_experiment_name = experiment_name
|
|
503
374
|
if experiment_status is not None:
|
|
504
|
-
self.
|
|
375
|
+
self.__eln_exp.notebook_experiment_status = experiment_status
|
|
505
376
|
if experiment_option_map is not None:
|
|
506
|
-
self.
|
|
377
|
+
self.__exp_options = experiment_option_map
|
|
507
378
|
|
|
508
379
|
def get_experiment_option(self, option: str) -> str:
|
|
509
380
|
"""
|
|
@@ -528,10 +399,7 @@ class ExperimentHandler:
|
|
|
528
399
|
|
|
529
400
|
:return: The map of options for this experiment.
|
|
530
401
|
"""
|
|
531
|
-
|
|
532
|
-
return self._exp_options
|
|
533
|
-
self._exp_options = self._eln_man.get_notebook_experiment_options(self._exp_id)
|
|
534
|
-
return self._exp_options
|
|
402
|
+
return self.__get_experiment_options()
|
|
535
403
|
|
|
536
404
|
def add_experiment_options(self, mapping: Mapping[str, str]) -> None:
|
|
537
405
|
"""
|
|
@@ -556,7 +424,7 @@ class ExperimentHandler:
|
|
|
556
424
|
|
|
557
425
|
:return: True if the experiment's status is Completed or CompletedApproved. False otherwise.
|
|
558
426
|
"""
|
|
559
|
-
return self.
|
|
427
|
+
return self.__eln_exp.notebook_experiment_status in self.__EXPERIMENT_COMPLETE_STATUSES
|
|
560
428
|
|
|
561
429
|
def experiment_is_canceled(self) -> bool:
|
|
562
430
|
"""
|
|
@@ -564,7 +432,7 @@ class ExperimentHandler:
|
|
|
564
432
|
|
|
565
433
|
:return: True if the experiment's status is Canceled. False otherwise.
|
|
566
434
|
"""
|
|
567
|
-
return self.
|
|
435
|
+
return self.__eln_exp.notebook_experiment_status == ElnExperimentStatus.Canceled
|
|
568
436
|
|
|
569
437
|
def experiment_is_locked(self) -> bool:
|
|
570
438
|
"""
|
|
@@ -573,34 +441,29 @@ class ExperimentHandler:
|
|
|
573
441
|
:return: True if the experiment's status is Completed, CompletedApproved, Canceled, LockedAwaitingApproval,
|
|
574
442
|
or LockedRejected. False otherwise.
|
|
575
443
|
"""
|
|
576
|
-
return self.
|
|
444
|
+
return self.__eln_exp.notebook_experiment_status in self.__EXPERIMENT_LOCKED_STATUSES
|
|
577
445
|
|
|
578
446
|
def complete_experiment(self) -> None:
|
|
579
447
|
"""
|
|
580
448
|
Set the experiment's status to Completed. Makes a webservice call to update the experiment. Checks if the
|
|
581
449
|
experiment is already completed, and does nothing if so.
|
|
582
|
-
|
|
583
|
-
NOTE: This will cause the usual process tracking logic to run as if you'd clicked the "Complete Experiment"
|
|
584
|
-
toolbar button. This includes moving the in process samples forward to the next step in the process.
|
|
585
450
|
"""
|
|
586
451
|
if not self.experiment_is_complete():
|
|
587
|
-
self.
|
|
588
|
-
self.
|
|
452
|
+
self.__protocol.complete_protocol()
|
|
453
|
+
self.__eln_exp.notebook_experiment_status = ElnExperimentStatus.Completed
|
|
589
454
|
|
|
590
455
|
def cancel_experiment(self) -> None:
|
|
591
456
|
"""
|
|
592
457
|
Set the experiment's status to Canceled. Makes a webservice call to update the experiment. Checks if the
|
|
593
458
|
experiment is already canceled, and does nothing if so.
|
|
594
459
|
|
|
595
|
-
NOTE: This will
|
|
596
|
-
toolbar button
|
|
597
|
-
|
|
598
|
-
On version 24.12 and earlier, this was not the case, as the process tracking logic was tied to the button
|
|
599
|
-
instead of being on the experiment status change.
|
|
460
|
+
NOTE: This will not run the usual logic around canceling an experiment that you'd see if canceling the
|
|
461
|
+
experiment using the "Cancel Experiment" toolbar button, such as moving in process samples back to the queue,
|
|
462
|
+
as those changes are tied to the button instead of being on the experiment status change.
|
|
600
463
|
"""
|
|
601
464
|
if not self.experiment_is_canceled():
|
|
602
|
-
self.
|
|
603
|
-
self.
|
|
465
|
+
self.__protocol.cancel_protocol()
|
|
466
|
+
self.__eln_exp.notebook_experiment_status = ElnExperimentStatus.Canceled
|
|
604
467
|
|
|
605
468
|
def step_exists(self, step_name: str) -> bool:
|
|
606
469
|
"""
|
|
@@ -626,14 +489,14 @@ class ExperimentHandler:
|
|
|
626
489
|
"""
|
|
627
490
|
return all([x is not None for x in self.get_steps(step_names, False)])
|
|
628
491
|
|
|
629
|
-
def get_step(self, step_name: str
|
|
492
|
+
def get_step(self, step_name: str, exception_on_none: bool = True) -> ElnEntryStep | None:
|
|
630
493
|
"""
|
|
631
494
|
Get the step of the given name from the experiment.
|
|
632
495
|
|
|
633
496
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
634
497
|
list of steps in the experiment and caches them.
|
|
635
498
|
|
|
636
|
-
:param step_name: The name
|
|
499
|
+
:param step_name: The name for the step to return.
|
|
637
500
|
:param exception_on_none: If false, returns None if the entry can't be found. If true, raises an exception
|
|
638
501
|
when the named entry doesn't exist in the experiment.
|
|
639
502
|
:return: An ElnEntrySteps matching the provided name. If there is no match and no exception is to be thrown,
|
|
@@ -641,32 +504,31 @@ class ExperimentHandler:
|
|
|
641
504
|
"""
|
|
642
505
|
return self.get_steps([step_name], exception_on_none)[0]
|
|
643
506
|
|
|
644
|
-
def get_steps(self, step_names: Iterable[str
|
|
507
|
+
def get_steps(self, step_names: Iterable[str], exception_on_none: bool = True) -> list[ElnEntryStep | None]:
|
|
645
508
|
"""
|
|
646
509
|
Get a list of steps of the given names from the experiment, sorted in the same order as the names are provided.
|
|
647
510
|
|
|
648
511
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
649
512
|
list of steps in the experiment and caches them.
|
|
650
513
|
|
|
651
|
-
:param step_names: A list of names
|
|
514
|
+
:param step_names: A list of names for the entries to return and the order to return them in.
|
|
652
515
|
:param exception_on_none: If false, returns None for entries that can't be found. If true, raises an exception
|
|
653
516
|
when the named entry doesn't exist in the experiment.
|
|
654
517
|
:return: A list of ElnEntrySteps matching the provided names in the order they were provided in. If there is no
|
|
655
518
|
match for a given step and no exception is to be thrown, returns None for that step.
|
|
656
519
|
"""
|
|
657
520
|
ret_list: list[ElnEntryStep | None] = []
|
|
658
|
-
for
|
|
521
|
+
for name in step_names:
|
|
659
522
|
# If we haven't queried the system for all steps in the experiment yet, then the reason that a step is
|
|
660
523
|
# missing may be because it wasn't in the webhook context. Therefore, query all steps and then check
|
|
661
524
|
# if the step name is still missing from the experiment before potentially throwing an exception.
|
|
662
|
-
if self.
|
|
663
|
-
self.
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
step: ElnEntryStep = self._steps_by_id.get(step_id)
|
|
525
|
+
if self.__queried_all_steps is False and name not in self.__steps:
|
|
526
|
+
self.__queried_all_steps = True
|
|
527
|
+
self.__steps.update({step.get_name(): step for step in self.__protocol.get_sorted_step_list()})
|
|
528
|
+
|
|
529
|
+
step: ElnEntryStep = self.__steps.get(name)
|
|
668
530
|
if step is None and exception_on_none is True:
|
|
669
|
-
raise SapioException(f"ElnEntryStep of name \"{
|
|
531
|
+
raise SapioException(f"ElnEntryStep of name \"{name}\" not found in experiment with ID {self.__exp_id}.")
|
|
670
532
|
ret_list.append(step)
|
|
671
533
|
return ret_list
|
|
672
534
|
|
|
@@ -680,35 +542,15 @@ class ExperimentHandler:
|
|
|
680
542
|
a data type name or wrapper is given, only returns entries that match that data type name or wrapper.
|
|
681
543
|
:return: Every entry in the experiment in order of appearance that match the provided data type, if any.
|
|
682
544
|
"""
|
|
683
|
-
if self.
|
|
684
|
-
self.
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
def sort_steps(step: ElnEntryStep) -> tuple:
|
|
688
|
-
entry = step.eln_entry
|
|
689
|
-
tab_order: int = self.get_tab(entry.notebook_experiment_tab_id).tab_order
|
|
690
|
-
entry_order: int = entry.order
|
|
691
|
-
column_order: int = entry.column_order
|
|
692
|
-
return tab_order, entry_order, column_order
|
|
693
|
-
|
|
694
|
-
self._steps.sort(key=sort_steps)
|
|
695
|
-
all_steps: list[ElnEntryStep] = self._steps
|
|
545
|
+
if self.__queried_all_steps is False:
|
|
546
|
+
self.__queried_all_steps = True
|
|
547
|
+
self.__steps.update({step.get_name(): step for step in self.__protocol.get_sorted_step_list()})
|
|
548
|
+
all_steps: list[ElnEntryStep] = self.__protocol.get_sorted_step_list()
|
|
696
549
|
if data_type is None:
|
|
697
550
|
return all_steps
|
|
698
551
|
data_type: str = AliasUtil.to_data_type_name(data_type)
|
|
699
552
|
return [x for x in all_steps if data_type in x.get_data_type_names()]
|
|
700
553
|
|
|
701
|
-
def _query_all_steps(self) -> None:
|
|
702
|
-
"""
|
|
703
|
-
Query the system for every step in the experiment and cache them.
|
|
704
|
-
"""
|
|
705
|
-
self._queried_all_steps = True
|
|
706
|
-
self._protocol.invalidate()
|
|
707
|
-
self._steps = self._protocol.get_sorted_step_list()
|
|
708
|
-
for step in self._steps:
|
|
709
|
-
self._steps_by_name[step.get_name()] = step
|
|
710
|
-
self._steps_by_id[step.get_id()] = step
|
|
711
|
-
|
|
712
554
|
def get_step_by_option(self, key: str, value: str | None = None) -> ElnEntryStep:
|
|
713
555
|
"""
|
|
714
556
|
Retrieve the step in this experiment that contains an entry option with the provided key and value.
|
|
@@ -757,8 +599,7 @@ class ExperimentHandler:
|
|
|
757
599
|
"""
|
|
758
600
|
return self.__to_eln_step(step).get_records()
|
|
759
601
|
|
|
760
|
-
def get_step_models(self, step: Step, wrapper_type: type[WrappedType]
|
|
761
|
-
-> list[WrappedType] | list[PyRecordModel]:
|
|
602
|
+
def get_step_models(self, step: Step, wrapper_type: type[WrappedType]) -> list[WrappedType]:
|
|
762
603
|
"""
|
|
763
604
|
Query for the data records for the given step and wrap them as record models with the given type. The returned
|
|
764
605
|
records are not cached by the ExperimentHandler.
|
|
@@ -770,11 +611,10 @@ class ExperimentHandler:
|
|
|
770
611
|
The step to get the data records for.
|
|
771
612
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
772
613
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
773
|
-
:param wrapper_type: The record model wrapper to use.
|
|
774
|
-
PyRecordModels instead of WrappedRecordModels.
|
|
614
|
+
:param wrapper_type: The record model wrapper to use.
|
|
775
615
|
:return: The record models for the given step.
|
|
776
616
|
"""
|
|
777
|
-
return self.
|
|
617
|
+
return self.__inst_man.add_existing_records_of_type(self.get_step_records(step), wrapper_type)
|
|
778
618
|
|
|
779
619
|
def add_step_records(self, step: Step, records: Iterable[SapioRecord]) -> None:
|
|
780
620
|
"""
|
|
@@ -797,9 +637,8 @@ class ExperimentHandler:
|
|
|
797
637
|
return
|
|
798
638
|
dt: str = AliasUtil.to_singular_data_type_name(records)
|
|
799
639
|
if ElnBaseDataType.is_base_data_type(dt):
|
|
800
|
-
raise SapioException(f"{dt} is an ELN data type. This function call has no effect on ELN data types.
|
|
801
|
-
f"
|
|
802
|
-
f"with the matching data type name.")
|
|
640
|
+
raise SapioException(f"{dt} is an ELN data type. This function call has no effect on ELN data types. "
|
|
641
|
+
f"Use add_eln_rows or add_sample_details instead.")
|
|
803
642
|
if dt != step.get_data_type_names()[0]:
|
|
804
643
|
raise SapioException(f"Cannot add {dt} records to entry {step.get_name()} of type "
|
|
805
644
|
f"{step.get_data_type_names()[0]}.")
|
|
@@ -825,9 +664,8 @@ class ExperimentHandler:
|
|
|
825
664
|
return
|
|
826
665
|
dt: str = AliasUtil.to_singular_data_type_name(records)
|
|
827
666
|
if ElnBaseDataType.is_base_data_type(dt):
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
return
|
|
667
|
+
raise SapioException(f"{dt} is an ELN data type. This function call has no effect on ELN data types. "
|
|
668
|
+
f"Use remove_eln_rows or remove_sample_details instead.")
|
|
831
669
|
if dt != step.get_data_type_names()[0]:
|
|
832
670
|
raise SapioException(f"Cannot remove {dt} records from entry {step.get_name()} of type "
|
|
833
671
|
f"{step.get_data_type_names()[0]}.")
|
|
@@ -856,14 +694,9 @@ class ExperimentHandler:
|
|
|
856
694
|
step = self.__to_eln_step(step)
|
|
857
695
|
if records:
|
|
858
696
|
dt: str = AliasUtil.to_singular_data_type_name(records)
|
|
859
|
-
# CR-47532: Add set_step_records support for Experiment Detail and Sample Detail entries.
|
|
860
697
|
if ElnBaseDataType.is_base_data_type(dt):
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
if record not in records:
|
|
864
|
-
remove_rows.append(record)
|
|
865
|
-
self.remove_eln_rows(step, remove_rows)
|
|
866
|
-
return
|
|
698
|
+
raise SapioException(f"{dt} is an ELN data type. This function call has no effect on ELN data types. "
|
|
699
|
+
f"Use add_eln_rows or add_sample_details instead.")
|
|
867
700
|
if dt != step.get_data_type_names()[0]:
|
|
868
701
|
raise SapioException(f"Cannot set {dt} records for entry {step.get_name()} of type "
|
|
869
702
|
f"{step.get_data_type_names()[0]}.")
|
|
@@ -885,30 +718,10 @@ class ExperimentHandler:
|
|
|
885
718
|
The record may be provided as either a DataRecord, PyRecordModel, or WrappedRecordModel.
|
|
886
719
|
"""
|
|
887
720
|
self.set_step_records(step, [record])
|
|
888
|
-
step = self.__to_eln_step(step)
|
|
889
|
-
if isinstance(step.eln_entry, ExperimentFormEntry):
|
|
890
|
-
step.eln_entry.record_id = AliasUtil.to_data_record(record).record_id
|
|
891
721
|
|
|
892
722
|
# FR-46496 - Provide functions for adding and removing rows from an ELN data type entry.
|
|
893
|
-
def add_eln_row(self, step: Step, wrapper_type: type[WrappedType] | None = None) -> WrappedType | PyRecordModel:
|
|
894
|
-
"""
|
|
895
|
-
Add a row to an ELNExperimentDetail or ELNSampleDetail table entry. The row will not appear in the system
|
|
896
|
-
until a record manager store and commit has occurred.
|
|
897
|
-
|
|
898
|
-
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
899
|
-
list of steps in the experiment and caches them.
|
|
900
|
-
|
|
901
|
-
:param step:
|
|
902
|
-
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
903
|
-
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
904
|
-
:param wrapper_type: Optionally wrap the ELN data type in a record model wrapper. If not provided, returns
|
|
905
|
-
an unwrapped PyRecordModel.
|
|
906
|
-
:return: The newly created row.
|
|
907
|
-
"""
|
|
908
|
-
return self.add_eln_rows(step, 1, wrapper_type)[0]
|
|
909
|
-
|
|
910
723
|
def add_eln_rows(self, step: Step, count: int, wrapper_type: type[WrappedType] | None = None) \
|
|
911
|
-
-> list[
|
|
724
|
+
-> list[PyRecordModel | WrappedType]:
|
|
912
725
|
"""
|
|
913
726
|
Add rows to an ELNExperimentDetail or ELNSampleDetail table entry. The rows will not appear in the system
|
|
914
727
|
until a record manager store and commit has occurred.
|
|
@@ -930,69 +743,15 @@ class ExperimentHandler:
|
|
|
930
743
|
dt: str = step.get_data_type_names()[0]
|
|
931
744
|
if not ElnBaseDataType.is_eln_type(dt):
|
|
932
745
|
raise SapioException("The provided step is not an ELN data type entry.")
|
|
933
|
-
records: list[PyRecordModel] = self.
|
|
934
|
-
if wrapper_type:
|
|
935
|
-
return self._inst_man.wrap_list(records, wrapper_type)
|
|
936
|
-
return records
|
|
937
|
-
|
|
938
|
-
def add_sample_detail(self, step: Step, sample: RecordModel,
|
|
939
|
-
wrapper_type: type[WrappedType] | None = None) \
|
|
940
|
-
-> WrappedType | PyRecordModel:
|
|
941
|
-
"""
|
|
942
|
-
Add a sample detail to a sample detail entry while relating it to the input sample record.
|
|
943
|
-
|
|
944
|
-
:param step:
|
|
945
|
-
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
946
|
-
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
947
|
-
:param sample: The sample record to add the sample detail to.
|
|
948
|
-
:param wrapper_type: Optionally wrap the sample detail in a record model wrapper. If not provided, returns
|
|
949
|
-
an unwrapped PyRecordModel.
|
|
950
|
-
:return: The newly created sample detail.
|
|
951
|
-
"""
|
|
952
|
-
return self.add_sample_details(step, [sample], wrapper_type)[0]
|
|
953
|
-
|
|
954
|
-
def add_sample_details(self, step: Step, samples: Iterable[RecordModel],
|
|
955
|
-
wrapper_type: type[WrappedType] | None = None) \
|
|
956
|
-
-> list[WrappedType] | list[PyRecordModel]:
|
|
957
|
-
"""
|
|
958
|
-
Add sample details to a sample details entry while relating them to the input sample records.
|
|
959
|
-
|
|
960
|
-
:param step:
|
|
961
|
-
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
962
|
-
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
963
|
-
:param samples: The sample records to add the sample details to.
|
|
964
|
-
:param wrapper_type: Optionally wrap the sample details in a record model wrapper. If not provided, returns
|
|
965
|
-
an unwrapped PyRecordModel.
|
|
966
|
-
:return: The newly created sample details. The indices of the samples in the input list match the index of the
|
|
967
|
-
sample details in this list that they are related to.
|
|
968
|
-
"""
|
|
969
|
-
step = self.__to_eln_step(step)
|
|
970
|
-
if step.eln_entry.entry_type != ElnEntryType.Table:
|
|
971
|
-
raise SapioException("The provided step is not a table entry.")
|
|
972
|
-
dt: str = step.get_data_type_names()[0]
|
|
973
|
-
if not ElnBaseDataType.is_eln_type(dt) or ElnBaseDataType.get_base_type(dt) != ElnBaseDataType.SAMPLE_DETAIL:
|
|
974
|
-
raise SapioException("The provided step is not an ELNSampleDetail entry.")
|
|
975
|
-
records: list[PyRecordModel] = []
|
|
976
|
-
for sample in samples:
|
|
977
|
-
if sample.data_type_name != "Sample":
|
|
978
|
-
raise SapioException(f"Received a {sample.data_type_name} record when Sample records were expected.")
|
|
979
|
-
detail: PyRecordModel = sample.add(Child.create_by_name(dt))
|
|
980
|
-
detail.set_field_values({
|
|
981
|
-
"SampleId": sample.get_field_value("SampleId"),
|
|
982
|
-
"OtherSampleId": sample.get_field_value("OtherSampleId")
|
|
983
|
-
})
|
|
984
|
-
records.append(detail)
|
|
746
|
+
records: list[PyRecordModel] = self.__inst_man.add_new_records(dt, count)
|
|
985
747
|
if wrapper_type:
|
|
986
|
-
return self.
|
|
748
|
+
return self.__inst_man.wrap_list(records, wrapper_type)
|
|
987
749
|
return records
|
|
988
750
|
|
|
989
|
-
def
|
|
751
|
+
def add_eln_row(self, step: Step, wrapper_type: type[WrappedType] | None = None) -> PyRecordModel | WrappedType:
|
|
990
752
|
"""
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
table entry is equivalent to deleting the records for the rows.
|
|
994
|
-
|
|
995
|
-
The row will not be deleted in the system until a record manager store and commit has occurred.
|
|
753
|
+
Add a row to an ELNExperimentDetail or ELNSampleDetail table entry. The row will not appear in the system
|
|
754
|
+
until a record manager store and commit has occurred.
|
|
996
755
|
|
|
997
756
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
998
757
|
list of steps in the experiment and caches them.
|
|
@@ -1000,13 +759,13 @@ class ExperimentHandler:
|
|
|
1000
759
|
:param step:
|
|
1001
760
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
1002
761
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1003
|
-
:param record
|
|
1004
|
-
|
|
1005
|
-
|
|
762
|
+
:param wrapper_type: Optionally wrap the ELN data type in a record model wrapper. If not provided, returns
|
|
763
|
+
an unwrapped PyRecordModel.
|
|
764
|
+
:return: The newly created row.
|
|
1006
765
|
"""
|
|
1007
|
-
self.
|
|
766
|
+
return self.add_eln_rows(step, 1, wrapper_type)[0]
|
|
1008
767
|
|
|
1009
|
-
def remove_eln_rows(self, step: Step, records:
|
|
768
|
+
def remove_eln_rows(self, step: Step, records: list[SapioRecord]) -> None:
|
|
1010
769
|
"""
|
|
1011
770
|
Remove rows from an ELNExperimentDetail or ELNSampleDetail table entry. ELN data type table entries display all
|
|
1012
771
|
records in the system that match the entry's data type. This means that removing rows from an ELN data type
|
|
@@ -1043,11 +802,64 @@ class ExperimentHandler:
|
|
|
1043
802
|
else:
|
|
1044
803
|
record.delete()
|
|
1045
804
|
if data_records:
|
|
1046
|
-
record_models: list[PyRecordModel] = self.
|
|
805
|
+
record_models: list[PyRecordModel] = self.__inst_man.add_existing_records(data_records)
|
|
1047
806
|
for record in record_models:
|
|
1048
807
|
record.delete()
|
|
1049
808
|
|
|
1050
|
-
|
|
809
|
+
def remove_eln_row(self, step: Step, record: SapioRecord) -> None:
|
|
810
|
+
"""
|
|
811
|
+
Remove a row from an ELNExperimentDetail or ELNSampleDetail table entry. ELN data type table entries display all
|
|
812
|
+
records in the system that match the entry's data type. This means that removing rows from an ELN data type
|
|
813
|
+
table entry is equivalent to deleting the records for the rows.
|
|
814
|
+
|
|
815
|
+
The row will not be deleted in the system until a record manager store and commit has occurred.
|
|
816
|
+
|
|
817
|
+
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
818
|
+
list of steps in the experiment and caches them.
|
|
819
|
+
|
|
820
|
+
:param step:
|
|
821
|
+
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
822
|
+
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
823
|
+
:param record:
|
|
824
|
+
The record to remove from the given step.
|
|
825
|
+
The record may be provided as either a DataRecord, PyRecordModel, or WrappedRecordModel.
|
|
826
|
+
"""
|
|
827
|
+
self.remove_eln_rows(step, [record])
|
|
828
|
+
|
|
829
|
+
def add_sample_details(self, step: Step, samples: list[RecordModel], wrapper_type: type[WrappedType]) \
|
|
830
|
+
-> list[PyRecordModel | WrappedType]:
|
|
831
|
+
"""
|
|
832
|
+
Add sample details to a sample details entry while relating them to the input sample records.
|
|
833
|
+
|
|
834
|
+
:param step:
|
|
835
|
+
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
836
|
+
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
837
|
+
:param samples: The sample records to add the sample details to.
|
|
838
|
+
:param wrapper_type: Optionally wrap the sample details in a record model wrapper. If not provided, returns
|
|
839
|
+
an unwrapped PyRecordModel.
|
|
840
|
+
:return: The newly created sample details. The indices of the samples in the input list match the index of the
|
|
841
|
+
sample details in this list that they are related to.
|
|
842
|
+
"""
|
|
843
|
+
step = self.__to_eln_step(step)
|
|
844
|
+
if step.eln_entry.entry_type != ElnEntryType.Table:
|
|
845
|
+
raise SapioException("The provided step is not a table entry.")
|
|
846
|
+
dt: str = step.get_data_type_names()[0]
|
|
847
|
+
if not ElnBaseDataType.is_eln_type(dt) or ElnBaseDataType.get_base_type(dt) != ElnBaseDataType.SAMPLE_DETAIL:
|
|
848
|
+
raise SapioException("The provided step is not an ELNSampleDetail entry.")
|
|
849
|
+
records: list[PyRecordModel] = []
|
|
850
|
+
for sample in samples:
|
|
851
|
+
if sample.data_type_name != "Sample":
|
|
852
|
+
raise SapioException(f"Received a {sample.data_type_name} record when Sample records were expected.")
|
|
853
|
+
detail: PyRecordModel = sample.add(Child.create_by_name(dt))
|
|
854
|
+
detail.set_field_values({
|
|
855
|
+
"SampleId": sample.get_field_value("SampleId"),
|
|
856
|
+
"OtherSampleId": sample.get_field_value("OtherSampleId")
|
|
857
|
+
})
|
|
858
|
+
records.append(detail)
|
|
859
|
+
if wrapper_type:
|
|
860
|
+
return self.__inst_man.wrap_list(records, wrapper_type)
|
|
861
|
+
return records
|
|
862
|
+
|
|
1051
863
|
def update_step(self, step: Step,
|
|
1052
864
|
entry_name: str | None = None,
|
|
1053
865
|
related_entry_set: Iterable[int] | None = None,
|
|
@@ -1114,227 +926,8 @@ class ExperimentHandler:
|
|
|
1114
926
|
If you wish to add options to the existing map of options that an entry has, use the
|
|
1115
927
|
add_step_options method.
|
|
1116
928
|
"""
|
|
1117
|
-
# FR-47468: Deprecating this since the parameters are ordered. The new method requires keyword parameters, so
|
|
1118
|
-
# that we can add new parameters wherever we want without breaking existing code.
|
|
1119
|
-
warnings.warn("Update step is deprecated. Use force_entry_update_params instead.",
|
|
1120
|
-
DeprecationWarning)
|
|
1121
|
-
self.force_step_update_params(step,
|
|
1122
|
-
entry_name=entry_name,
|
|
1123
|
-
related_entry_set=related_entry_set,
|
|
1124
|
-
dependency_set=dependency_set,
|
|
1125
|
-
entry_status=entry_status,
|
|
1126
|
-
order=order,
|
|
1127
|
-
description=description,
|
|
1128
|
-
requires_grabber_plugin=requires_grabber_plugin,
|
|
1129
|
-
is_initialization_required=is_initialization_required,
|
|
1130
|
-
notebook_experiment_tab_id=notebook_experiment_tab_id,
|
|
1131
|
-
entry_height=entry_height,
|
|
1132
|
-
column_order=column_order,
|
|
1133
|
-
column_span=column_span,
|
|
1134
|
-
is_removable=is_removable,
|
|
1135
|
-
is_renamable=is_renamable,
|
|
1136
|
-
source_entry_id=source_entry_id,
|
|
1137
|
-
clear_source_entry_id=clear_source_entry_id,
|
|
1138
|
-
is_hidden=is_hidden,
|
|
1139
|
-
is_static_view=is_static_View,
|
|
1140
|
-
is_shown_in_template=is_shown_in_template,
|
|
1141
|
-
template_item_fulfilled_timestamp=template_item_fulfilled_timestamp,
|
|
1142
|
-
clear_template_item_fulfilled_timestamp=clear_template_item_fulfilled_timestamp,
|
|
1143
|
-
entry_options_map=entry_options_map)
|
|
1144
|
-
|
|
1145
|
-
# FR-47468: Some functions that can help with entry updates.
|
|
1146
|
-
def force_step_update_params(self, step: Step, *,
|
|
1147
|
-
entry_name: str | None = None,
|
|
1148
|
-
related_entry_set: Iterable[int] | None = None,
|
|
1149
|
-
dependency_set: Iterable[int] | None = None,
|
|
1150
|
-
entry_status: ExperimentEntryStatus | None = None,
|
|
1151
|
-
order: int | None = None,
|
|
1152
|
-
description: str | None = None,
|
|
1153
|
-
requires_grabber_plugin: bool | None = None,
|
|
1154
|
-
is_initialization_required: bool | None = None,
|
|
1155
|
-
notebook_experiment_tab_id: int | None = None,
|
|
1156
|
-
entry_height: int | None = None,
|
|
1157
|
-
column_order: int | None = None,
|
|
1158
|
-
column_span: int | None = None,
|
|
1159
|
-
is_removable: bool | None = None,
|
|
1160
|
-
is_renamable: bool | None = None,
|
|
1161
|
-
source_entry_id: int | None = None,
|
|
1162
|
-
clear_source_entry_id: bool | None = None,
|
|
1163
|
-
is_hidden: bool | None = None,
|
|
1164
|
-
is_static_view: bool | None = None,
|
|
1165
|
-
is_shown_in_template: bool | None = None,
|
|
1166
|
-
template_item_fulfilled_timestamp: int | None = None,
|
|
1167
|
-
clear_template_item_fulfilled_timestamp: bool | None = None,
|
|
1168
|
-
entry_options_map: dict[str, str] | None = None) -> None:
|
|
1169
|
-
"""
|
|
1170
|
-
Immediately sent an update to an entry in this experiment. All changes will be reflected by the ExperimentEntry
|
|
1171
|
-
of the Step that is being updated.
|
|
1172
|
-
|
|
1173
|
-
Consider using store_step_update and commit_step_updates instead if the update does not need to be immediate.
|
|
1174
|
-
|
|
1175
|
-
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
1176
|
-
list of steps in the experiment and caches them.
|
|
1177
|
-
|
|
1178
|
-
:param step:
|
|
1179
|
-
The entry step to update.
|
|
1180
|
-
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
1181
|
-
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1182
|
-
:param entry_name: The new name of this entry.
|
|
1183
|
-
:param related_entry_set: The new set of entry IDs for the entries that are related (implicitly dependent) to
|
|
1184
|
-
this entry. Completely overwrites the existing related entries.
|
|
1185
|
-
:param dependency_set: The new set of entry IDs for the entries that are dependent (explicitly dependent) on
|
|
1186
|
-
this entry. Completely overwrites the existing dependent entries.
|
|
1187
|
-
:param entry_status: The new status of this entry.
|
|
1188
|
-
:param order: The row order of this entry in its tab.
|
|
1189
|
-
:param description: The new description of this entry.
|
|
1190
|
-
:param requires_grabber_plugin: Whether this entry's initialization is handled by a grabber plugin. If true,
|
|
1191
|
-
then is_initialization_required is forced to true by the server.
|
|
1192
|
-
:param is_initialization_required: Whether the user is required to manually initialize this entry.
|
|
1193
|
-
:param notebook_experiment_tab_id: The ID of the tab that this entry should appear on.
|
|
1194
|
-
:param entry_height: The height of this entry.
|
|
1195
|
-
:param column_order: The column order of this entry.
|
|
1196
|
-
:param column_span: How many columns this entry spans.
|
|
1197
|
-
:param is_removable: Whether this entry can be removed by the user.
|
|
1198
|
-
:param is_renamable: Whether this entry can be renamed by the user.
|
|
1199
|
-
:param source_entry_id: The ID of this entry from its template.
|
|
1200
|
-
:param clear_source_entry_id: True if the source entry ID should be cleared.
|
|
1201
|
-
:param is_hidden: Whether this entry is hidden from the user.
|
|
1202
|
-
:param is_static_view: Whether this entry is static. Static entries are uneditable and shared across all
|
|
1203
|
-
experiments of the same template.
|
|
1204
|
-
:param is_shown_in_template: Whether this entry is saved to and shown in the experiment's template.
|
|
1205
|
-
:param template_item_fulfilled_timestamp: A timestamp in milliseconds for when this entry was initialized.
|
|
1206
|
-
:param clear_template_item_fulfilled_timestamp: True if the template item fulfilled timestamp should be cleared,
|
|
1207
|
-
uninitializing the entry.
|
|
1208
|
-
:param entry_options_map:
|
|
1209
|
-
The new map of options for this entry. Completely overwrites the existing options map.
|
|
1210
|
-
Any changes to the entry options will update this ExperimentHandler's cache of entry options.
|
|
1211
|
-
If you wish to add options to the existing map of options that an entry has, use the
|
|
1212
|
-
add_step_options method.
|
|
1213
|
-
"""
|
|
1214
|
-
update = self._criteria_from_params(step,
|
|
1215
|
-
entry_name=entry_name,
|
|
1216
|
-
related_entry_set=related_entry_set,
|
|
1217
|
-
dependency_set=dependency_set,
|
|
1218
|
-
entry_status=entry_status,
|
|
1219
|
-
order=order,
|
|
1220
|
-
description=description,
|
|
1221
|
-
requires_grabber_plugin=requires_grabber_plugin,
|
|
1222
|
-
is_initialization_required=is_initialization_required,
|
|
1223
|
-
notebook_experiment_tab_id=notebook_experiment_tab_id,
|
|
1224
|
-
entry_height=entry_height,
|
|
1225
|
-
column_order=column_order,
|
|
1226
|
-
column_span=column_span,
|
|
1227
|
-
is_removable=is_removable,
|
|
1228
|
-
is_renamable=is_renamable,
|
|
1229
|
-
source_entry_id=source_entry_id,
|
|
1230
|
-
clear_source_entry_id=clear_source_entry_id,
|
|
1231
|
-
is_hidden=is_hidden,
|
|
1232
|
-
is_static_view=is_static_view,
|
|
1233
|
-
is_shown_in_template=is_shown_in_template,
|
|
1234
|
-
template_item_fulfilled_timestamp=template_item_fulfilled_timestamp,
|
|
1235
|
-
clear_template_item_fulfilled_timestamp=clear_template_item_fulfilled_timestamp,
|
|
1236
|
-
entry_options_map=entry_options_map)
|
|
1237
|
-
self.force_step_update(step, update)
|
|
1238
|
-
|
|
1239
|
-
def force_step_update(self, step: Step, update: AbstractElnEntryUpdateCriteria) -> None:
|
|
1240
|
-
"""
|
|
1241
|
-
Immediately sent an update to an entry in this experiment. All changes will be reflected by the ExperimentEntry
|
|
1242
|
-
of the Step that is being updated.
|
|
1243
|
-
|
|
1244
|
-
Consider using store_step_update and commit_step_updates instead if the update does not need to be immediate.
|
|
1245
|
-
|
|
1246
|
-
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
1247
|
-
list of steps in the experiment and caches them.
|
|
1248
|
-
|
|
1249
|
-
:param step: The step to update.
|
|
1250
|
-
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
1251
|
-
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1252
|
-
:param update: The update to make to the step.
|
|
1253
|
-
"""
|
|
1254
|
-
step = self.__to_eln_step(step)
|
|
1255
|
-
self._eln_man.update_experiment_entry(self._exp_id, step.get_id(), update)
|
|
1256
|
-
self._update_entry_details(step, update)
|
|
1257
|
-
|
|
1258
|
-
def store_step_update(self, step: Step, update: AbstractElnEntryUpdateCriteria) -> None:
|
|
1259
|
-
"""
|
|
1260
|
-
Store updates to be made to an entry in this experiment. The updates are not committed until
|
|
1261
|
-
commit_entry_updates is called.
|
|
1262
|
-
|
|
1263
|
-
If the same entry is updated multiple times before committing, the latest update will be merged on top of the
|
|
1264
|
-
previous updates; where the new update and old update conflict, the new update will take precedence.
|
|
1265
|
-
|
|
1266
|
-
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
1267
|
-
list of steps in the experiment and caches them.
|
|
1268
|
-
|
|
1269
|
-
:param step: The step to update.
|
|
1270
|
-
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
1271
|
-
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1272
|
-
:param update: The update to make to the step.
|
|
1273
|
-
"""
|
|
1274
|
-
step = self.__to_eln_step(step)
|
|
1275
|
-
if step.eln_entry.entry_type != update.entry_type:
|
|
1276
|
-
raise SapioException(f"The provided step and update criteria are not of the same entry type. "
|
|
1277
|
-
f"The step is of type {step.eln_entry.entry_type} and the update criteria is of type "
|
|
1278
|
-
f"{update.entry_type}.")
|
|
1279
|
-
if step.get_id() in self._step_updates:
|
|
1280
|
-
self._merge_updates(update, self._step_updates[step.get_id()])
|
|
1281
|
-
else:
|
|
1282
|
-
self._step_updates[step.get_id()] = update
|
|
1283
|
-
|
|
1284
|
-
def store_step_updates(self, updates: dict[Step, AbstractElnEntryUpdateCriteria]) -> None:
|
|
1285
|
-
"""
|
|
1286
|
-
Store updates to be made to multiple entries in this experiment. The updates are not committed until
|
|
1287
|
-
commit_entry_updates is called.
|
|
1288
|
-
|
|
1289
|
-
If the same entry is updated multiple times before committing, the latest update will be merged on top of the
|
|
1290
|
-
previous updates; where the new update and old update conflict, the new update will take precedence.
|
|
1291
|
-
|
|
1292
|
-
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
1293
|
-
list of steps in the experiment and caches them.
|
|
1294
|
-
|
|
1295
|
-
:param updates: A dictionary of steps and their respective updates.
|
|
1296
|
-
"""
|
|
1297
|
-
for step, update in updates.items():
|
|
1298
|
-
self.store_step_update(step, update)
|
|
1299
|
-
|
|
1300
|
-
def commit_step_updates(self) -> None:
|
|
1301
|
-
"""
|
|
1302
|
-
Commit all the stored updates to the entries in this experiment. The updates are made in the order that they
|
|
1303
|
-
were stored.
|
|
1304
|
-
"""
|
|
1305
|
-
self._eln_man.update_experiment_entries(self._exp_id, self._step_updates)
|
|
1306
|
-
for step_id, criteria in self._step_updates.items():
|
|
1307
|
-
self._update_entry_details(self._steps_by_id[step_id], criteria)
|
|
1308
|
-
self._step_updates.clear()
|
|
1309
|
-
|
|
1310
|
-
def _criteria_from_params(self, step: Step, *,
|
|
1311
|
-
entry_name: str | None = None,
|
|
1312
|
-
related_entry_set: Iterable[int] | None = None,
|
|
1313
|
-
dependency_set: Iterable[int] | None = None,
|
|
1314
|
-
entry_status: ExperimentEntryStatus | None = None,
|
|
1315
|
-
order: int | None = None,
|
|
1316
|
-
description: str | None = None,
|
|
1317
|
-
requires_grabber_plugin: bool | None = None,
|
|
1318
|
-
is_initialization_required: bool | None = None,
|
|
1319
|
-
notebook_experiment_tab_id: int | None = None,
|
|
1320
|
-
entry_height: int | None = None,
|
|
1321
|
-
column_order: int | None = None,
|
|
1322
|
-
column_span: int | None = None,
|
|
1323
|
-
is_removable: bool | None = None,
|
|
1324
|
-
is_renamable: bool | None = None,
|
|
1325
|
-
source_entry_id: int | None = None,
|
|
1326
|
-
clear_source_entry_id: bool | None = None,
|
|
1327
|
-
is_hidden: bool | None = None,
|
|
1328
|
-
is_static_view: bool | None = None,
|
|
1329
|
-
is_shown_in_template: bool | None = None,
|
|
1330
|
-
template_item_fulfilled_timestamp: int | None = None,
|
|
1331
|
-
clear_template_item_fulfilled_timestamp: bool | None = None,
|
|
1332
|
-
entry_options_map: dict[str, str] | None = None) -> AbstractElnEntryUpdateCriteria:
|
|
1333
|
-
"""
|
|
1334
|
-
Create an abstract update criteria object from the provided parameters for the given step.
|
|
1335
|
-
"""
|
|
1336
929
|
step: ElnEntryStep = self.__to_eln_step(step)
|
|
1337
|
-
|
|
930
|
+
criteria = AbstractElnEntryUpdateCriteria(step.eln_entry.entry_type)
|
|
1338
931
|
|
|
1339
932
|
# These two variables could be iterables that aren't lists. Convert them to plain
|
|
1340
933
|
# lists, since that's what the update criteria is expecting.
|
|
@@ -1343,154 +936,81 @@ class ExperimentHandler:
|
|
|
1343
936
|
if dependency_set is not None:
|
|
1344
937
|
dependency_set = list(dependency_set)
|
|
1345
938
|
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
def _merge_updates(new_update: AbstractElnEntryUpdateCriteria, old_update: AbstractElnEntryUpdateCriteria) -> None:
|
|
1373
|
-
"""
|
|
1374
|
-
Merge the new update criteria onto the old update criteria. The new update will take precedence where there
|
|
1375
|
-
are conflicts.
|
|
1376
|
-
"""
|
|
1377
|
-
for key, value in new_update.__dict__.items():
|
|
1378
|
-
if value is not None:
|
|
1379
|
-
old_update.__dict__[key] = value
|
|
1380
|
-
|
|
1381
|
-
def _update_entry_details(self, step: Step, update: AbstractElnEntryUpdateCriteria) -> None:
|
|
1382
|
-
"""
|
|
1383
|
-
Update the cached information for this entry in case it's needed by the caller after updating.
|
|
1384
|
-
"""
|
|
939
|
+
criteria.entry_name = entry_name
|
|
940
|
+
criteria.related_entry_set = related_entry_set
|
|
941
|
+
criteria.dependency_set = dependency_set
|
|
942
|
+
criteria.entry_status = entry_status
|
|
943
|
+
criteria.order = order
|
|
944
|
+
criteria.description = description
|
|
945
|
+
criteria.requires_grabber_plugin = requires_grabber_plugin
|
|
946
|
+
criteria.is_initialization_required = is_initialization_required
|
|
947
|
+
criteria.notebook_experiment_tab_id = notebook_experiment_tab_id
|
|
948
|
+
criteria.entry_height = entry_height
|
|
949
|
+
criteria.column_order = column_order
|
|
950
|
+
criteria.column_span = column_span
|
|
951
|
+
criteria.is_removable = is_removable
|
|
952
|
+
criteria.is_renamable = is_renamable
|
|
953
|
+
criteria.source_entry_id = source_entry_id
|
|
954
|
+
criteria.clear_source_entry_id = clear_source_entry_id
|
|
955
|
+
criteria.is_hidden = is_hidden
|
|
956
|
+
criteria.is_static_View = is_static_View
|
|
957
|
+
criteria.is_shown_in_template = is_shown_in_template
|
|
958
|
+
criteria.template_item_fulfilled_timestamp = template_item_fulfilled_timestamp
|
|
959
|
+
criteria.clear_template_item_fulfilled_timestamp = clear_template_item_fulfilled_timestamp
|
|
960
|
+
criteria.entry_options_map = entry_options_map
|
|
961
|
+
|
|
962
|
+
self.__eln_man.update_experiment_entry(self.__exp_id, step.get_id(), criteria)
|
|
963
|
+
|
|
964
|
+
# Update the cached information for this entry in case it's needed by the caller after updating.
|
|
1385
965
|
entry: ExperimentEntry = step.eln_entry
|
|
1386
|
-
if
|
|
966
|
+
if entry_name is not None:
|
|
1387
967
|
# PR-46477 - Ensure that the previous name of the updated entry already existed in the cache.
|
|
1388
|
-
if entry.entry_name in self.
|
|
1389
|
-
self.
|
|
1390
|
-
entry.entry_name =
|
|
1391
|
-
self.
|
|
1392
|
-
if
|
|
1393
|
-
entry.related_entry_id_set =
|
|
1394
|
-
if
|
|
1395
|
-
entry.dependency_set =
|
|
1396
|
-
if
|
|
1397
|
-
entry.entry_status =
|
|
1398
|
-
if
|
|
1399
|
-
entry.order =
|
|
1400
|
-
if
|
|
1401
|
-
entry.description =
|
|
1402
|
-
if
|
|
1403
|
-
entry.requires_grabber_plugin =
|
|
1404
|
-
if
|
|
1405
|
-
entry.is_initialization_required =
|
|
1406
|
-
if
|
|
1407
|
-
entry.notebook_experiment_tab_id =
|
|
1408
|
-
if
|
|
1409
|
-
entry.entry_height =
|
|
1410
|
-
if
|
|
1411
|
-
entry.column_order =
|
|
1412
|
-
if
|
|
1413
|
-
entry.column_span =
|
|
1414
|
-
if
|
|
1415
|
-
entry.is_removable =
|
|
1416
|
-
if
|
|
1417
|
-
entry.is_renamable =
|
|
1418
|
-
if
|
|
1419
|
-
entry.source_entry_id =
|
|
1420
|
-
if
|
|
968
|
+
if entry.entry_name in self.__steps:
|
|
969
|
+
self.__steps.pop(entry.entry_name)
|
|
970
|
+
entry.entry_name = entry_name
|
|
971
|
+
self.__steps.update({entry_name: step})
|
|
972
|
+
if related_entry_set is not None:
|
|
973
|
+
entry.related_entry_id_set = related_entry_set
|
|
974
|
+
if dependency_set is not None:
|
|
975
|
+
entry.dependency_set = dependency_set
|
|
976
|
+
if entry_status is not None:
|
|
977
|
+
entry.entry_status = entry_status
|
|
978
|
+
if order is not None:
|
|
979
|
+
entry.order = order
|
|
980
|
+
if description is not None:
|
|
981
|
+
entry.description = description
|
|
982
|
+
if requires_grabber_plugin is not None:
|
|
983
|
+
entry.requires_grabber_plugin = requires_grabber_plugin
|
|
984
|
+
if is_initialization_required is not None:
|
|
985
|
+
entry.is_initialization_required = is_initialization_required
|
|
986
|
+
if notebook_experiment_tab_id is not None:
|
|
987
|
+
entry.notebook_experiment_tab_id = notebook_experiment_tab_id
|
|
988
|
+
if entry_height is not None:
|
|
989
|
+
entry.entry_height = entry_height
|
|
990
|
+
if column_order is not None:
|
|
991
|
+
entry.column_order = column_order
|
|
992
|
+
if column_span is not None:
|
|
993
|
+
entry.column_span = column_span
|
|
994
|
+
if is_removable is not None:
|
|
995
|
+
entry.is_removable = is_removable
|
|
996
|
+
if is_renamable is not None:
|
|
997
|
+
entry.is_renamable = is_renamable
|
|
998
|
+
if source_entry_id is not None:
|
|
999
|
+
entry.source_entry_id = source_entry_id
|
|
1000
|
+
if clear_source_entry_id is True:
|
|
1421
1001
|
entry.source_entry_id = None
|
|
1422
|
-
if
|
|
1423
|
-
entry.is_hidden =
|
|
1424
|
-
if
|
|
1425
|
-
entry.is_static_View =
|
|
1426
|
-
if
|
|
1427
|
-
entry.is_shown_in_template =
|
|
1428
|
-
if
|
|
1429
|
-
entry.template_item_fulfilled_timestamp =
|
|
1430
|
-
if
|
|
1002
|
+
if is_hidden is not None:
|
|
1003
|
+
entry.is_hidden = is_hidden
|
|
1004
|
+
if is_static_View is not None:
|
|
1005
|
+
entry.is_static_View = is_static_View
|
|
1006
|
+
if is_shown_in_template is not None:
|
|
1007
|
+
entry.is_shown_in_template = is_shown_in_template
|
|
1008
|
+
if template_item_fulfilled_timestamp is not None:
|
|
1009
|
+
entry.template_item_fulfilled_timestamp = template_item_fulfilled_timestamp
|
|
1010
|
+
if clear_template_item_fulfilled_timestamp is True:
|
|
1431
1011
|
entry.template_item_fulfilled_timestamp = None
|
|
1432
|
-
if
|
|
1433
|
-
self.
|
|
1434
|
-
|
|
1435
|
-
if isinstance(entry, ExperimentAttachmentEntry) and isinstance(update, ElnAttachmentEntryUpdateCriteria):
|
|
1436
|
-
if update.entry_attachment_list is not None:
|
|
1437
|
-
entry.entry_attachment_list = update.entry_attachment_list
|
|
1438
|
-
if update.record_id is not None:
|
|
1439
|
-
entry.record_id = update.record_id
|
|
1440
|
-
if update.attachment_name is not None:
|
|
1441
|
-
entry.attachment_name = update.attachment_name
|
|
1442
|
-
elif isinstance(entry, ExperimentDashboardEntry) and isinstance(update, ElnDashboardEntryUpdateCriteria):
|
|
1443
|
-
if update.dashboard_guid is not None:
|
|
1444
|
-
entry.dashboard_guid = update.dashboard_guid
|
|
1445
|
-
if update.dashboard_guid_list is not None:
|
|
1446
|
-
entry.dashboard_guid_list = update.dashboard_guid_list
|
|
1447
|
-
if update.data_source_entry_id is not None:
|
|
1448
|
-
entry.data_source_entry_id = update.data_source_entry_id
|
|
1449
|
-
elif isinstance(entry, ExperimentFormEntry) and isinstance(update, ElnFormEntryUpdateCriteria):
|
|
1450
|
-
if update.record_id is not None:
|
|
1451
|
-
entry.record_id = update.record_id
|
|
1452
|
-
if update.form_name_list is not None:
|
|
1453
|
-
entry.form_name_list = update.form_name_list
|
|
1454
|
-
if update.data_type_layout_name is not None:
|
|
1455
|
-
entry.data_type_layout_name = update.data_type_layout_name
|
|
1456
|
-
if update.field_set_id_list is not None:
|
|
1457
|
-
entry.field_set_id_list = update.field_set_id_list
|
|
1458
|
-
if update.extension_type_list is not None:
|
|
1459
|
-
entry.extension_type_list = update.extension_type_list
|
|
1460
|
-
if update.data_field_name_list is not None:
|
|
1461
|
-
entry.data_field_name_list = update.data_field_name_list
|
|
1462
|
-
if update.is_existing_field_removable is not None:
|
|
1463
|
-
entry.is_existing_field_removable = update.is_existing_field_removable
|
|
1464
|
-
if update.is_field_addable is not None:
|
|
1465
|
-
entry.is_field_addable = update.is_field_addable
|
|
1466
|
-
elif isinstance(entry, ExperimentPluginEntry) and isinstance(update, ElnPluginEntryUpdateCriteria):
|
|
1467
|
-
if update.plugin_name is not None:
|
|
1468
|
-
entry.plugin_name = update.plugin_name
|
|
1469
|
-
if update.provides_template_data is not None:
|
|
1470
|
-
entry.provides_template_data = update.provides_template_data
|
|
1471
|
-
if update.using_template_data is not None:
|
|
1472
|
-
entry.using_template_data = update.using_template_data
|
|
1473
|
-
if isinstance(entry, ExperimentTableEntry) and isinstance(update, ElnTableEntryUpdateCriteria):
|
|
1474
|
-
if update.data_type_layout_name is not None:
|
|
1475
|
-
entry.data_type_layout_name = update.data_type_layout_name
|
|
1476
|
-
if update.extension_type_list is not None:
|
|
1477
|
-
entry.extension_type_list = update.extension_type_list
|
|
1478
|
-
if update.field_set_id_list is not None:
|
|
1479
|
-
entry.field_set_id_list = update.field_set_id_list
|
|
1480
|
-
if update.is_existing_field_removable is not None:
|
|
1481
|
-
entry.is_existing_field_removable = update.is_existing_field_removable
|
|
1482
|
-
if update.is_field_addable is not None:
|
|
1483
|
-
entry.is_field_addable = update.is_field_addable
|
|
1484
|
-
if update.show_key_fields is not None:
|
|
1485
|
-
entry.show_key_fields = update.show_key_fields
|
|
1486
|
-
if update.table_column_list is not None:
|
|
1487
|
-
entry.table_column_list = update.table_column_list
|
|
1488
|
-
elif isinstance(entry, ExperimentTempDataEntry) and isinstance(update, ElnTempDataEntryUpdateCriteria):
|
|
1489
|
-
if update.plugin_path is not None:
|
|
1490
|
-
entry.plugin_path = update.plugin_path
|
|
1491
|
-
elif isinstance(entry, ExperimentTextEntry) and isinstance(update, ElnTextEntryUpdateCriteria):
|
|
1492
|
-
# Text update criteria has no additional fields.
|
|
1493
|
-
pass
|
|
1012
|
+
if entry_options_map is not None:
|
|
1013
|
+
self.__step_options.update({step.get_id(): entry_options_map})
|
|
1494
1014
|
|
|
1495
1015
|
def get_step_option(self, step: Step, option: str) -> str:
|
|
1496
1016
|
"""
|
|
@@ -1530,10 +1050,9 @@ class ExperimentHandler:
|
|
|
1530
1050
|
:return: The map of options for the input step.
|
|
1531
1051
|
"""
|
|
1532
1052
|
step = self.__to_eln_step(step)
|
|
1533
|
-
if step not in self.
|
|
1534
|
-
self.
|
|
1535
|
-
|
|
1536
|
-
return self._step_options[step.get_id()]
|
|
1053
|
+
if step not in self.__step_options:
|
|
1054
|
+
self.__step_options.update(ExperimentReportUtil.get_experiment_entry_options(self.user, self.get_all_steps()))
|
|
1055
|
+
return self.__step_options[step.get_id()]
|
|
1537
1056
|
|
|
1538
1057
|
def add_step_options(self, step: Step, mapping: Mapping[str, str]):
|
|
1539
1058
|
"""
|
|
@@ -1557,7 +1076,7 @@ class ExperimentHandler:
|
|
|
1557
1076
|
"""
|
|
1558
1077
|
options: dict[str, str] = self.get_step_options(step)
|
|
1559
1078
|
options.update(mapping)
|
|
1560
|
-
self.
|
|
1079
|
+
self.update_step(step, entry_options_map=options)
|
|
1561
1080
|
|
|
1562
1081
|
def initialize_step(self, step: Step) -> None:
|
|
1563
1082
|
"""
|
|
@@ -1575,7 +1094,7 @@ class ExperimentHandler:
|
|
|
1575
1094
|
# Avoid unnecessary calls if the step is already initialized.
|
|
1576
1095
|
step: ElnEntryStep = self.__to_eln_step(step)
|
|
1577
1096
|
if step.eln_entry.template_item_fulfilled_timestamp is None:
|
|
1578
|
-
self.
|
|
1097
|
+
self.update_step(step, template_item_fulfilled_timestamp=round(time.time() * 1000))
|
|
1579
1098
|
|
|
1580
1099
|
def uninitialize_step(self, step: Step) -> None:
|
|
1581
1100
|
"""
|
|
@@ -1593,7 +1112,7 @@ class ExperimentHandler:
|
|
|
1593
1112
|
# Avoid unnecessary calls if the step is already uninitialized.
|
|
1594
1113
|
step: ElnEntryStep = self.__to_eln_step(step)
|
|
1595
1114
|
if step.eln_entry.template_item_fulfilled_timestamp is not None:
|
|
1596
|
-
self.
|
|
1115
|
+
self.update_step(step, clear_template_item_fulfilled_timestamp=True)
|
|
1597
1116
|
|
|
1598
1117
|
def complete_step(self, step: Step) -> None:
|
|
1599
1118
|
"""
|
|
@@ -1609,7 +1128,7 @@ class ExperimentHandler:
|
|
|
1609
1128
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1610
1129
|
"""
|
|
1611
1130
|
step = self.__to_eln_step(step)
|
|
1612
|
-
if step.eln_entry.entry_status not in self.
|
|
1131
|
+
if step.eln_entry.entry_status not in self.__ENTRY_COMPLETE_STATUSES:
|
|
1613
1132
|
step.complete_step()
|
|
1614
1133
|
step.eln_entry.entry_status = ExperimentEntryStatus.Completed
|
|
1615
1134
|
|
|
@@ -1627,7 +1146,7 @@ class ExperimentHandler:
|
|
|
1627
1146
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1628
1147
|
"""
|
|
1629
1148
|
step = self.__to_eln_step(step)
|
|
1630
|
-
if step.eln_entry.entry_status in self.
|
|
1149
|
+
if step.eln_entry.entry_status in self.__ENTRY_LOCKED_STATUSES:
|
|
1631
1150
|
step.unlock_step()
|
|
1632
1151
|
step.eln_entry.entry_status = ExperimentEntryStatus.UnlockedChangesRequired
|
|
1633
1152
|
|
|
@@ -1649,8 +1168,8 @@ class ExperimentHandler:
|
|
|
1649
1168
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1650
1169
|
"""
|
|
1651
1170
|
step = self.__to_eln_step(step)
|
|
1652
|
-
if step.eln_entry.entry_status in self.
|
|
1653
|
-
self.
|
|
1171
|
+
if step.eln_entry.entry_status in self.__ENTRY_LOCKED_STATUSES:
|
|
1172
|
+
self.update_step(step, entry_status=ExperimentEntryStatus.Disabled)
|
|
1654
1173
|
|
|
1655
1174
|
def step_is_submitted(self, step: Step) -> bool:
|
|
1656
1175
|
"""
|
|
@@ -1665,7 +1184,7 @@ class ExperimentHandler:
|
|
|
1665
1184
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1666
1185
|
:return: True if the step's status is Completed or CompletedApproved. False otherwise.
|
|
1667
1186
|
"""
|
|
1668
|
-
return self.__to_eln_step(step).eln_entry.entry_status in self.
|
|
1187
|
+
return self.__to_eln_step(step).eln_entry.entry_status in self.__ENTRY_COMPLETE_STATUSES
|
|
1669
1188
|
|
|
1670
1189
|
def step_is_locked(self, step: Step) -> bool:
|
|
1671
1190
|
"""
|
|
@@ -1681,171 +1200,7 @@ class ExperimentHandler:
|
|
|
1681
1200
|
:return: True if the step's status is Completed, CompletedApproved, Disabled, LockedAwaitingApproval,
|
|
1682
1201
|
or LockedRejected. False otherwise.
|
|
1683
1202
|
"""
|
|
1684
|
-
return self.__to_eln_step(step).eln_entry.entry_status in self.
|
|
1685
|
-
|
|
1686
|
-
# FR-47464: Some functions that can help with entry placement.
|
|
1687
|
-
def get_all_tabs(self) -> list[ElnExperimentTab]:
|
|
1688
|
-
"""
|
|
1689
|
-
If no tab functions have been called before and a tab is being searched for by name, queries for the
|
|
1690
|
-
list of tabs in the experiment and caches them.
|
|
1691
|
-
|
|
1692
|
-
:return: A list of all the tabs in the experiment in order of appearance.
|
|
1693
|
-
"""
|
|
1694
|
-
if not self._queried_all_tabs:
|
|
1695
|
-
self._tabs = self._eln_man.get_tabs_for_experiment(self._exp_id)
|
|
1696
|
-
self._tabs.sort(key=lambda t: t.tab_order)
|
|
1697
|
-
self._tabs_by_id = {tab.tab_id: tab for tab in self._tabs}
|
|
1698
|
-
self._tabs_by_name = {tab.tab_name: tab for tab in self._tabs}
|
|
1699
|
-
return self._tabs
|
|
1700
|
-
|
|
1701
|
-
def get_first_tab(self) -> ElnExperimentTab:
|
|
1702
|
-
"""
|
|
1703
|
-
If no tab functions have been called before and a tab is being searched for by name, queries for the
|
|
1704
|
-
list of tabs in the experiment and caches them.
|
|
1705
|
-
|
|
1706
|
-
:return: The first tab in the experiment.
|
|
1707
|
-
"""
|
|
1708
|
-
return self.get_all_tabs()[0]
|
|
1709
|
-
|
|
1710
|
-
def get_last_tab(self) -> ElnExperimentTab:
|
|
1711
|
-
"""
|
|
1712
|
-
If no tab functions have been called before and a tab is being searched for by name, queries for the
|
|
1713
|
-
list of tabs in the experiment and caches them.
|
|
1714
|
-
|
|
1715
|
-
:return: The last tab in the experiment.
|
|
1716
|
-
"""
|
|
1717
|
-
return self.get_all_tabs()[-1]
|
|
1718
|
-
|
|
1719
|
-
def create_tab(self, tab_name: str) -> ElnExperimentTab:
|
|
1720
|
-
"""
|
|
1721
|
-
Create a new tab in the experiment with the input name.
|
|
1722
|
-
|
|
1723
|
-
:param tab_name: The name of the tab to create.
|
|
1724
|
-
:return: The newly created tab.
|
|
1725
|
-
"""
|
|
1726
|
-
crit = ElnExperimentTabAddCriteria(tab_name, [])
|
|
1727
|
-
tab: ElnExperimentTab = self._eln_man.add_tab_for_experiment(self._exp_id, crit)
|
|
1728
|
-
self.add_tab_to_cache(tab)
|
|
1729
|
-
return tab
|
|
1730
|
-
|
|
1731
|
-
def get_tab(self, tab_name: str | int, exception_on_none: bool = True) -> ElnExperimentTab:
|
|
1732
|
-
"""
|
|
1733
|
-
Return the tab with the input name.
|
|
1734
|
-
|
|
1735
|
-
If no tab functions have been called before and a tab is being searched for by name, queries for the
|
|
1736
|
-
list of tabs in the experiment and caches them.
|
|
1737
|
-
|
|
1738
|
-
:param tab_name: The name or ID of the tab to get.
|
|
1739
|
-
:param exception_on_none: If True, raises an exception if no tab with the given name exists.
|
|
1740
|
-
:return: The tab with the input name, or None if no such tab exists.
|
|
1741
|
-
"""
|
|
1742
|
-
if tab_name not in self._tabs_by_name and tab_name not in self._tabs_by_id:
|
|
1743
|
-
self.get_all_tabs()
|
|
1744
|
-
if isinstance(tab_name, str):
|
|
1745
|
-
tab = self._tabs_by_name.get(tab_name)
|
|
1746
|
-
else:
|
|
1747
|
-
tab = self._tabs_by_id.get(tab_name)
|
|
1748
|
-
if tab is None and exception_on_none:
|
|
1749
|
-
raise SapioException(f"No tab with the name\\ID \"{tab_name}\" exists in this experiment.")
|
|
1750
|
-
return tab
|
|
1751
|
-
|
|
1752
|
-
def get_steps_in_tab(self, tab: TabIdentifier | str, data_type: DataTypeIdentifier | None = None) \
|
|
1753
|
-
-> list[ElnEntryStep]:
|
|
1754
|
-
"""
|
|
1755
|
-
Get all the steps in the input tab sorted in order of appearance.
|
|
1756
|
-
|
|
1757
|
-
If no tab functions have been called before and a tab is being searched for by name, queries for the
|
|
1758
|
-
list of tabs in the experiment and caches them.
|
|
1759
|
-
|
|
1760
|
-
If the steps in the experiment have not been queried before, queries for the list of steps in the experiment
|
|
1761
|
-
and caches them.
|
|
1762
|
-
|
|
1763
|
-
:param tab: The tab to get the steps of. This can be either an ElnExperimentTab object, or the name or ID of
|
|
1764
|
-
the tab.
|
|
1765
|
-
:param data_type: The data type to filter the steps by. If None, all steps are returned.
|
|
1766
|
-
:return: A list of all the steps in the input tab sorted in order of appearance.
|
|
1767
|
-
"""
|
|
1768
|
-
tab: ElnExperimentTab = self.__to_eln_tab(tab)
|
|
1769
|
-
steps: list[ElnEntryStep] = []
|
|
1770
|
-
for step in self.get_all_steps(data_type):
|
|
1771
|
-
if step.eln_entry.notebook_experiment_tab_id == tab.tab_id:
|
|
1772
|
-
steps.append(step)
|
|
1773
|
-
return steps
|
|
1774
|
-
|
|
1775
|
-
def get_next_entry_order_in_tab(self, tab: TabIdentifier | str) -> int:
|
|
1776
|
-
"""
|
|
1777
|
-
Get the next available order for a new entry in the input tab.
|
|
1778
|
-
|
|
1779
|
-
If no tab functions have been called before and a tab is being searched for by name, queries for the
|
|
1780
|
-
list of tabs in the experiment and caches them.
|
|
1781
|
-
|
|
1782
|
-
If the steps in the experiment have not been queried before, queries for the list of steps in the experiment
|
|
1783
|
-
and caches them.
|
|
1784
|
-
|
|
1785
|
-
:param tab: The tab to get the next entry order of. This can be either an ElnExperimentTab object, or the name
|
|
1786
|
-
or ID of the tab.
|
|
1787
|
-
:return: The next available order for a new entry in the input tab.
|
|
1788
|
-
"""
|
|
1789
|
-
steps = self.get_steps_in_tab(tab)
|
|
1790
|
-
return steps[-1].eln_entry.order + 1 if steps else 0
|
|
1791
|
-
|
|
1792
|
-
# FR-47530: Add functions for dealing with entry positioning.
|
|
1793
|
-
def step_to_position(self, step: Step) -> ElnEntryPosition:
|
|
1794
|
-
"""
|
|
1795
|
-
Get the position of the input step in the experiment.
|
|
1796
|
-
|
|
1797
|
-
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
1798
|
-
list of steps in the experiment and caches them.
|
|
1799
|
-
|
|
1800
|
-
:param step:
|
|
1801
|
-
The step to get the position of.
|
|
1802
|
-
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
1803
|
-
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1804
|
-
:return: The position of the input step in the experiment.
|
|
1805
|
-
"""
|
|
1806
|
-
step: ElnEntryStep = self.__to_eln_step(step)
|
|
1807
|
-
entry: ExperimentEntry = step.eln_entry
|
|
1808
|
-
return ElnEntryPosition(entry.notebook_experiment_tab_id,
|
|
1809
|
-
entry.order,
|
|
1810
|
-
entry.column_span,
|
|
1811
|
-
entry.column_order)
|
|
1812
|
-
|
|
1813
|
-
def step_at_position(self, position: ElnEntryPosition) -> Step | None:
|
|
1814
|
-
"""
|
|
1815
|
-
Get the step at the input position in the experiment.
|
|
1816
|
-
|
|
1817
|
-
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
1818
|
-
list of steps in the experiment and caches them.
|
|
1819
|
-
|
|
1820
|
-
:param position: The position to get the step at.
|
|
1821
|
-
:return: The step at the input position in the experiment, or None if no step exists at that position.
|
|
1822
|
-
"""
|
|
1823
|
-
if position.tab_id is None or position.order is None:
|
|
1824
|
-
raise SapioException("The provided position must at least have a tab ID and order.")
|
|
1825
|
-
for step in self.get_steps_in_tab(position.tab_id):
|
|
1826
|
-
entry: ExperimentEntry = step.eln_entry
|
|
1827
|
-
if entry.order != position.order:
|
|
1828
|
-
continue
|
|
1829
|
-
if position.column_span is not None and entry.column_span != position.column_span:
|
|
1830
|
-
continue
|
|
1831
|
-
if position.column_order is not None and entry.column_order != position.column_order:
|
|
1832
|
-
continue
|
|
1833
|
-
return step
|
|
1834
|
-
return None
|
|
1835
|
-
|
|
1836
|
-
# FR-47530: Create a function for adding protocol templates to the experiment.
|
|
1837
|
-
def add_protocol(self, protocol: ProtocolTemplateInfo | int, position: ElnEntryPosition) -> list[ElnEntryStep]:
|
|
1838
|
-
"""
|
|
1839
|
-
Add a protocol to the experiment. Updates the handler cache with the newly created entries.
|
|
1840
|
-
|
|
1841
|
-
:param protocol: The protocol to add. This can be either a ProtocolTemplateInfo object or the ID of the
|
|
1842
|
-
protocol template.
|
|
1843
|
-
:param position: The position that the protocol's first entry will be placed at.
|
|
1844
|
-
:return: The newly created protocol entries.
|
|
1845
|
-
"""
|
|
1846
|
-
protocol = protocol if isinstance(protocol, int) else protocol.template_id
|
|
1847
|
-
new_entries: list[ExperimentEntry] = self._eln_man.add_protocol_template(self._exp_id, protocol, position)
|
|
1848
|
-
return self.add_entries_to_caches(new_entries)
|
|
1203
|
+
return self.__to_eln_step(step).eln_entry.entry_status in self.__ENTRY_LOCKED_STATUSES
|
|
1849
1204
|
|
|
1850
1205
|
def __to_eln_step(self, step: Step) -> ElnEntryStep:
|
|
1851
1206
|
"""
|
|
@@ -1855,24 +1210,15 @@ class ExperimentHandler:
|
|
|
1855
1210
|
|
|
1856
1211
|
:return: The input step as an ElnEntryStep.
|
|
1857
1212
|
"""
|
|
1858
|
-
if isinstance(step, str)
|
|
1859
|
-
return self.get_step(step)
|
|
1860
|
-
if isinstance(step, int):
|
|
1861
|
-
return self._steps_by_id.get(step)
|
|
1862
|
-
if isinstance(step, ExperimentEntry):
|
|
1863
|
-
return self.add_entry_to_caches(step)
|
|
1864
|
-
return step
|
|
1213
|
+
return self.get_step(step) if isinstance(step, str) else step
|
|
1865
1214
|
|
|
1866
|
-
def
|
|
1215
|
+
def __get_experiment_options(self) -> dict[str, str]:
|
|
1867
1216
|
"""
|
|
1868
|
-
|
|
1869
|
-
This will query and cache the tabs for the experiment if the input tab is a name and the tabs have not been
|
|
1870
|
-
cached before.
|
|
1217
|
+
Cache the experiment options if they haven't been cached yet.
|
|
1871
1218
|
|
|
1872
|
-
:return: The
|
|
1219
|
+
:return: The options for this experiment.
|
|
1873
1220
|
"""
|
|
1874
|
-
if
|
|
1875
|
-
return self.
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
return tab
|
|
1221
|
+
if hasattr(self, "_ExperimentHandler__exp_options"):
|
|
1222
|
+
return self.__exp_options
|
|
1223
|
+
self.__exp_options = self.__eln_man.get_notebook_experiment_options(self.__exp_id)
|
|
1224
|
+
return self.__exp_options
|