sapiopycommons 2025.7.1a575__py3-none-any.whl → 2025.7.1a576__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/ai/api/fielddefinitions/proto/fields_pb2.py +43 -0
- sapiopycommons/ai/api/fielddefinitions/proto/fields_pb2.pyi +31 -0
- sapiopycommons/ai/api/fielddefinitions/proto/fields_pb2_grpc.py +24 -0
- sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def_pb2.py +123 -0
- sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def_pb2.pyi +598 -0
- sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def_pb2_grpc.py +24 -0
- sapiopycommons/ai/api/plan/proto/step_output_pb2.py +45 -0
- sapiopycommons/ai/api/plan/proto/step_output_pb2.pyi +42 -0
- sapiopycommons/ai/api/plan/proto/step_output_pb2_grpc.py +24 -0
- sapiopycommons/ai/api/plan/proto/step_pb2.py +43 -0
- sapiopycommons/ai/api/plan/proto/step_pb2.pyi +43 -0
- sapiopycommons/ai/api/plan/proto/step_pb2_grpc.py +24 -0
- sapiopycommons/ai/api/plan/script/proto/script_pb2.py +53 -0
- sapiopycommons/ai/api/plan/script/proto/script_pb2.pyi +99 -0
- sapiopycommons/ai/api/plan/script/proto/script_pb2_grpc.py +153 -0
- sapiopycommons/ai/api/plan/tool/proto/entry_pb2.py +57 -0
- sapiopycommons/ai/api/plan/tool/proto/entry_pb2.pyi +96 -0
- sapiopycommons/ai/api/plan/tool/proto/entry_pb2_grpc.py +24 -0
- sapiopycommons/ai/api/plan/tool/proto/tool_pb2.py +67 -0
- sapiopycommons/ai/api/plan/tool/proto/tool_pb2.pyi +220 -0
- sapiopycommons/ai/api/plan/tool/proto/tool_pb2_grpc.py +154 -0
- sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2.py +39 -0
- sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2.pyi +32 -0
- sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2_grpc.py +24 -0
- sapiopycommons/ai/protobuf_utils.py +508 -0
- sapiopycommons/ai/test_client.py +216 -0
- sapiopycommons/ai/tool_service_base.py +798 -0
- sapiopycommons/callbacks/callback_util.py +332 -665
- sapiopycommons/callbacks/field_builder.py +0 -2
- sapiopycommons/chem/IndigoMolecules.py +1 -29
- sapiopycommons/chem/Molecules.py +3 -3
- sapiopycommons/customreport/auto_pagers.py +1 -26
- sapiopycommons/customreport/term_builder.py +1 -1
- sapiopycommons/datatype/pseudo_data_types.py +326 -349
- sapiopycommons/eln/experiment_handler.py +767 -408
- sapiopycommons/eln/experiment_report_util.py +6 -11
- sapiopycommons/eln/plate_designer.py +2 -7
- sapiopycommons/files/file_util.py +5 -7
- sapiopycommons/general/accession_service.py +2 -2
- sapiopycommons/general/aliases.py +1 -3
- sapiopycommons/general/audit_log.py +0 -7
- sapiopycommons/general/custom_report_util.py +0 -12
- sapiopycommons/processtracking/custom_workflow_handler.py +1 -11
- sapiopycommons/processtracking/endpoints.py +0 -27
- sapiopycommons/recordmodel/record_handler.py +317 -657
- sapiopycommons/rules/eln_rule_handler.py +1 -8
- sapiopycommons/rules/on_save_rule_handler.py +1 -8
- sapiopycommons/webhook/webhook_handlers.py +0 -3
- sapiopycommons/webhook/webservice_handlers.py +2 -2
- {sapiopycommons-2025.7.1a575.dist-info → sapiopycommons-2025.7.1a576.dist-info}/METADATA +2 -2
- sapiopycommons-2025.7.1a576.dist-info/RECORD +92 -0
- sapiopycommons/ai/tool_of_tools.py +0 -917
- sapiopycommons/eln/experiment_cache.py +0 -188
- sapiopycommons/eln/experiment_step_factory.py +0 -476
- sapiopycommons/eln/step_creation.py +0 -236
- sapiopycommons/general/data_structure_util.py +0 -115
- sapiopycommons-2025.7.1a575.dist-info/RECORD +0 -70
- {sapiopycommons-2025.7.1a575.dist-info → sapiopycommons-2025.7.1a576.dist-info}/WHEEL +0 -0
- {sapiopycommons-2025.7.1a575.dist-info → sapiopycommons-2025.7.1a576.dist-info}/licenses/LICENSE +0 -0
|
@@ -5,46 +5,58 @@ from collections.abc import Mapping, Iterable
|
|
|
5
5
|
from typing import TypeAlias
|
|
6
6
|
from weakref import WeakValueDictionary
|
|
7
7
|
|
|
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
|
|
12
|
-
from sapiopycommons.general.exceptions import SapioException
|
|
13
|
-
from sapiopycommons.general.time_util import TimeUtil
|
|
14
|
-
from sapiopycommons.recordmodel.record_handler import RecordHandler
|
|
15
8
|
from sapiopylib.rest.DataMgmtService import DataMgmtServer
|
|
9
|
+
from sapiopylib.rest.DataRecordManagerService import DataRecordManager
|
|
16
10
|
from sapiopylib.rest.ELNService import ElnManager
|
|
17
11
|
from sapiopylib.rest.User import SapioUser
|
|
18
12
|
from sapiopylib.rest.pojo.DataRecord import DataRecord
|
|
13
|
+
from sapiopylib.rest.pojo.TableColumn import TableColumn
|
|
14
|
+
from sapiopylib.rest.pojo.datatype.FieldDefinition import AbstractVeloxFieldDefinition
|
|
19
15
|
from sapiopylib.rest.pojo.eln.ElnEntryPosition import ElnEntryPosition
|
|
20
16
|
from sapiopylib.rest.pojo.eln.ElnExperiment import ElnExperiment, TemplateExperimentQueryPojo, ElnTemplate, \
|
|
21
17
|
InitializeNotebookExperimentPojo, ElnExperimentUpdateCriteria
|
|
22
18
|
from sapiopylib.rest.pojo.eln.ExperimentEntry import ExperimentEntry, ExperimentTableEntry, ExperimentFormEntry, \
|
|
23
19
|
ExperimentAttachmentEntry, ExperimentPluginEntry, ExperimentDashboardEntry, ExperimentTextEntry, \
|
|
24
|
-
ExperimentTempDataEntry
|
|
20
|
+
ExperimentTempDataEntry, EntryAttachment, EntryRecordAttachment
|
|
25
21
|
from sapiopylib.rest.pojo.eln.ExperimentEntryCriteria import AbstractElnEntryUpdateCriteria, \
|
|
26
22
|
ElnTableEntryUpdateCriteria, ElnFormEntryUpdateCriteria, ElnAttachmentEntryUpdateCriteria, \
|
|
27
23
|
ElnPluginEntryUpdateCriteria, ElnDashboardEntryUpdateCriteria, ElnTextEntryUpdateCriteria, \
|
|
28
|
-
ElnTempDataEntryUpdateCriteria
|
|
24
|
+
ElnTempDataEntryUpdateCriteria, ElnEntryCriteria
|
|
29
25
|
from sapiopylib.rest.pojo.eln.SapioELNEnums import ExperimentEntryStatus, ElnExperimentStatus, ElnEntryType, \
|
|
30
26
|
ElnBaseDataType
|
|
31
27
|
from sapiopylib.rest.pojo.eln.eln_headings import ElnExperimentTab, ElnExperimentTabAddCriteria
|
|
32
|
-
from sapiopylib.rest.pojo.eln.
|
|
28
|
+
from sapiopylib.rest.pojo.eln.field_set import ElnFieldSetInfo
|
|
33
29
|
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
34
30
|
from sapiopylib.rest.pojo.webhook.WebhookDirective import ElnExperimentDirective
|
|
35
31
|
from sapiopylib.rest.pojo.webhook.WebhookResult import SapioWebhookResult
|
|
36
32
|
from sapiopylib.rest.utils.Protocols import ElnEntryStep, ElnExperimentProtocol
|
|
33
|
+
from sapiopylib.rest.utils.plates.MultiLayerPlating import MultiLayerPlateConfig, MultiLayerPlateLayer, \
|
|
34
|
+
MultiLayerDataTypeConfig, MultiLayerReplicateConfig, MultiLayerDilutionConfig
|
|
35
|
+
from sapiopylib.rest.utils.plates.MultiLayerPlatingUtils import MultiLayerPlatingManager
|
|
36
|
+
from sapiopylib.rest.utils.plates.PlatingUtils import PlatingOrder
|
|
37
37
|
from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
|
|
38
38
|
from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelInstanceManager, RecordModelManager
|
|
39
39
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
|
|
40
40
|
from sapiopylib.rest.utils.recordmodel.properties import Child
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
from sapiopycommons.datatype.data_fields import SystemFields
|
|
43
|
+
from sapiopycommons.eln.experiment_report_util import ExperimentReportUtil
|
|
44
|
+
from sapiopycommons.eln.experiment_tags import PLATE_DESIGNER_PLUGIN
|
|
45
|
+
from sapiopycommons.general.aliases import AliasUtil, SapioRecord, ExperimentIdentifier, UserIdentifier, \
|
|
46
|
+
DataTypeIdentifier, RecordModel, FieldMap, FieldIdentifier
|
|
47
|
+
from sapiopycommons.general.exceptions import SapioException
|
|
48
|
+
from sapiopycommons.general.time_util import TimeUtil
|
|
49
|
+
from sapiopycommons.recordmodel.record_handler import RecordHandler
|
|
50
|
+
|
|
51
|
+
Step: TypeAlias = str | ElnEntryStep
|
|
52
|
+
"""An object representing an identifier to an ElnEntryStep. May be either the name of the step or the ElnEntryStep
|
|
53
|
+
itself."""
|
|
54
|
+
Tab: TypeAlias = str | ElnExperimentTab
|
|
55
|
+
"""An object representing an identifier to an ElnExperimentTab. May be either the name of the tab or the
|
|
56
|
+
ElnExperimentTab itself."""
|
|
57
|
+
ElnDataTypeFields: TypeAlias = AbstractVeloxFieldDefinition | ElnFieldSetInfo | str | int
|
|
58
|
+
"""An object representing an identifier to an ElnDataType field. These can be field definitions for ad hoc fields,
|
|
59
|
+
predefined field set info objects, predefined field names, or integers for predefined field set IDs."""
|
|
48
60
|
|
|
49
61
|
|
|
50
62
|
# FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
|
|
@@ -65,8 +77,6 @@ class ExperimentHandler:
|
|
|
65
77
|
# Managers.
|
|
66
78
|
_eln_man: ElnManager
|
|
67
79
|
"""The ELN manager. Used for updating the experiment and its steps."""
|
|
68
|
-
_exp_cache: ExperimentCacheManager
|
|
69
|
-
"""The experiment cache manager. Used for caching experiment-related information."""
|
|
70
80
|
_inst_man: RecordModelInstanceManager
|
|
71
81
|
"""The record model instance manager. Used for wrapping the data records of a step as record models."""
|
|
72
82
|
_rec_handler: RecordHandler
|
|
@@ -85,9 +95,7 @@ class ExperimentHandler:
|
|
|
85
95
|
|
|
86
96
|
_queried_all_steps: bool
|
|
87
97
|
"""Whether this ExperimentHandler has queried the system for all steps in the experiment."""
|
|
88
|
-
_steps:
|
|
89
|
-
"""The sorted list of steps for this experiment. All steps are cached the first time any individual step is accessed."""
|
|
90
|
-
_steps_by_name: dict[str, ElnEntryStep]
|
|
98
|
+
_steps: dict[str, ElnEntryStep]
|
|
91
99
|
"""Steps from this experiment by their name. All steps are cached the first time any individual step is accessed."""
|
|
92
100
|
_steps_by_id: dict[int, ElnEntryStep]
|
|
93
101
|
"""Steps from this experiment by their ID. All steps are cached the first time any individual step is accessed."""
|
|
@@ -101,12 +109,13 @@ class ExperimentHandler:
|
|
|
101
109
|
_queried_all_tabs: bool
|
|
102
110
|
"""Whether this ExperimentHandler has queried the system for all tabs in the experiment."""
|
|
103
111
|
_tabs: list[ElnExperimentTab]
|
|
104
|
-
"""The
|
|
105
|
-
_tabs_by_id: dict[int, ElnExperimentTab]
|
|
106
|
-
"""The tabs for this experiment by their ID. Only cached when first accessed."""
|
|
112
|
+
"""The tabs for this experiment. Only cached when first accessed."""
|
|
107
113
|
_tabs_by_name: dict[str, ElnExperimentTab]
|
|
108
114
|
"""The tabs for this experiment by their name. Only cached when first accessed."""
|
|
109
115
|
|
|
116
|
+
_predefined_fields: dict[str, dict[str, AbstractVeloxFieldDefinition]]
|
|
117
|
+
"""A dictionary of predefined fields for each ELN data type. Only cached when first accessed."""
|
|
118
|
+
|
|
110
119
|
# Constants
|
|
111
120
|
_ENTRY_COMPLETE_STATUSES = [ExperimentEntryStatus.Completed, ExperimentEntryStatus.CompletedApproved]
|
|
112
121
|
"""The set of statuses that an ELN entry could have and be considered completed/submitted."""
|
|
@@ -164,20 +173,17 @@ class ExperimentHandler:
|
|
|
164
173
|
|
|
165
174
|
# Grab various managers that may be used.
|
|
166
175
|
self._eln_man = DataMgmtServer.get_eln_manager(self.user)
|
|
167
|
-
self._exp_cache = ExperimentCacheManager(self.user)
|
|
168
176
|
self._inst_man = RecordModelManager(self.user).instance_manager
|
|
169
177
|
self._rec_handler = RecordHandler(self.user)
|
|
170
178
|
|
|
171
179
|
# Create empty caches to fill when necessary.
|
|
172
180
|
self._queried_all_steps = False
|
|
173
|
-
self.
|
|
181
|
+
self._steps = {}
|
|
174
182
|
self._steps_by_id = {}
|
|
175
|
-
self._steps = []
|
|
176
183
|
self._step_options = {}
|
|
177
184
|
self._step_updates = {}
|
|
178
185
|
|
|
179
186
|
self._tabs = []
|
|
180
|
-
self._tabs_by_id = {}
|
|
181
187
|
self._tabs_by_name = {}
|
|
182
188
|
|
|
183
189
|
self._queried_all_tabs = False
|
|
@@ -193,8 +199,7 @@ class ExperimentHandler:
|
|
|
193
199
|
for entry in self.context.experiment_entry_list:
|
|
194
200
|
cache_steps.append(ElnEntryStep(self._protocol, entry))
|
|
195
201
|
for step in cache_steps:
|
|
196
|
-
self._steps.
|
|
197
|
-
self._steps_by_name.update({step.get_name(): step})
|
|
202
|
+
self._steps.update({step.get_name(): step})
|
|
198
203
|
self._steps_by_id.update({step.get_id(): step})
|
|
199
204
|
|
|
200
205
|
@staticmethod
|
|
@@ -259,7 +264,6 @@ class ExperimentHandler:
|
|
|
259
264
|
"""
|
|
260
265
|
self._queried_all_steps = False
|
|
261
266
|
self._steps.clear()
|
|
262
|
-
self._steps_by_name.clear()
|
|
263
267
|
self._steps_by_id.clear()
|
|
264
268
|
self._step_options.clear()
|
|
265
269
|
self._step_updates.clear()
|
|
@@ -278,46 +282,32 @@ class ExperimentHandler:
|
|
|
278
282
|
"""
|
|
279
283
|
self._queried_all_tabs = False
|
|
280
284
|
self._tabs.clear()
|
|
281
|
-
self._tabs_by_id.clear()
|
|
282
285
|
self._tabs_by_name.clear()
|
|
283
286
|
|
|
284
|
-
def add_entry_to_caches(self, entry: ExperimentEntry | ElnEntryStep) ->
|
|
287
|
+
def add_entry_to_caches(self, entry: ExperimentEntry | ElnEntryStep) -> None:
|
|
285
288
|
"""
|
|
286
289
|
Add the given entry to the cache of steps for this experiment. This is necessary in order for certain methods to
|
|
287
290
|
work. You should only need to do this if you have created a new entry in your code using a method outside
|
|
288
291
|
of this ExperimentHandler.
|
|
289
292
|
|
|
290
293
|
:param entry: The entry to add to the cache.
|
|
291
|
-
:return: The entry that was added to the cache as an ElnEntryStep.
|
|
292
294
|
"""
|
|
293
|
-
# ExperimentEntries are stored as ElnEntrySteps in the cache.
|
|
294
295
|
if isinstance(entry, ExperimentEntry):
|
|
295
296
|
entry = ElnEntryStep(self._protocol, entry)
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
if entry.get_id() not in self._steps_by_id:
|
|
302
|
-
self._steps.append(entry)
|
|
303
|
-
self._steps_by_name.update({entry.get_name(): entry})
|
|
304
|
-
self._steps_by_id.update({entry.get_id(): entry})
|
|
305
|
-
# Skipping the options cache. The get_step_options method will update that cache when necessary.
|
|
306
|
-
return entry
|
|
307
|
-
|
|
308
|
-
def add_entries_to_caches(self, entries: list[ExperimentEntry | ElnEntryStep]) -> list[ElnEntryStep]:
|
|
297
|
+
self._steps.update({entry.get_name(): entry})
|
|
298
|
+
self._steps_by_id.update({entry.get_id(): entry})
|
|
299
|
+
# Skipping the options cache. The get_step_options method will update the cache when necessary.
|
|
300
|
+
|
|
301
|
+
def add_entries_to_caches(self, entries: list[ExperimentEntry | ElnEntryStep]) -> None:
|
|
309
302
|
"""
|
|
310
303
|
Add the given entries to the cache of steps for this experiment. This is necessary in order for certain methods
|
|
311
304
|
to work. You should only need to do this if you have created a new entry in your code using a method outside
|
|
312
305
|
of this ExperimentHandler.
|
|
313
306
|
|
|
314
307
|
:param entries: The entries to add to the cache.
|
|
315
|
-
:return: The entries that were added to the cache as ElnEntrySteps.
|
|
316
308
|
"""
|
|
317
|
-
new_entries: list[ElnEntryStep] = []
|
|
318
309
|
for entry in entries:
|
|
319
|
-
|
|
320
|
-
return new_entries
|
|
310
|
+
self.add_entry_to_caches(entry)
|
|
321
311
|
|
|
322
312
|
def add_tab_to_cache(self, tab: ElnExperimentTab) -> None:
|
|
323
313
|
"""
|
|
@@ -329,15 +319,12 @@ class ExperimentHandler:
|
|
|
329
319
|
"""
|
|
330
320
|
self._tabs.append(tab)
|
|
331
321
|
self._tabs.sort(key=lambda t: t.tab_order)
|
|
332
|
-
self._tabs_by_id[tab.tab_id] = tab
|
|
333
322
|
self._tabs_by_name[tab.tab_name] = tab
|
|
334
323
|
|
|
335
324
|
# FR-46495: Split the creation of the experiment in launch_experiment into a create_experiment function.
|
|
336
|
-
# CR-47703: Allow create_experiment and launch_experiment to accept None as a template_name to create a blank
|
|
337
|
-
# experiment. Also allow a SapioUser object to be provided as context instead of a full SapioWebhookContext object.
|
|
338
325
|
@staticmethod
|
|
339
|
-
def create_experiment(context:
|
|
340
|
-
template_name: str
|
|
326
|
+
def create_experiment(context: SapioWebhookContext,
|
|
327
|
+
template_name: str,
|
|
341
328
|
experiment_name: str | None = None,
|
|
342
329
|
parent_record: SapioRecord | None = None, *,
|
|
343
330
|
template_version: int | None = None, active_templates_only: bool = True) -> ElnExperiment:
|
|
@@ -348,9 +335,8 @@ class ExperimentHandler:
|
|
|
348
335
|
templates match the same criteria, the first template that is encountered in the query is used. Throws an
|
|
349
336
|
exception if no template is found. Also makes a webservice request to create the experiment.
|
|
350
337
|
|
|
351
|
-
:param context: The current webhook context
|
|
352
|
-
:param template_name: The name of the template to create the experiment from.
|
|
353
|
-
is created.
|
|
338
|
+
:param context: The current webhook context.
|
|
339
|
+
:param template_name: The name of the template to create the experiment from.
|
|
354
340
|
:param experiment_name: The name to give to the experiment after it is created. If not provided, defaults to the
|
|
355
341
|
display name of the template.
|
|
356
342
|
:param parent_record: The parent record to attach this experiment under. This record must be an eligible
|
|
@@ -362,27 +348,32 @@ class ExperimentHandler:
|
|
|
362
348
|
:param active_templates_only: Whether only active templates should be queried for.
|
|
363
349
|
:return: The newly created experiment.
|
|
364
350
|
"""
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
351
|
+
template_query = TemplateExperimentQueryPojo(latest_version_only=(template_version is None),
|
|
352
|
+
active_templates_only=active_templates_only)
|
|
353
|
+
templates: list[ElnTemplate] = context.eln_manager.get_template_experiment_list(template_query)
|
|
354
|
+
launch_template: ElnTemplate | None = None
|
|
355
|
+
for template in templates:
|
|
356
|
+
if template.template_name != template_name:
|
|
357
|
+
continue
|
|
358
|
+
if template_version is not None and template.template_version != template_version:
|
|
359
|
+
continue
|
|
360
|
+
launch_template = template
|
|
361
|
+
break
|
|
362
|
+
if launch_template is None:
|
|
363
|
+
raise SapioException(f"No template with the name \"{template_name}\"" +
|
|
364
|
+
("" if template_version is None else f" and the version {template_version}") +
|
|
365
|
+
f" found.")
|
|
366
|
+
|
|
367
|
+
if experiment_name is None:
|
|
368
|
+
experiment_name: str = launch_template.display_name
|
|
378
369
|
if parent_record is not None:
|
|
379
370
|
parent_record: DataRecord = AliasUtil.to_data_record(parent_record)
|
|
380
|
-
notebook_init = InitializeNotebookExperimentPojo(experiment_name, template_id, parent_record)
|
|
381
|
-
return eln_manager.create_notebook_experiment(notebook_init)
|
|
371
|
+
notebook_init = InitializeNotebookExperimentPojo(experiment_name, launch_template.template_id, parent_record)
|
|
372
|
+
return context.eln_manager.create_notebook_experiment(notebook_init)
|
|
382
373
|
|
|
383
374
|
@staticmethod
|
|
384
|
-
def launch_experiment(context:
|
|
385
|
-
template_name: str
|
|
375
|
+
def launch_experiment(context: SapioWebhookContext,
|
|
376
|
+
template_name: str,
|
|
386
377
|
experiment_name: str | None = None,
|
|
387
378
|
parent_record: SapioRecord | None = None, *,
|
|
388
379
|
template_version: int | None = None,
|
|
@@ -395,9 +386,8 @@ class ExperimentHandler:
|
|
|
395
386
|
templates match the same criteria, the first template that is encountered in the query is used. Throws an
|
|
396
387
|
exception if no template is found. Also makes a webservice request to create the experiment.
|
|
397
388
|
|
|
398
|
-
:param context: The current webhook context
|
|
399
|
-
:param template_name: The name of the template to create the experiment from.
|
|
400
|
-
is created.
|
|
389
|
+
:param context: The current webhook context.
|
|
390
|
+
:param template_name: The name of the template to create the experiment from.
|
|
401
391
|
:param experiment_name: The name to give to the experiment after it is created. If not provided, defaults to the
|
|
402
392
|
display name of the template.
|
|
403
393
|
:param parent_record: The parent record to attach this experiment under. This record must be an eligible
|
|
@@ -600,9 +590,6 @@ class ExperimentHandler:
|
|
|
600
590
|
"""
|
|
601
591
|
Set the experiment's status to Completed. Makes a webservice call to update the experiment. Checks if the
|
|
602
592
|
experiment is already completed, and does nothing if so.
|
|
603
|
-
|
|
604
|
-
NOTE: This will cause the usual process tracking logic to run as if you'd clicked the "Complete Experiment"
|
|
605
|
-
toolbar button. This includes moving the in process samples forward to the next step in the process.
|
|
606
593
|
"""
|
|
607
594
|
if not self.experiment_is_complete():
|
|
608
595
|
self._protocol.complete_protocol()
|
|
@@ -613,11 +600,9 @@ class ExperimentHandler:
|
|
|
613
600
|
Set the experiment's status to Canceled. Makes a webservice call to update the experiment. Checks if the
|
|
614
601
|
experiment is already canceled, and does nothing if so.
|
|
615
602
|
|
|
616
|
-
NOTE: This will
|
|
617
|
-
toolbar button
|
|
618
|
-
|
|
619
|
-
On version 24.12 and earlier, this was not the case, as the process tracking logic was tied to the button
|
|
620
|
-
instead of being on the experiment status change.
|
|
603
|
+
NOTE: This will not run the usual logic around canceling an experiment that you'd see if canceling the
|
|
604
|
+
experiment using the "Cancel Experiment" toolbar button, such as moving in process samples back to the queue,
|
|
605
|
+
as those changes are tied to the button instead of being on the experiment status change.
|
|
621
606
|
"""
|
|
622
607
|
if not self.experiment_is_canceled():
|
|
623
608
|
self._protocol.cancel_protocol()
|
|
@@ -647,14 +632,14 @@ class ExperimentHandler:
|
|
|
647
632
|
"""
|
|
648
633
|
return all([x is not None for x in self.get_steps(step_names, False)])
|
|
649
634
|
|
|
650
|
-
def get_step(self, step_name:
|
|
635
|
+
def get_step(self, step_name: str, exception_on_none: bool = True) -> ElnEntryStep | None:
|
|
651
636
|
"""
|
|
652
637
|
Get the step of the given name from the experiment.
|
|
653
638
|
|
|
654
639
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
655
640
|
list of steps in the experiment and caches them.
|
|
656
641
|
|
|
657
|
-
:param step_name: The
|
|
642
|
+
:param step_name: The name for the step to return.
|
|
658
643
|
:param exception_on_none: If false, returns None if the entry can't be found. If true, raises an exception
|
|
659
644
|
when the named entry doesn't exist in the experiment.
|
|
660
645
|
:return: An ElnEntrySteps matching the provided name. If there is no match and no exception is to be thrown,
|
|
@@ -662,49 +647,31 @@ class ExperimentHandler:
|
|
|
662
647
|
"""
|
|
663
648
|
return self.get_steps([step_name], exception_on_none)[0]
|
|
664
649
|
|
|
665
|
-
def get_steps(self, step_names: Iterable[
|
|
650
|
+
def get_steps(self, step_names: Iterable[str], exception_on_none: bool = True) -> list[ElnEntryStep | None]:
|
|
666
651
|
"""
|
|
667
652
|
Get a list of steps of the given names from the experiment, sorted in the same order as the names are provided.
|
|
668
653
|
|
|
669
654
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
670
655
|
list of steps in the experiment and caches them.
|
|
671
656
|
|
|
672
|
-
:param step_names: A list of
|
|
657
|
+
:param step_names: A list of names for the entries to return and the order to return them in.
|
|
673
658
|
:param exception_on_none: If false, returns None for entries that can't be found. If true, raises an exception
|
|
674
659
|
when the named entry doesn't exist in the experiment.
|
|
675
660
|
:return: A list of ElnEntrySteps matching the provided names in the order they were provided in. If there is no
|
|
676
661
|
match for a given step and no exception is to be thrown, returns None for that step.
|
|
677
662
|
"""
|
|
678
|
-
# CR-47700: Support getting steps from entry objects.
|
|
679
|
-
step_ids: list[str | int] = []
|
|
680
|
-
for step in step_names:
|
|
681
|
-
# Convert any ElnEntrySteps into ExperimentEntries.
|
|
682
|
-
if isinstance(step, ElnEntryStep):
|
|
683
|
-
step: ExperimentEntry = step.eln_entry
|
|
684
|
-
# If an ExperimentEntry is provided, then add its ID to the list of steps to return.
|
|
685
|
-
if isinstance(step, ExperimentEntry):
|
|
686
|
-
step_ids.append(step.entry_id)
|
|
687
|
-
# Add this entry to the caches so that it doesn't need to be queried for. This will also verify that
|
|
688
|
-
# this entry belongs to the experiment that this handler is for.
|
|
689
|
-
self.add_entry_to_caches(step)
|
|
690
|
-
elif isinstance(step, (str, int)):
|
|
691
|
-
step_ids.append(step)
|
|
692
|
-
|
|
693
663
|
ret_list: list[ElnEntryStep | None] = []
|
|
694
|
-
for
|
|
664
|
+
for name in step_names:
|
|
695
665
|
# If we haven't queried the system for all steps in the experiment yet, then the reason that a step is
|
|
696
666
|
# missing may be because it wasn't in the webhook context. Therefore, query all steps and then check
|
|
697
667
|
# if the step name is still missing from the experiment before potentially throwing an exception.
|
|
698
|
-
if not self.
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
step: ElnEntryStep = self._steps_by_name.get(step_id)
|
|
704
|
-
else:
|
|
705
|
-
step: ElnEntryStep = self._steps_by_id.get(step_id)
|
|
668
|
+
if self._queried_all_steps is False and name not in self._steps:
|
|
669
|
+
self._queried_all_steps = True
|
|
670
|
+
self._steps.update({step.get_name(): step for step in self._protocol.get_sorted_step_list()})
|
|
671
|
+
|
|
672
|
+
step: ElnEntryStep = self._steps.get(name)
|
|
706
673
|
if step is None and exception_on_none is True:
|
|
707
|
-
raise SapioException(f"ElnEntryStep of name \"{
|
|
674
|
+
raise SapioException(f"ElnEntryStep of name \"{name}\" not found in experiment with ID {self._exp_id}.")
|
|
708
675
|
ret_list.append(step)
|
|
709
676
|
return ret_list
|
|
710
677
|
|
|
@@ -719,34 +686,14 @@ class ExperimentHandler:
|
|
|
719
686
|
:return: Every entry in the experiment in order of appearance that match the provided data type, if any.
|
|
720
687
|
"""
|
|
721
688
|
if self._queried_all_steps is False:
|
|
722
|
-
self.
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
def sort_steps(step: ElnEntryStep) -> tuple:
|
|
726
|
-
entry = step.eln_entry
|
|
727
|
-
tab_order: int = self.get_tab_for_step(step).tab_order
|
|
728
|
-
entry_order: int = entry.order
|
|
729
|
-
column_order: int = entry.column_order
|
|
730
|
-
return tab_order, entry_order, column_order
|
|
731
|
-
|
|
732
|
-
self._steps.sort(key=sort_steps)
|
|
733
|
-
all_steps: list[ElnEntryStep] = self._steps
|
|
689
|
+
self._queried_all_steps = True
|
|
690
|
+
self._steps.update({step.get_name(): step for step in self._protocol.get_sorted_step_list()})
|
|
691
|
+
all_steps: list[ElnEntryStep] = self._protocol.get_sorted_step_list()
|
|
734
692
|
if data_type is None:
|
|
735
693
|
return all_steps
|
|
736
694
|
data_type: str = AliasUtil.to_data_type_name(data_type)
|
|
737
695
|
return [x for x in all_steps if data_type in x.get_data_type_names()]
|
|
738
696
|
|
|
739
|
-
def _query_all_steps(self) -> None:
|
|
740
|
-
"""
|
|
741
|
-
Query the system for every step in the experiment and cache them.
|
|
742
|
-
"""
|
|
743
|
-
self._queried_all_steps = True
|
|
744
|
-
self._protocol.invalidate()
|
|
745
|
-
self._steps = self._protocol.get_sorted_step_list()
|
|
746
|
-
for step in self._steps:
|
|
747
|
-
self._steps_by_name[step.get_name()] = step
|
|
748
|
-
self._steps_by_id[step.get_id()] = step
|
|
749
|
-
|
|
750
697
|
def get_step_by_option(self, key: str, value: str | None = None) -> ElnEntryStep:
|
|
751
698
|
"""
|
|
752
699
|
Retrieve the step in this experiment that contains an entry option with the provided key and value.
|
|
@@ -793,7 +740,7 @@ class ExperimentHandler:
|
|
|
793
740
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
794
741
|
:return: The data records for the given step.
|
|
795
742
|
"""
|
|
796
|
-
return self.
|
|
743
|
+
return self.__to_eln_step(step).get_records()
|
|
797
744
|
|
|
798
745
|
def get_step_models(self, step: Step, wrapper_type: type[WrappedType] | None = None) \
|
|
799
746
|
-> list[WrappedType] | list[PyRecordModel]:
|
|
@@ -830,14 +777,13 @@ class ExperimentHandler:
|
|
|
830
777
|
A list of records to add to the given step.
|
|
831
778
|
The records may be provided as either DataRecords, PyRecordModels, or WrappedRecordModels.
|
|
832
779
|
"""
|
|
833
|
-
step
|
|
780
|
+
step = self.__to_eln_step(step)
|
|
834
781
|
if not records:
|
|
835
782
|
return
|
|
836
783
|
dt: str = AliasUtil.to_singular_data_type_name(records)
|
|
837
784
|
if ElnBaseDataType.is_base_data_type(dt):
|
|
838
|
-
raise SapioException(f"{dt} is an ELN data type. This function call has no effect on ELN data types.
|
|
839
|
-
f"
|
|
840
|
-
f"with the matching data type name.")
|
|
785
|
+
raise SapioException(f"{dt} is an ELN data type. This function call has no effect on ELN data types. "
|
|
786
|
+
f"Use add_eln_rows or add_sample_details instead.")
|
|
841
787
|
if dt != step.get_data_type_names()[0]:
|
|
842
788
|
raise SapioException(f"Cannot add {dt} records to entry {step.get_name()} of type "
|
|
843
789
|
f"{step.get_data_type_names()[0]}.")
|
|
@@ -845,7 +791,8 @@ class ExperimentHandler:
|
|
|
845
791
|
|
|
846
792
|
def remove_step_records(self, step: Step, records: Iterable[SapioRecord]) -> None:
|
|
847
793
|
"""
|
|
848
|
-
Make a webservice call to remove a list of records from a step.
|
|
794
|
+
Make a webservice call to remove a list of records from a step. Only functions for global data type table
|
|
795
|
+
entries. For removing from an ELN data type table entry, see remove_eln_rows.
|
|
849
796
|
|
|
850
797
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
851
798
|
list of steps in the experiment and caches them.
|
|
@@ -857,14 +804,13 @@ class ExperimentHandler:
|
|
|
857
804
|
A list of records to remove from the given step.
|
|
858
805
|
The records may be provided as either DataRecords, PyRecordModels, or WrappedRecordModels.
|
|
859
806
|
"""
|
|
860
|
-
step
|
|
807
|
+
step = self.__to_eln_step(step)
|
|
861
808
|
if not records:
|
|
862
809
|
return
|
|
863
810
|
dt: str = AliasUtil.to_singular_data_type_name(records)
|
|
864
811
|
if ElnBaseDataType.is_base_data_type(dt):
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
return
|
|
812
|
+
raise SapioException(f"{dt} is an ELN data type. This function call has no effect on ELN data types. "
|
|
813
|
+
f"Use remove_eln_rows or remove_sample_details instead.")
|
|
868
814
|
if dt != step.get_data_type_names()[0]:
|
|
869
815
|
raise SapioException(f"Cannot remove {dt} records from entry {step.get_name()} of type "
|
|
870
816
|
f"{step.get_data_type_names()[0]}.")
|
|
@@ -890,17 +836,12 @@ class ExperimentHandler:
|
|
|
890
836
|
A list of records to set for the given step,
|
|
891
837
|
The records may be provided as either DataRecords, PyRecordModels, or WrappedRecordModels.
|
|
892
838
|
"""
|
|
893
|
-
step
|
|
839
|
+
step = self.__to_eln_step(step)
|
|
894
840
|
if records:
|
|
895
841
|
dt: str = AliasUtil.to_singular_data_type_name(records)
|
|
896
|
-
# CR-47532: Add set_step_records support for Experiment Detail and Sample Detail entries.
|
|
897
842
|
if ElnBaseDataType.is_base_data_type(dt):
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
if record not in records:
|
|
901
|
-
remove_rows.append(record)
|
|
902
|
-
self.remove_eln_rows(step, remove_rows)
|
|
903
|
-
return
|
|
843
|
+
raise SapioException(f"{dt} is an ELN data type. This function call has no effect on ELN data types. "
|
|
844
|
+
f"Use add_eln_rows or add_sample_details instead.")
|
|
904
845
|
if dt != step.get_data_type_names()[0]:
|
|
905
846
|
raise SapioException(f"Cannot set {dt} records for entry {step.get_name()} of type "
|
|
906
847
|
f"{step.get_data_type_names()[0]}.")
|
|
@@ -922,28 +863,11 @@ class ExperimentHandler:
|
|
|
922
863
|
The record may be provided as either a DataRecord, PyRecordModel, or WrappedRecordModel.
|
|
923
864
|
"""
|
|
924
865
|
self.set_step_records(step, [record])
|
|
925
|
-
step
|
|
866
|
+
step = self.__to_eln_step(step)
|
|
926
867
|
if isinstance(step.eln_entry, ExperimentFormEntry):
|
|
927
868
|
step.eln_entry.record_id = AliasUtil.to_data_record(record).record_id
|
|
928
869
|
|
|
929
870
|
# FR-46496 - Provide functions for adding and removing rows from an ELN data type entry.
|
|
930
|
-
def add_eln_row(self, step: Step, wrapper_type: type[WrappedType] | None = None) -> WrappedType | PyRecordModel:
|
|
931
|
-
"""
|
|
932
|
-
Add a row to an ELNExperimentDetail or ELNSampleDetail table entry. The row will not appear in the system
|
|
933
|
-
until a record manager store and commit has occurred.
|
|
934
|
-
|
|
935
|
-
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
936
|
-
list of steps in the experiment and caches them.
|
|
937
|
-
|
|
938
|
-
:param step:
|
|
939
|
-
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
940
|
-
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
941
|
-
:param wrapper_type: Optionally wrap the ELN data type in a record model wrapper. If not provided, returns
|
|
942
|
-
an unwrapped PyRecordModel.
|
|
943
|
-
:return: The newly created row.
|
|
944
|
-
"""
|
|
945
|
-
return self.add_eln_rows(step, 1, wrapper_type)[0]
|
|
946
|
-
|
|
947
871
|
def add_eln_rows(self, step: Step, count: int, wrapper_type: type[WrappedType] | None = None) \
|
|
948
872
|
-> list[WrappedType] | list[PyRecordModel]:
|
|
949
873
|
"""
|
|
@@ -961,7 +885,7 @@ class ExperimentHandler:
|
|
|
961
885
|
an unwrapped PyRecordModel.
|
|
962
886
|
:return: A list of the newly created rows.
|
|
963
887
|
"""
|
|
964
|
-
step
|
|
888
|
+
step = self.__to_eln_step(step)
|
|
965
889
|
if step.eln_entry.entry_type != ElnEntryType.Table:
|
|
966
890
|
raise SapioException("The provided step is not a table entry.")
|
|
967
891
|
dt: str = step.get_data_type_names()[0]
|
|
@@ -972,64 +896,10 @@ class ExperimentHandler:
|
|
|
972
896
|
return self._inst_man.wrap_list(records, wrapper_type)
|
|
973
897
|
return records
|
|
974
898
|
|
|
975
|
-
def
|
|
976
|
-
wrapper_type: type[WrappedType] | None = None) \
|
|
977
|
-
-> WrappedType | PyRecordModel:
|
|
978
|
-
"""
|
|
979
|
-
Add a sample detail to a sample detail entry while relating it to the input sample record.
|
|
980
|
-
|
|
981
|
-
:param step:
|
|
982
|
-
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
983
|
-
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
984
|
-
:param sample: The sample record to add the sample detail to.
|
|
985
|
-
:param wrapper_type: Optionally wrap the sample detail in a record model wrapper. If not provided, returns
|
|
986
|
-
an unwrapped PyRecordModel.
|
|
987
|
-
:return: The newly created sample detail.
|
|
988
|
-
"""
|
|
989
|
-
return self.add_sample_details(step, [sample], wrapper_type)[0]
|
|
990
|
-
|
|
991
|
-
def add_sample_details(self, step: Step, samples: Iterable[RecordModel],
|
|
992
|
-
wrapper_type: type[WrappedType] | None = None) \
|
|
993
|
-
-> list[WrappedType] | list[PyRecordModel]:
|
|
994
|
-
"""
|
|
995
|
-
Add sample details to a sample details entry while relating them to the input sample records.
|
|
996
|
-
|
|
997
|
-
:param step:
|
|
998
|
-
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
999
|
-
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1000
|
-
:param samples: The sample records to add the sample details to.
|
|
1001
|
-
:param wrapper_type: Optionally wrap the sample details in a record model wrapper. If not provided, returns
|
|
1002
|
-
an unwrapped PyRecordModel.
|
|
1003
|
-
:return: The newly created sample details. The indices of the samples in the input list match the index of the
|
|
1004
|
-
sample details in this list that they are related to.
|
|
1005
|
-
"""
|
|
1006
|
-
step: ElnEntryStep = self.get_step(step)
|
|
1007
|
-
if step.eln_entry.entry_type != ElnEntryType.Table:
|
|
1008
|
-
raise SapioException("The provided step is not a table entry.")
|
|
1009
|
-
dt: str = step.get_data_type_names()[0]
|
|
1010
|
-
if not ElnBaseDataType.is_eln_type(dt) or ElnBaseDataType.get_base_type(dt) != ElnBaseDataType.SAMPLE_DETAIL:
|
|
1011
|
-
raise SapioException("The provided step is not an ELNSampleDetail entry.")
|
|
1012
|
-
records: list[PyRecordModel] = []
|
|
1013
|
-
for sample in samples:
|
|
1014
|
-
if sample.data_type_name != "Sample":
|
|
1015
|
-
raise SapioException(f"Received a {sample.data_type_name} record when Sample records were expected.")
|
|
1016
|
-
detail: PyRecordModel = sample.add(Child.create_by_name(dt))
|
|
1017
|
-
detail.set_field_values({
|
|
1018
|
-
"SampleId": sample.get_field_value("SampleId"),
|
|
1019
|
-
"OtherSampleId": sample.get_field_value("OtherSampleId")
|
|
1020
|
-
})
|
|
1021
|
-
records.append(detail)
|
|
1022
|
-
if wrapper_type:
|
|
1023
|
-
return self._inst_man.wrap_list(records, wrapper_type)
|
|
1024
|
-
return records
|
|
1025
|
-
|
|
1026
|
-
def remove_eln_row(self, step: Step, record: SapioRecord) -> None:
|
|
899
|
+
def add_eln_row(self, step: Step, wrapper_type: type[WrappedType] | None = None) -> WrappedType | PyRecordModel:
|
|
1027
900
|
"""
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
table entry is equivalent to deleting the records for the rows.
|
|
1031
|
-
|
|
1032
|
-
The row will not be deleted in the system until a record manager store and commit has occurred.
|
|
901
|
+
Add a row to an ELNExperimentDetail or ELNSampleDetail table entry. The row will not appear in the system
|
|
902
|
+
until a record manager store and commit has occurred.
|
|
1033
903
|
|
|
1034
904
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
1035
905
|
list of steps in the experiment and caches them.
|
|
@@ -1037,13 +907,13 @@ class ExperimentHandler:
|
|
|
1037
907
|
:param step:
|
|
1038
908
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
1039
909
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1040
|
-
:param record
|
|
1041
|
-
|
|
1042
|
-
|
|
910
|
+
:param wrapper_type: Optionally wrap the ELN data type in a record model wrapper. If not provided, returns
|
|
911
|
+
an unwrapped PyRecordModel.
|
|
912
|
+
:return: The newly created row.
|
|
1043
913
|
"""
|
|
1044
|
-
self.
|
|
914
|
+
return self.add_eln_rows(step, 1, wrapper_type)[0]
|
|
1045
915
|
|
|
1046
|
-
def remove_eln_rows(self, step: Step, records:
|
|
916
|
+
def remove_eln_rows(self, step: Step, records: list[SapioRecord]) -> None:
|
|
1047
917
|
"""
|
|
1048
918
|
Remove rows from an ELNExperimentDetail or ELNSampleDetail table entry. ELN data type table entries display all
|
|
1049
919
|
records in the system that match the entry's data type. This means that removing rows from an ELN data type
|
|
@@ -1061,7 +931,7 @@ class ExperimentHandler:
|
|
|
1061
931
|
A list of records to remove from the given step.
|
|
1062
932
|
The records may be provided as either DataRecords, PyRecordModels, or WrappedRecordModels.
|
|
1063
933
|
"""
|
|
1064
|
-
step
|
|
934
|
+
step = self.__to_eln_step(step)
|
|
1065
935
|
dt: str = step.get_data_type_names()[0]
|
|
1066
936
|
if not ElnBaseDataType.is_eln_type(dt):
|
|
1067
937
|
raise SapioException("The provided step is not an ELN data type entry.")
|
|
@@ -1084,6 +954,61 @@ class ExperimentHandler:
|
|
|
1084
954
|
for record in record_models:
|
|
1085
955
|
record.delete()
|
|
1086
956
|
|
|
957
|
+
def remove_eln_row(self, step: Step, record: SapioRecord) -> None:
|
|
958
|
+
"""
|
|
959
|
+
Remove a row from an ELNExperimentDetail or ELNSampleDetail table entry. ELN data type table entries display all
|
|
960
|
+
records in the system that match the entry's data type. This means that removing rows from an ELN data type
|
|
961
|
+
table entry is equivalent to deleting the records for the rows.
|
|
962
|
+
|
|
963
|
+
The row will not be deleted in the system until a record manager store and commit has occurred.
|
|
964
|
+
|
|
965
|
+
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
966
|
+
list of steps in the experiment and caches them.
|
|
967
|
+
|
|
968
|
+
:param step:
|
|
969
|
+
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
970
|
+
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
971
|
+
:param record:
|
|
972
|
+
The record to remove from the given step.
|
|
973
|
+
The record may be provided as either a DataRecord, PyRecordModel, or WrappedRecordModel.
|
|
974
|
+
"""
|
|
975
|
+
self.remove_eln_rows(step, [record])
|
|
976
|
+
|
|
977
|
+
def add_sample_details(self, step: Step, samples: list[RecordModel],
|
|
978
|
+
wrapper_type: type[WrappedType] | None = None) \
|
|
979
|
+
-> list[WrappedType] | list[PyRecordModel]:
|
|
980
|
+
"""
|
|
981
|
+
Add sample details to a sample details entry while relating them to the input sample records.
|
|
982
|
+
|
|
983
|
+
:param step:
|
|
984
|
+
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
985
|
+
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
986
|
+
:param samples: The sample records to add the sample details to.
|
|
987
|
+
:param wrapper_type: Optionally wrap the sample details in a record model wrapper. If not provided, returns
|
|
988
|
+
an unwrapped PyRecordModel.
|
|
989
|
+
:return: The newly created sample details. The indices of the samples in the input list match the index of the
|
|
990
|
+
sample details in this list that they are related to.
|
|
991
|
+
"""
|
|
992
|
+
step = self.__to_eln_step(step)
|
|
993
|
+
if step.eln_entry.entry_type != ElnEntryType.Table:
|
|
994
|
+
raise SapioException("The provided step is not a table entry.")
|
|
995
|
+
dt: str = step.get_data_type_names()[0]
|
|
996
|
+
if not ElnBaseDataType.is_eln_type(dt) or ElnBaseDataType.get_base_type(dt) != ElnBaseDataType.SAMPLE_DETAIL:
|
|
997
|
+
raise SapioException("The provided step is not an ELNSampleDetail entry.")
|
|
998
|
+
records: list[PyRecordModel] = []
|
|
999
|
+
for sample in samples:
|
|
1000
|
+
if sample.data_type_name != "Sample":
|
|
1001
|
+
raise SapioException(f"Received a {sample.data_type_name} record when Sample records were expected.")
|
|
1002
|
+
detail: PyRecordModel = sample.add(Child.create_by_name(dt))
|
|
1003
|
+
detail.set_field_values({
|
|
1004
|
+
"SampleId": sample.get_field_value("SampleId"),
|
|
1005
|
+
"OtherSampleId": sample.get_field_value("OtherSampleId")
|
|
1006
|
+
})
|
|
1007
|
+
records.append(detail)
|
|
1008
|
+
if wrapper_type:
|
|
1009
|
+
return self._inst_man.wrap_list(records, wrapper_type)
|
|
1010
|
+
return records
|
|
1011
|
+
|
|
1087
1012
|
# noinspection PyPep8Naming
|
|
1088
1013
|
def update_step(self, step: Step,
|
|
1089
1014
|
entry_name: str | None = None,
|
|
@@ -1151,45 +1076,128 @@ class ExperimentHandler:
|
|
|
1151
1076
|
If you wish to add options to the existing map of options that an entry has, use the
|
|
1152
1077
|
add_step_options method.
|
|
1153
1078
|
"""
|
|
1154
|
-
# FR-47468: Deprecating this since the
|
|
1155
|
-
|
|
1079
|
+
# FR-47468: Deprecating this since the parameters are ordered. The new method requires keyword parameters, so
|
|
1080
|
+
# that we can add new parameters wherever we want without breaking existing code.
|
|
1081
|
+
warnings.warn("Update step is deprecated. Use force_entry_update_params instead.",
|
|
1156
1082
|
DeprecationWarning)
|
|
1157
|
-
|
|
1158
|
-
|
|
1083
|
+
self.force_step_update_params(step,
|
|
1084
|
+
entry_name=entry_name,
|
|
1085
|
+
related_entry_set=related_entry_set,
|
|
1086
|
+
dependency_set=dependency_set,
|
|
1087
|
+
entry_status=entry_status,
|
|
1088
|
+
order=order,
|
|
1089
|
+
description=description,
|
|
1090
|
+
requires_grabber_plugin=requires_grabber_plugin,
|
|
1091
|
+
is_initialization_required=is_initialization_required,
|
|
1092
|
+
notebook_experiment_tab_id=notebook_experiment_tab_id,
|
|
1093
|
+
entry_height=entry_height,
|
|
1094
|
+
column_order=column_order,
|
|
1095
|
+
column_span=column_span,
|
|
1096
|
+
is_removable=is_removable,
|
|
1097
|
+
is_renamable=is_renamable,
|
|
1098
|
+
source_entry_id=source_entry_id,
|
|
1099
|
+
clear_source_entry_id=clear_source_entry_id,
|
|
1100
|
+
is_hidden=is_hidden,
|
|
1101
|
+
is_static_view=is_static_View,
|
|
1102
|
+
is_shown_in_template=is_shown_in_template,
|
|
1103
|
+
template_item_fulfilled_timestamp=template_item_fulfilled_timestamp,
|
|
1104
|
+
clear_template_item_fulfilled_timestamp=clear_template_item_fulfilled_timestamp,
|
|
1105
|
+
entry_options_map=entry_options_map)
|
|
1159
1106
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1107
|
+
# FR-47468: Some functions that can help with entry updates.
|
|
1108
|
+
def force_step_update_params(self, step: Step, *,
|
|
1109
|
+
entry_name: str | None = None,
|
|
1110
|
+
related_entry_set: Iterable[int] | None = None,
|
|
1111
|
+
dependency_set: Iterable[int] | None = None,
|
|
1112
|
+
entry_status: ExperimentEntryStatus | None = None,
|
|
1113
|
+
order: int | None = None,
|
|
1114
|
+
description: str | None = None,
|
|
1115
|
+
requires_grabber_plugin: bool | None = None,
|
|
1116
|
+
is_initialization_required: bool | None = None,
|
|
1117
|
+
notebook_experiment_tab_id: int | None = None,
|
|
1118
|
+
entry_height: int | None = None,
|
|
1119
|
+
column_order: int | None = None,
|
|
1120
|
+
column_span: int | None = None,
|
|
1121
|
+
is_removable: bool | None = None,
|
|
1122
|
+
is_renamable: bool | None = None,
|
|
1123
|
+
source_entry_id: int | None = None,
|
|
1124
|
+
clear_source_entry_id: bool | None = None,
|
|
1125
|
+
is_hidden: bool | None = None,
|
|
1126
|
+
is_static_view: bool | None = None,
|
|
1127
|
+
is_shown_in_template: bool | None = None,
|
|
1128
|
+
template_item_fulfilled_timestamp: int | None = None,
|
|
1129
|
+
clear_template_item_fulfilled_timestamp: bool | None = None,
|
|
1130
|
+
entry_options_map: dict[str, str] | None = None) -> None:
|
|
1131
|
+
"""
|
|
1132
|
+
Immediately sent an update to an entry in this experiment. All changes will be reflected by the ExperimentEntry
|
|
1133
|
+
of the Step that is being updated.
|
|
1166
1134
|
|
|
1167
|
-
update
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
update.order = order
|
|
1172
|
-
update.description = description
|
|
1173
|
-
update.requires_grabber_plugin = requires_grabber_plugin
|
|
1174
|
-
update.is_initialization_required = is_initialization_required
|
|
1175
|
-
update.notebook_experiment_tab_id = notebook_experiment_tab_id
|
|
1176
|
-
update.entry_height = entry_height
|
|
1177
|
-
update.column_order = column_order
|
|
1178
|
-
update.column_span = column_span
|
|
1179
|
-
update.is_removable = is_removable
|
|
1180
|
-
update.is_renamable = is_renamable
|
|
1181
|
-
update.source_entry_id = source_entry_id
|
|
1182
|
-
update.clear_source_entry_id = clear_source_entry_id
|
|
1183
|
-
update.is_hidden = is_hidden
|
|
1184
|
-
update.is_static_View = is_static_View
|
|
1185
|
-
update.is_shown_in_template = is_shown_in_template
|
|
1186
|
-
update.template_item_fulfilled_timestamp = template_item_fulfilled_timestamp
|
|
1187
|
-
update.clear_template_item_fulfilled_timestamp = clear_template_item_fulfilled_timestamp
|
|
1188
|
-
update.entry_options_map = entry_options_map
|
|
1135
|
+
Consider using store_step_update and commit_step_updates instead if the update does not need to be immediate.
|
|
1136
|
+
|
|
1137
|
+
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
1138
|
+
list of steps in the experiment and caches them.
|
|
1189
1139
|
|
|
1140
|
+
:param step:
|
|
1141
|
+
The entry step to update.
|
|
1142
|
+
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
1143
|
+
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1144
|
+
:param entry_name: The new name of this entry.
|
|
1145
|
+
:param related_entry_set: The new set of entry IDs for the entries that are related (implicitly dependent) to
|
|
1146
|
+
this entry. Completely overwrites the existing related entries.
|
|
1147
|
+
:param dependency_set: The new set of entry IDs for the entries that are dependent (explicitly dependent) on
|
|
1148
|
+
this entry. Completely overwrites the existing dependent entries.
|
|
1149
|
+
:param entry_status: The new status of this entry.
|
|
1150
|
+
:param order: The row order of this entry in its tab.
|
|
1151
|
+
:param description: The new description of this entry.
|
|
1152
|
+
:param requires_grabber_plugin: Whether this entry's initialization is handled by a grabber plugin. If true,
|
|
1153
|
+
then is_initialization_required is forced to true by the server.
|
|
1154
|
+
:param is_initialization_required: Whether the user is required to manually initialize this entry.
|
|
1155
|
+
:param notebook_experiment_tab_id: The ID of the tab that this entry should appear on.
|
|
1156
|
+
:param entry_height: The height of this entry.
|
|
1157
|
+
:param column_order: The column order of this entry.
|
|
1158
|
+
:param column_span: How many columns this entry spans.
|
|
1159
|
+
:param is_removable: Whether this entry can be removed by the user.
|
|
1160
|
+
:param is_renamable: Whether this entry can be renamed by the user.
|
|
1161
|
+
:param source_entry_id: The ID of this entry from its template.
|
|
1162
|
+
:param clear_source_entry_id: True if the source entry ID should be cleared.
|
|
1163
|
+
:param is_hidden: Whether this entry is hidden from the user.
|
|
1164
|
+
:param is_static_view: Whether this entry is static. Static entries are uneditable and shared across all
|
|
1165
|
+
experiments of the same template.
|
|
1166
|
+
:param is_shown_in_template: Whether this entry is saved to and shown in the experiment's template.
|
|
1167
|
+
:param template_item_fulfilled_timestamp: A timestamp in milliseconds for when this entry was initialized.
|
|
1168
|
+
:param clear_template_item_fulfilled_timestamp: True if the template item fulfilled timestamp should be cleared,
|
|
1169
|
+
uninitializing the entry.
|
|
1170
|
+
:param entry_options_map:
|
|
1171
|
+
The new map of options for this entry. Completely overwrites the existing options map.
|
|
1172
|
+
Any changes to the entry options will update this ExperimentHandler's cache of entry options.
|
|
1173
|
+
If you wish to add options to the existing map of options that an entry has, use the
|
|
1174
|
+
add_step_options method.
|
|
1175
|
+
"""
|
|
1176
|
+
update = self._criteria_from_params(step,
|
|
1177
|
+
entry_name=entry_name,
|
|
1178
|
+
related_entry_set=related_entry_set,
|
|
1179
|
+
dependency_set=dependency_set,
|
|
1180
|
+
entry_status=entry_status,
|
|
1181
|
+
order=order,
|
|
1182
|
+
description=description,
|
|
1183
|
+
requires_grabber_plugin=requires_grabber_plugin,
|
|
1184
|
+
is_initialization_required=is_initialization_required,
|
|
1185
|
+
notebook_experiment_tab_id=notebook_experiment_tab_id,
|
|
1186
|
+
entry_height=entry_height,
|
|
1187
|
+
column_order=column_order,
|
|
1188
|
+
column_span=column_span,
|
|
1189
|
+
is_removable=is_removable,
|
|
1190
|
+
is_renamable=is_renamable,
|
|
1191
|
+
source_entry_id=source_entry_id,
|
|
1192
|
+
clear_source_entry_id=clear_source_entry_id,
|
|
1193
|
+
is_hidden=is_hidden,
|
|
1194
|
+
is_static_view=is_static_view,
|
|
1195
|
+
is_shown_in_template=is_shown_in_template,
|
|
1196
|
+
template_item_fulfilled_timestamp=template_item_fulfilled_timestamp,
|
|
1197
|
+
clear_template_item_fulfilled_timestamp=clear_template_item_fulfilled_timestamp,
|
|
1198
|
+
entry_options_map=entry_options_map)
|
|
1190
1199
|
self.force_step_update(step, update)
|
|
1191
1200
|
|
|
1192
|
-
# FR-47468: Some functions that can help with entry updates.
|
|
1193
1201
|
def force_step_update(self, step: Step, update: AbstractElnEntryUpdateCriteria) -> None:
|
|
1194
1202
|
"""
|
|
1195
1203
|
Immediately sent an update to an entry in this experiment. All changes will be reflected by the ExperimentEntry
|
|
@@ -1205,7 +1213,7 @@ class ExperimentHandler:
|
|
|
1205
1213
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1206
1214
|
:param update: The update to make to the step.
|
|
1207
1215
|
"""
|
|
1208
|
-
step
|
|
1216
|
+
step = self.__to_eln_step(step)
|
|
1209
1217
|
self._eln_man.update_experiment_entry(self._exp_id, step.get_id(), update)
|
|
1210
1218
|
self._update_entry_details(step, update)
|
|
1211
1219
|
|
|
@@ -1225,7 +1233,7 @@ class ExperimentHandler:
|
|
|
1225
1233
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1226
1234
|
:param update: The update to make to the step.
|
|
1227
1235
|
"""
|
|
1228
|
-
step
|
|
1236
|
+
step = self.__to_eln_step(step)
|
|
1229
1237
|
if step.eln_entry.entry_type != update.entry_type:
|
|
1230
1238
|
raise SapioException(f"The provided step and update criteria are not of the same entry type. "
|
|
1231
1239
|
f"The step is of type {step.eln_entry.entry_type} and the update criteria is of type "
|
|
@@ -1256,13 +1264,72 @@ class ExperimentHandler:
|
|
|
1256
1264
|
Commit all the stored updates to the entries in this experiment. The updates are made in the order that they
|
|
1257
1265
|
were stored.
|
|
1258
1266
|
"""
|
|
1259
|
-
if not self._step_updates:
|
|
1260
|
-
return
|
|
1261
1267
|
self._eln_man.update_experiment_entries(self._exp_id, self._step_updates)
|
|
1262
1268
|
for step_id, criteria in self._step_updates.items():
|
|
1263
1269
|
self._update_entry_details(self._steps_by_id[step_id], criteria)
|
|
1264
1270
|
self._step_updates.clear()
|
|
1265
1271
|
|
|
1272
|
+
def _criteria_from_params(self, step: Step, *,
|
|
1273
|
+
entry_name: str | None = None,
|
|
1274
|
+
related_entry_set: Iterable[int] | None = None,
|
|
1275
|
+
dependency_set: Iterable[int] | None = None,
|
|
1276
|
+
entry_status: ExperimentEntryStatus | None = None,
|
|
1277
|
+
order: int | None = None,
|
|
1278
|
+
description: str | None = None,
|
|
1279
|
+
requires_grabber_plugin: bool | None = None,
|
|
1280
|
+
is_initialization_required: bool | None = None,
|
|
1281
|
+
notebook_experiment_tab_id: int | None = None,
|
|
1282
|
+
entry_height: int | None = None,
|
|
1283
|
+
column_order: int | None = None,
|
|
1284
|
+
column_span: int | None = None,
|
|
1285
|
+
is_removable: bool | None = None,
|
|
1286
|
+
is_renamable: bool | None = None,
|
|
1287
|
+
source_entry_id: int | None = None,
|
|
1288
|
+
clear_source_entry_id: bool | None = None,
|
|
1289
|
+
is_hidden: bool | None = None,
|
|
1290
|
+
is_static_view: bool | None = None,
|
|
1291
|
+
is_shown_in_template: bool | None = None,
|
|
1292
|
+
template_item_fulfilled_timestamp: int | None = None,
|
|
1293
|
+
clear_template_item_fulfilled_timestamp: bool | None = None,
|
|
1294
|
+
entry_options_map: dict[str, str] | None = None) -> AbstractElnEntryUpdateCriteria:
|
|
1295
|
+
"""
|
|
1296
|
+
Create an abstract update criteria object from the provided parameters for the given step.
|
|
1297
|
+
"""
|
|
1298
|
+
step: ElnEntryStep = self.__to_eln_step(step)
|
|
1299
|
+
update = AbstractElnEntryUpdateCriteria(step.eln_entry.entry_type)
|
|
1300
|
+
|
|
1301
|
+
# These two variables could be iterables that aren't lists. Convert them to plain
|
|
1302
|
+
# lists, since that's what the update criteria is expecting.
|
|
1303
|
+
if related_entry_set is not None:
|
|
1304
|
+
related_entry_set = list(related_entry_set)
|
|
1305
|
+
if dependency_set is not None:
|
|
1306
|
+
dependency_set = list(dependency_set)
|
|
1307
|
+
|
|
1308
|
+
update.entry_name = entry_name
|
|
1309
|
+
update.related_entry_set = related_entry_set
|
|
1310
|
+
update.dependency_set = dependency_set
|
|
1311
|
+
update.entry_status = entry_status
|
|
1312
|
+
update.order = order
|
|
1313
|
+
update.description = description
|
|
1314
|
+
update.requires_grabber_plugin = requires_grabber_plugin
|
|
1315
|
+
update.is_initialization_required = is_initialization_required
|
|
1316
|
+
update.notebook_experiment_tab_id = notebook_experiment_tab_id
|
|
1317
|
+
update.entry_height = entry_height
|
|
1318
|
+
update.column_order = column_order
|
|
1319
|
+
update.column_span = column_span
|
|
1320
|
+
update.is_removable = is_removable
|
|
1321
|
+
update.is_renamable = is_renamable
|
|
1322
|
+
update.source_entry_id = source_entry_id
|
|
1323
|
+
update.clear_source_entry_id = clear_source_entry_id
|
|
1324
|
+
update.is_hidden = is_hidden
|
|
1325
|
+
update.is_static_View = is_static_view
|
|
1326
|
+
update.is_shown_in_template = is_shown_in_template
|
|
1327
|
+
update.template_item_fulfilled_timestamp = template_item_fulfilled_timestamp
|
|
1328
|
+
update.clear_template_item_fulfilled_timestamp = clear_template_item_fulfilled_timestamp
|
|
1329
|
+
update.entry_options_map = entry_options_map
|
|
1330
|
+
|
|
1331
|
+
return update
|
|
1332
|
+
|
|
1266
1333
|
@staticmethod
|
|
1267
1334
|
def _merge_updates(new_update: AbstractElnEntryUpdateCriteria, old_update: AbstractElnEntryUpdateCriteria) -> None:
|
|
1268
1335
|
"""
|
|
@@ -1280,10 +1347,10 @@ class ExperimentHandler:
|
|
|
1280
1347
|
entry: ExperimentEntry = step.eln_entry
|
|
1281
1348
|
if update.entry_name is not None:
|
|
1282
1349
|
# PR-46477 - Ensure that the previous name of the updated entry already existed in the cache.
|
|
1283
|
-
if entry.entry_name in self.
|
|
1284
|
-
self.
|
|
1350
|
+
if entry.entry_name in self._steps:
|
|
1351
|
+
self._steps.pop(entry.entry_name)
|
|
1285
1352
|
entry.entry_name = update.entry_name
|
|
1286
|
-
self.
|
|
1353
|
+
self._steps.update({update.entry_name: step})
|
|
1287
1354
|
if update.related_entry_set is not None:
|
|
1288
1355
|
entry.related_entry_id_set = update.related_entry_set
|
|
1289
1356
|
if update.dependency_set is not None:
|
|
@@ -1424,7 +1491,7 @@ class ExperimentHandler:
|
|
|
1424
1491
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1425
1492
|
:return: The map of options for the input step.
|
|
1426
1493
|
"""
|
|
1427
|
-
step
|
|
1494
|
+
step = self.__to_eln_step(step)
|
|
1428
1495
|
if step not in self._step_options:
|
|
1429
1496
|
self._step_options.update(ExperimentReportUtil.get_experiment_entry_options(self.user,
|
|
1430
1497
|
self.get_all_steps()))
|
|
@@ -1447,16 +1514,12 @@ class ExperimentHandler:
|
|
|
1447
1514
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
1448
1515
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1449
1516
|
:param mapping: The new options and values to add to the existing step options, provided as some Mapping
|
|
1450
|
-
(e.g. a
|
|
1451
|
-
|
|
1517
|
+
(e.g. a Dict). If an option key already exists and is provided in the mapping, overwrites the existing value
|
|
1518
|
+
for that key.
|
|
1452
1519
|
"""
|
|
1453
|
-
# PR-47698: Convert the given step to an ElnEntryStep if it is not already one.
|
|
1454
|
-
step: ElnEntryStep = self.get_step(step)
|
|
1455
1520
|
options: dict[str, str] = self.get_step_options(step)
|
|
1456
1521
|
options.update(mapping)
|
|
1457
|
-
|
|
1458
|
-
update.entry_options_map = options
|
|
1459
|
-
self.force_step_update(step, update)
|
|
1522
|
+
self.force_step_update_params(step, entry_options_map=options)
|
|
1460
1523
|
|
|
1461
1524
|
def initialize_step(self, step: Step) -> None:
|
|
1462
1525
|
"""
|
|
@@ -1472,11 +1535,9 @@ class ExperimentHandler:
|
|
|
1472
1535
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1473
1536
|
"""
|
|
1474
1537
|
# Avoid unnecessary calls if the step is already initialized.
|
|
1475
|
-
step: ElnEntryStep = self.
|
|
1538
|
+
step: ElnEntryStep = self.__to_eln_step(step)
|
|
1476
1539
|
if step.eln_entry.template_item_fulfilled_timestamp is None:
|
|
1477
|
-
|
|
1478
|
-
update.template_item_fulfilled_timestamp = TimeUtil.now_in_millis()
|
|
1479
|
-
self.force_step_update(step, update)
|
|
1540
|
+
self.force_step_update_params(step, template_item_fulfilled_timestamp=TimeUtil.now_in_millis())
|
|
1480
1541
|
|
|
1481
1542
|
def uninitialize_step(self, step: Step) -> None:
|
|
1482
1543
|
"""
|
|
@@ -1492,11 +1553,9 @@ class ExperimentHandler:
|
|
|
1492
1553
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1493
1554
|
"""
|
|
1494
1555
|
# Avoid unnecessary calls if the step is already uninitialized.
|
|
1495
|
-
step: ElnEntryStep = self.
|
|
1556
|
+
step: ElnEntryStep = self.__to_eln_step(step)
|
|
1496
1557
|
if step.eln_entry.template_item_fulfilled_timestamp is not None:
|
|
1497
|
-
|
|
1498
|
-
update.clear_template_item_fulfilled_timestamp = True
|
|
1499
|
-
self.force_step_update(step, update)
|
|
1558
|
+
self.force_step_update_params(step, clear_template_item_fulfilled_timestamp=True)
|
|
1500
1559
|
|
|
1501
1560
|
def complete_step(self, step: Step) -> None:
|
|
1502
1561
|
"""
|
|
@@ -1511,7 +1570,7 @@ class ExperimentHandler:
|
|
|
1511
1570
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
1512
1571
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1513
1572
|
"""
|
|
1514
|
-
step
|
|
1573
|
+
step = self.__to_eln_step(step)
|
|
1515
1574
|
if step.eln_entry.entry_status not in self._ENTRY_COMPLETE_STATUSES:
|
|
1516
1575
|
step.complete_step()
|
|
1517
1576
|
step.eln_entry.entry_status = ExperimentEntryStatus.Completed
|
|
@@ -1529,7 +1588,7 @@ class ExperimentHandler:
|
|
|
1529
1588
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
1530
1589
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1531
1590
|
"""
|
|
1532
|
-
step
|
|
1591
|
+
step = self.__to_eln_step(step)
|
|
1533
1592
|
if step.eln_entry.entry_status in self._ENTRY_LOCKED_STATUSES:
|
|
1534
1593
|
step.unlock_step()
|
|
1535
1594
|
step.eln_entry.entry_status = ExperimentEntryStatus.UnlockedChangesRequired
|
|
@@ -1551,11 +1610,9 @@ class ExperimentHandler:
|
|
|
1551
1610
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
1552
1611
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1553
1612
|
"""
|
|
1554
|
-
step
|
|
1613
|
+
step = self.__to_eln_step(step)
|
|
1555
1614
|
if step.eln_entry.entry_status in self._ENTRY_LOCKED_STATUSES:
|
|
1556
|
-
|
|
1557
|
-
update.entry_status = ExperimentEntryStatus.Disabled
|
|
1558
|
-
self.force_step_update(step, update)
|
|
1615
|
+
self.force_step_update_params(step, entry_status=ExperimentEntryStatus.Disabled)
|
|
1559
1616
|
|
|
1560
1617
|
def step_is_submitted(self, step: Step) -> bool:
|
|
1561
1618
|
"""
|
|
@@ -1570,7 +1627,7 @@ class ExperimentHandler:
|
|
|
1570
1627
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1571
1628
|
:return: True if the step's status is Completed or CompletedApproved. False otherwise.
|
|
1572
1629
|
"""
|
|
1573
|
-
return self.
|
|
1630
|
+
return self.__to_eln_step(step).eln_entry.entry_status in self._ENTRY_COMPLETE_STATUSES
|
|
1574
1631
|
|
|
1575
1632
|
def step_is_locked(self, step: Step) -> bool:
|
|
1576
1633
|
"""
|
|
@@ -1586,7 +1643,7 @@ class ExperimentHandler:
|
|
|
1586
1643
|
:return: True if the step's status is Completed, CompletedApproved, Disabled, LockedAwaitingApproval,
|
|
1587
1644
|
or LockedRejected. False otherwise.
|
|
1588
1645
|
"""
|
|
1589
|
-
return self.
|
|
1646
|
+
return self.__to_eln_step(step).eln_entry.entry_status in self._ENTRY_LOCKED_STATUSES
|
|
1590
1647
|
|
|
1591
1648
|
# FR-47464: Some functions that can help with entry placement.
|
|
1592
1649
|
def get_all_tabs(self) -> list[ElnExperimentTab]:
|
|
@@ -1599,7 +1656,6 @@ class ExperimentHandler:
|
|
|
1599
1656
|
if not self._queried_all_tabs:
|
|
1600
1657
|
self._tabs = self._eln_man.get_tabs_for_experiment(self._exp_id)
|
|
1601
1658
|
self._tabs.sort(key=lambda t: t.tab_order)
|
|
1602
|
-
self._tabs_by_id = {tab.tab_id: tab for tab in self._tabs}
|
|
1603
1659
|
self._tabs_by_name = {tab.tab_name: tab for tab in self._tabs}
|
|
1604
1660
|
return self._tabs
|
|
1605
1661
|
|
|
@@ -1633,34 +1689,21 @@ class ExperimentHandler:
|
|
|
1633
1689
|
self.add_tab_to_cache(tab)
|
|
1634
1690
|
return tab
|
|
1635
1691
|
|
|
1636
|
-
def get_tab(self,
|
|
1692
|
+
def get_tab(self, tab_name: str) -> ElnExperimentTab:
|
|
1637
1693
|
"""
|
|
1638
1694
|
Return the tab with the input name.
|
|
1639
1695
|
|
|
1640
1696
|
If no tab functions have been called before and a tab is being searched for by name, queries for the
|
|
1641
1697
|
list of tabs in the experiment and caches them.
|
|
1642
1698
|
|
|
1643
|
-
:param
|
|
1644
|
-
:
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
self.get_all_tabs()
|
|
1650
|
-
eln_tab = self._tabs_by_name.get(tab)
|
|
1651
|
-
elif isinstance(tab, int):
|
|
1652
|
-
# The given integer is expected to be 1-indexed, but we read from the list with a 0-index.
|
|
1653
|
-
tab -= 1
|
|
1654
|
-
tabs = self.get_all_tabs()
|
|
1655
|
-
eln_tab = tabs[tab] if len(tabs) > tab else None
|
|
1656
|
-
else:
|
|
1657
|
-
raise SapioException(f"Tab must be a string or an integer, not {type(tab)}.")
|
|
1658
|
-
if eln_tab is None and exception_on_none:
|
|
1659
|
-
raise SapioException(f"No tab with the name\\order \"{tab}\" exists in this experiment.")
|
|
1660
|
-
return eln_tab
|
|
1699
|
+
:param tab_name: The name of the tab to get.
|
|
1700
|
+
:return: The tab with the input name.
|
|
1701
|
+
"""
|
|
1702
|
+
if tab_name not in self._tabs_by_name:
|
|
1703
|
+
self.get_all_tabs()
|
|
1704
|
+
return self._tabs_by_name[tab_name]
|
|
1661
1705
|
|
|
1662
|
-
def get_steps_in_tab(self, tab: Tab, data_type: DataTypeIdentifier | None = None)
|
|
1663
|
-
-> list[ElnEntryStep]:
|
|
1706
|
+
def get_steps_in_tab(self, tab: Tab, data_type: DataTypeIdentifier | None = None) -> list[ElnEntryStep]:
|
|
1664
1707
|
"""
|
|
1665
1708
|
Get all the steps in the input tab sorted in order of appearance.
|
|
1666
1709
|
|
|
@@ -1670,8 +1713,7 @@ class ExperimentHandler:
|
|
|
1670
1713
|
If the steps in the experiment have not been queried before, queries for the list of steps in the experiment
|
|
1671
1714
|
and caches them.
|
|
1672
1715
|
|
|
1673
|
-
:param tab: The tab to get the steps of.
|
|
1674
|
-
The order is 1-indexed.
|
|
1716
|
+
:param tab: The tab or tab name to get the steps of.
|
|
1675
1717
|
:param data_type: The data type to filter the steps by. If None, all steps are returned.
|
|
1676
1718
|
:return: A list of all the steps in the input tab sorted in order of appearance.
|
|
1677
1719
|
"""
|
|
@@ -1680,30 +1722,9 @@ class ExperimentHandler:
|
|
|
1680
1722
|
for step in self.get_all_steps(data_type):
|
|
1681
1723
|
if step.eln_entry.notebook_experiment_tab_id == tab.tab_id:
|
|
1682
1724
|
steps.append(step)
|
|
1725
|
+
steps.sort(key=lambda s: (s.eln_entry.order, s.eln_entry.column_order))
|
|
1683
1726
|
return steps
|
|
1684
1727
|
|
|
1685
|
-
def get_tab_for_step(self, step: Step) -> ElnExperimentTab:
|
|
1686
|
-
"""
|
|
1687
|
-
Get the tab that a particular step is located in.
|
|
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
|
-
If the steps in the experiment have not been queried before, queries for the list of steps in the experiment
|
|
1693
|
-
and caches them.
|
|
1694
|
-
|
|
1695
|
-
:param step:
|
|
1696
|
-
The step to get the position of.
|
|
1697
|
-
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
1698
|
-
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1699
|
-
:return: The tab that the input step is located in.
|
|
1700
|
-
"""
|
|
1701
|
-
step: ElnEntryStep = self.get_step(step)
|
|
1702
|
-
tab_id = step.eln_entry.notebook_experiment_tab_id
|
|
1703
|
-
if tab_id not in self._tabs_by_id:
|
|
1704
|
-
self.get_all_tabs()
|
|
1705
|
-
return self._tabs_by_id.get(tab_id)
|
|
1706
|
-
|
|
1707
1728
|
def get_next_entry_order_in_tab(self, tab: Tab) -> int:
|
|
1708
1729
|
"""
|
|
1709
1730
|
Get the next available order for a new entry in the input tab.
|
|
@@ -1714,81 +1735,419 @@ class ExperimentHandler:
|
|
|
1714
1735
|
If the steps in the experiment have not been queried before, queries for the list of steps in the experiment
|
|
1715
1736
|
and caches them.
|
|
1716
1737
|
|
|
1717
|
-
:param tab: The tab to get the steps of.
|
|
1718
|
-
The order is 1-indexed.
|
|
1738
|
+
:param tab: The tab or tab name to get the steps of.
|
|
1719
1739
|
:return: The next available order for a new entry in the input tab.
|
|
1720
1740
|
"""
|
|
1721
1741
|
steps = self.get_steps_in_tab(tab)
|
|
1722
1742
|
return steps[-1].eln_entry.order + 1 if steps else 0
|
|
1723
1743
|
|
|
1724
|
-
# FR-
|
|
1725
|
-
def
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
:param
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
:return: The
|
|
1737
|
-
"""
|
|
1738
|
-
step
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
+
# FR-47468: Add functions for creating new entries in the experiment.
|
|
1745
|
+
def create_attachment_step(self, entry_name: str, data_type: DataTypeIdentifier,
|
|
1746
|
+
*,
|
|
1747
|
+
position: ElnEntryPosition | None = None,
|
|
1748
|
+
attachments: list[EntryAttachment] | list[SapioRecord] | None = None) -> ElnEntryStep:
|
|
1749
|
+
"""
|
|
1750
|
+
Create a new attachment entry in the experiment.
|
|
1751
|
+
|
|
1752
|
+
:param entry_name: The name of the entry.
|
|
1753
|
+
:param data_type: The data type of the entry.
|
|
1754
|
+
:param position: Information about where to place the entry in the experiment.
|
|
1755
|
+
:param attachments: The list of attachments to initially populate the entry with.
|
|
1756
|
+
:return: The newly created attachment entry.
|
|
1757
|
+
"""
|
|
1758
|
+
step = self._create_step(ElnEntryType.Attachment, entry_name, data_type, position)
|
|
1759
|
+
if attachments:
|
|
1760
|
+
entry_attachments: list[EntryAttachment] = []
|
|
1761
|
+
for entry in attachments:
|
|
1762
|
+
if isinstance(entry, EntryAttachment):
|
|
1763
|
+
entry_attachments.append(entry)
|
|
1764
|
+
elif isinstance(entry, SapioRecord):
|
|
1765
|
+
entry: SapioRecord
|
|
1766
|
+
file_name: str = entry.get_field_value("FilePath")
|
|
1767
|
+
if not file_name:
|
|
1768
|
+
file_name = entry.get_field_value(SystemFields.DATA_RECORD_NAME__FIELD.field_name)
|
|
1769
|
+
rec_id: int = AliasUtil.to_record_id(entry)
|
|
1770
|
+
entry_attachments.append(EntryRecordAttachment(file_name, rec_id))
|
|
1771
|
+
else:
|
|
1772
|
+
raise SapioException("Attachments must be of type EntryAttachment or SapioRecord.")
|
|
1773
|
+
update = ElnAttachmentEntryUpdateCriteria()
|
|
1774
|
+
update.entry_attachment_list = attachments
|
|
1775
|
+
self.force_step_update(step, update)
|
|
1776
|
+
return step
|
|
1777
|
+
|
|
1778
|
+
def create_form_step(self, entry_name: str,
|
|
1779
|
+
data_type: DataTypeIdentifier,
|
|
1780
|
+
fields: list[FieldIdentifier] | None = None,
|
|
1781
|
+
layout_name: str | None = None,
|
|
1782
|
+
form_names: list[str] | None = None,
|
|
1783
|
+
*,
|
|
1784
|
+
position: ElnEntryPosition | None = None,
|
|
1785
|
+
record: SapioRecord | None = None) -> ElnEntryStep:
|
|
1786
|
+
"""
|
|
1787
|
+
Create a new form entry in the experiment.
|
|
1788
|
+
|
|
1789
|
+
:param entry_name: The name of the entry.
|
|
1790
|
+
:param data_type: The data type of the entry.
|
|
1791
|
+
:param fields: The list of data field names for the given data type that should appear on the
|
|
1792
|
+
form. Fields will appear in the order they are provided. If not provided and a layout name is not provided,
|
|
1793
|
+
the entry will be created with the data type's default layout.
|
|
1794
|
+
:param layout_name: The name of the layout to use for the entry. If not provided and field names are not
|
|
1795
|
+
provided, the entry will be created with the data type's default layout.
|
|
1796
|
+
:param form_names: The list of layout component form names to use from the specified layout name. If not
|
|
1797
|
+
provided, then all available forms from the specified layout will be used.
|
|
1798
|
+
:param position: Information about where to place the entry in the experiment.
|
|
1799
|
+
:param record: The record to initially populate the entry with.
|
|
1800
|
+
:return: The newly created form entry.
|
|
1801
|
+
"""
|
|
1802
|
+
if record:
|
|
1803
|
+
rdt: str = AliasUtil.to_data_type_name(record)
|
|
1804
|
+
sdt: str = AliasUtil.to_data_type_name(data_type)
|
|
1805
|
+
if rdt != sdt:
|
|
1806
|
+
raise SapioException(f"Cannot set {rdt} records for entry {entry_name} of type "
|
|
1807
|
+
f"{sdt}.")
|
|
1808
|
+
|
|
1809
|
+
step = self._create_step(ElnEntryType.Form, entry_name, data_type, position)
|
|
1810
|
+
if fields or layout_name or form_names or record:
|
|
1811
|
+
update = ElnFormEntryUpdateCriteria()
|
|
1812
|
+
if fields:
|
|
1813
|
+
update.data_field_name_list = AliasUtil.to_data_field_names(fields)
|
|
1814
|
+
update.data_type_layout_name = layout_name
|
|
1815
|
+
update.form_name_list = form_names
|
|
1816
|
+
if record:
|
|
1817
|
+
update.record_id = AliasUtil.to_record_id(record)
|
|
1818
|
+
self.force_step_update(step, update)
|
|
1819
|
+
return step
|
|
1820
|
+
|
|
1821
|
+
def create_experiment_detail_form_step(self, entry_name: str,
|
|
1822
|
+
fields: list[ElnDataTypeFields] | None = None,
|
|
1823
|
+
field_map: FieldMap | None = None,
|
|
1824
|
+
*,
|
|
1825
|
+
position: ElnEntryPosition | None = None,
|
|
1826
|
+
is_field_addable: bool | None = None,
|
|
1827
|
+
is_existing_field_removable: bool | None = None) -> ElnEntryStep:
|
|
1828
|
+
"""
|
|
1829
|
+
Create a new ELN experiment details form entry in the experiment.
|
|
1830
|
+
|
|
1831
|
+
:param entry_name: The name of the entry.
|
|
1832
|
+
:param fields: A list of objects representing the data fields that should appear on the entry. Fields
|
|
1833
|
+
will appear in the order they are provided.
|
|
1834
|
+
:param field_map: A field map that will be used to populate the entry. The data field names in
|
|
1835
|
+
the map must match the field names of the provided field definitions.
|
|
1836
|
+
:param position: Information about where to place the entry in the experiment.
|
|
1837
|
+
:param is_field_addable: Whether users are able to add additional fields to this entry in the UI.
|
|
1838
|
+
:param is_existing_field_removable: Whether users are able to remove existing fields from this entry in the UI.
|
|
1839
|
+
:return: The newly created form entry.
|
|
1840
|
+
"""
|
|
1841
|
+
return self._create_eln_dt_form_step(entry_name, ElnBaseDataType.EXPERIMENT_DETAIL, fields, field_map,
|
|
1842
|
+
position=position,
|
|
1843
|
+
is_field_addable=is_field_addable,
|
|
1844
|
+
is_existing_field_removable=is_existing_field_removable)
|
|
1845
|
+
|
|
1846
|
+
def create_sample_detail_form_step(self, entry_name: str,
|
|
1847
|
+
fields: list[ElnDataTypeFields] | None = None,
|
|
1848
|
+
field_map: FieldMap | None = None,
|
|
1849
|
+
*,
|
|
1850
|
+
position: ElnEntryPosition | None = None,
|
|
1851
|
+
is_field_addable: bool | None = None,
|
|
1852
|
+
is_existing_field_removable: bool | None = None) -> ElnEntryStep:
|
|
1853
|
+
"""
|
|
1854
|
+
Create a new ELN sample details form entry in the experiment.
|
|
1855
|
+
|
|
1856
|
+
:param entry_name: The name of the entry.
|
|
1857
|
+
:param fields: A list of objects representing the data fields that should appear on the entry. Fields
|
|
1858
|
+
will appear in the order they are provided.
|
|
1859
|
+
:param field_map: A field map that will be used to populate the entry. The data field names in
|
|
1860
|
+
the map must match the field names of the provided field definitions.
|
|
1861
|
+
:param position: Information about where to place the entry in the experiment.
|
|
1862
|
+
:param is_field_addable: Whether users are able to add additional fields to this entry in the UI.
|
|
1863
|
+
:param is_existing_field_removable: Whether users are able to remove existing fields from this entry in the UI.
|
|
1864
|
+
:return: The newly created form entry.
|
|
1865
|
+
"""
|
|
1866
|
+
return self._create_eln_dt_form_step(entry_name, ElnBaseDataType.SAMPLE_DETAIL, fields, field_map,
|
|
1867
|
+
position=position,
|
|
1868
|
+
is_field_addable=is_field_addable,
|
|
1869
|
+
is_existing_field_removable=is_existing_field_removable)
|
|
1870
|
+
|
|
1871
|
+
def _create_eln_dt_form_step(self, entry_name: str,
|
|
1872
|
+
dt: ElnBaseDataType,
|
|
1873
|
+
fields: list[ElnDataTypeFields] | None = None,
|
|
1874
|
+
field_map: FieldMap | None = None,
|
|
1875
|
+
*,
|
|
1876
|
+
position: ElnEntryPosition | None = None,
|
|
1877
|
+
is_field_addable: bool | None = None,
|
|
1878
|
+
is_existing_field_removable: bool | None = None) -> ElnEntryStep:
|
|
1879
|
+
fields: list[AbstractVeloxFieldDefinition] | None = self._to_field_defs(fields, dt)
|
|
1880
|
+
step = self._create_step(ElnEntryType.Form, entry_name, dt.data_type_name,
|
|
1881
|
+
position,
|
|
1882
|
+
field_definition_list=fields,
|
|
1883
|
+
field_map_list=[field_map] if field_map else None)
|
|
1884
|
+
if is_field_addable is not None or is_existing_field_removable is not None:
|
|
1885
|
+
update = ElnFormEntryUpdateCriteria()
|
|
1886
|
+
update.is_field_addable = is_field_addable
|
|
1887
|
+
update.is_existing_field_removable = is_existing_field_removable
|
|
1888
|
+
self.force_step_update(step, update)
|
|
1889
|
+
return step
|
|
1890
|
+
|
|
1891
|
+
def create_plugin_step(self, entry_name: str, data_type: DataTypeIdentifier, plugin_path: str, *,
|
|
1892
|
+
position: ElnEntryPosition | None = None) -> ElnEntryStep:
|
|
1893
|
+
"""
|
|
1894
|
+
Create a new plugin entry in the experiment.
|
|
1895
|
+
|
|
1896
|
+
:param entry_name: The name of the entry.
|
|
1897
|
+
:param data_type: The data type of the entry.
|
|
1898
|
+
:param plugin_path: The plugin path to the CSP that determines this entry's functionality.
|
|
1899
|
+
:param position: Information about where to place the entry in the experiment.
|
|
1900
|
+
:return: The newly created plugin entry.
|
|
1901
|
+
"""
|
|
1902
|
+
return self._create_step(ElnEntryType.Plugin, entry_name, data_type, position,
|
|
1903
|
+
csp_plugin_name=plugin_path)
|
|
1904
|
+
|
|
1905
|
+
def create_table_step(self, entry_name: str, data_type: DataTypeIdentifier,
|
|
1906
|
+
fields: list[FieldIdentifier] | list[TableColumn] | None = None,
|
|
1907
|
+
layout_name: str | None = None,
|
|
1908
|
+
show_key_fields: bool | None = None,
|
|
1909
|
+
*,
|
|
1910
|
+
position: ElnEntryPosition | None = None,
|
|
1911
|
+
records: list[SapioRecord] | None = None) -> ElnEntryStep:
|
|
1912
|
+
"""
|
|
1913
|
+
Create a new table entry in the experiment.
|
|
1914
|
+
|
|
1915
|
+
:param entry_name: The name of the entry.
|
|
1916
|
+
:param data_type: The data type of the entry.
|
|
1917
|
+
:param fields: The list of data field names for the given data type that should appear on the
|
|
1918
|
+
table. You may also provide a list of TableColumns instead of a list of field names for when you want to
|
|
1919
|
+
set the sort order and direction of the columns in the table. Fields will appear in the order they are
|
|
1920
|
+
provided. If not provided and a layout name is not provided, the entry will be created with the data type's
|
|
1921
|
+
default layout.
|
|
1922
|
+
:param layout_name: The name of the layout to use for the entry. If not provided and field names are not
|
|
1923
|
+
provided, the entry will be created with the data type's default layout.
|
|
1924
|
+
:param show_key_fields: Whether the table should only show key fields of the data type.
|
|
1925
|
+
:param position: Information about where to place the entry in the experiment.
|
|
1926
|
+
:param records: The list of records to initially populate the entry with.
|
|
1927
|
+
:return: The newly created table entry.
|
|
1928
|
+
"""
|
|
1929
|
+
step = self._create_step(ElnEntryType.Table, entry_name, data_type, position)
|
|
1930
|
+
if fields or layout_name:
|
|
1931
|
+
update = ElnTableEntryUpdateCriteria()
|
|
1932
|
+
if fields:
|
|
1933
|
+
dt: str = AliasUtil.to_data_type_name(data_type)
|
|
1934
|
+
columns: list[TableColumn] = []
|
|
1935
|
+
for field in fields:
|
|
1936
|
+
if isinstance(field, TableColumn):
|
|
1937
|
+
columns.append(field)
|
|
1938
|
+
else:
|
|
1939
|
+
columns.append(TableColumn(dt, AliasUtil.to_data_field_name(field)))
|
|
1940
|
+
update.table_column_list = columns
|
|
1941
|
+
update.data_type_layout_name = layout_name
|
|
1942
|
+
update.show_key_fields = show_key_fields
|
|
1943
|
+
self.force_step_update(step, update)
|
|
1944
|
+
if records:
|
|
1945
|
+
self.set_step_records(step, records)
|
|
1946
|
+
return step
|
|
1947
|
+
|
|
1948
|
+
def create_experiment_detail_table_step(self, entry_name: str,
|
|
1949
|
+
fields: list[ElnDataTypeFields] | None = None,
|
|
1950
|
+
field_maps: list[FieldMap] | None = None, *,
|
|
1951
|
+
position: ElnEntryPosition | None = None,
|
|
1952
|
+
is_field_addable: bool | None = None,
|
|
1953
|
+
is_existing_field_removable: bool | None = None) -> ElnEntryStep:
|
|
1954
|
+
"""
|
|
1955
|
+
Create a new ELN experiment details table entry in the experiment.
|
|
1956
|
+
|
|
1957
|
+
:param entry_name: The name of the entry.
|
|
1958
|
+
:param fields: A list of objects representing the data fields that should appear on the entry. Fields
|
|
1959
|
+
will appear in the order they are provided.
|
|
1960
|
+
:param field_maps: A field maps list that will be used to populate the entry. The data field names in
|
|
1961
|
+
the maps must match the field names of the provided field definitions.
|
|
1962
|
+
:param position: Information about where to place the entry in the experiment.
|
|
1963
|
+
:param is_field_addable: Whether users are able to add additional fields to this entry in the UI.
|
|
1964
|
+
:param is_existing_field_removable: Whether users are able to remove existing fields from this entry in the UI.
|
|
1965
|
+
:return: The newly created table entry.
|
|
1966
|
+
"""
|
|
1967
|
+
return self._create_eln_dt_table_step(entry_name, ElnBaseDataType.EXPERIMENT_DETAIL, fields, field_maps,
|
|
1968
|
+
position=position,
|
|
1969
|
+
is_field_addable=is_field_addable,
|
|
1970
|
+
is_existing_field_removable=is_existing_field_removable)
|
|
1971
|
+
|
|
1972
|
+
def create_sample_detail_table_step(self, entry_name: str,
|
|
1973
|
+
fields: list[ElnDataTypeFields] | None = None,
|
|
1974
|
+
field_maps: list[FieldMap] | None = None, *,
|
|
1975
|
+
position: ElnEntryPosition | None = None,
|
|
1976
|
+
is_field_addable: bool | None = None,
|
|
1977
|
+
is_existing_field_removable: bool | None = None) -> ElnEntryStep:
|
|
1978
|
+
"""
|
|
1979
|
+
Create a new ELN sample details table entry in the experiment.
|
|
1980
|
+
|
|
1981
|
+
:param entry_name: The name of the entry.
|
|
1982
|
+
:param fields: A list of objects representing the data fields that should appear on the entry. Fields
|
|
1983
|
+
will appear in the order they are provided.
|
|
1984
|
+
:param field_maps: A field maps list that will be used to populate the entry. The data field names in
|
|
1985
|
+
the maps must match the field names of the provided field definitions.
|
|
1986
|
+
:param position: Information about where to place the entry in the experiment.
|
|
1987
|
+
:param is_field_addable: Whether users are able to add additional fields to this entry in the UI.
|
|
1988
|
+
:param is_existing_field_removable: Whether users are able to remove existing fields from this entry in the UI.
|
|
1989
|
+
:return: The newly created table entry.
|
|
1990
|
+
"""
|
|
1991
|
+
return self._create_eln_dt_table_step(entry_name, ElnBaseDataType.SAMPLE_DETAIL, fields, field_maps,
|
|
1992
|
+
position=position,
|
|
1993
|
+
is_field_addable=is_field_addable,
|
|
1994
|
+
is_existing_field_removable=is_existing_field_removable)
|
|
1995
|
+
|
|
1996
|
+
def _create_eln_dt_table_step(self, entry_name: str,
|
|
1997
|
+
dt: ElnBaseDataType,
|
|
1998
|
+
fields: list[ElnDataTypeFields] | None = None,
|
|
1999
|
+
field_maps: list[FieldMap] | None = None, *,
|
|
2000
|
+
position: ElnEntryPosition | None = None,
|
|
2001
|
+
is_field_addable: bool | None = None,
|
|
2002
|
+
is_existing_field_removable: bool | None = None) -> ElnEntryStep:
|
|
2003
|
+
fields: list[AbstractVeloxFieldDefinition] | None = self._to_field_defs(fields, dt)
|
|
2004
|
+
step = self._create_step(ElnEntryType.Table, entry_name, dt.data_type_name,
|
|
2005
|
+
position,
|
|
2006
|
+
field_definition_list=fields,
|
|
2007
|
+
field_map_list=field_maps)
|
|
2008
|
+
if is_field_addable is not None or is_existing_field_removable is not None:
|
|
2009
|
+
update = ElnTableEntryUpdateCriteria()
|
|
2010
|
+
update.is_field_addable = is_field_addable
|
|
2011
|
+
update.is_existing_field_removable = is_existing_field_removable
|
|
2012
|
+
self.force_step_update(step, update)
|
|
2013
|
+
return step
|
|
2014
|
+
|
|
2015
|
+
def create_temp_data_step(self, entry_name: str, data_type: DataTypeIdentifier, plugin_path: str, *,
|
|
2016
|
+
position: ElnEntryPosition | None = None) -> ElnEntryStep:
|
|
2017
|
+
"""
|
|
2018
|
+
Create a new temp data entry in the experiment.
|
|
2019
|
+
|
|
2020
|
+
:param entry_name: The name of the entry.
|
|
2021
|
+
:param data_type: The data type of the entry.
|
|
2022
|
+
:param plugin_path: The plugin path to the plugin that populates the entry.
|
|
2023
|
+
:param position: Information about where to place the entry in the experiment.
|
|
2024
|
+
:return: The newly created temp data entry.
|
|
2025
|
+
"""
|
|
2026
|
+
return self._create_step(ElnEntryType.TempData, entry_name, data_type, position,
|
|
2027
|
+
temp_data_plugin_path=plugin_path)
|
|
2028
|
+
|
|
2029
|
+
def create_text_step(self, entry_name: str, text: str | None = None, *,
|
|
2030
|
+
position: ElnEntryPosition | None = None) -> ElnEntryStep:
|
|
2031
|
+
"""
|
|
2032
|
+
Create a new text entry in the experiment.
|
|
2033
|
+
|
|
2034
|
+
:param entry_name: The name of the entry.
|
|
2035
|
+
:param text: The text to populate the entry with.
|
|
2036
|
+
:param position: Information about where to place the entry in the experiment.
|
|
2037
|
+
:return: The newly created text entry.
|
|
2038
|
+
"""
|
|
2039
|
+
step: ElnEntryStep = self._create_step(ElnEntryType.Text, entry_name,
|
|
2040
|
+
ElnBaseDataType.TEXT_ENTRY_DETAIL.data_type_name, position)
|
|
2041
|
+
if text:
|
|
2042
|
+
text_record: DataRecord = self.get_step_records(entry_name)[0]
|
|
2043
|
+
text_record.set_field_value(ElnBaseDataType.get_text_entry_data_field_name(), text)
|
|
2044
|
+
DataRecordManager(self.user).commit_data_records([text_record])
|
|
2045
|
+
return step
|
|
2046
|
+
|
|
2047
|
+
def create_plate_designer_step(self, entry_name: str, source_entry: Step, *,
|
|
2048
|
+
position: ElnEntryPosition | None = None) -> ElnEntryStep:
|
|
2049
|
+
"""
|
|
2050
|
+
Create a new 3D plate designer entry in the experiment.
|
|
2051
|
+
|
|
2052
|
+
:param entry_name: The name of the entry.
|
|
2053
|
+
:param source_entry: The entry that the plate designer will source its samples from.
|
|
2054
|
+
:param position: Information about where to place the entry in the experiment.
|
|
2055
|
+
:return: The newly created plate designer entry.
|
|
2056
|
+
"""
|
|
2057
|
+
step = self.create_plugin_step(entry_name, "Sample", PLATE_DESIGNER_PLUGIN, position=position)
|
|
2058
|
+
default_layer = MultiLayerPlateLayer(
|
|
2059
|
+
MultiLayerDataTypeConfig("Sample"),
|
|
2060
|
+
PlatingOrder.FillBy.BY_COLUMN,
|
|
2061
|
+
MultiLayerReplicateConfig(),
|
|
2062
|
+
MultiLayerDilutionConfig()
|
|
2063
|
+
)
|
|
2064
|
+
initial_step_options: dict[str, str] = {
|
|
2065
|
+
"MultiLayerPlating_Plate_RecordIdList": "",
|
|
2066
|
+
"MultiLayerPlating_Entry_Prefs": MultiLayerPlatingManager.get_entry_prefs_json([default_layer]),
|
|
2067
|
+
"MultiLayerPlating_Entry_PrePlating_Prefs": MultiLayerPlatingManager.get_plate_configs_json(MultiLayerPlateConfig())
|
|
2068
|
+
}
|
|
2069
|
+
source_entry = self.__to_eln_step(source_entry)
|
|
2070
|
+
self.force_step_update_params(step, entry_height=600, entry_options_map=initial_step_options,
|
|
2071
|
+
related_entry_set=[source_entry.get_id()])
|
|
2072
|
+
return step
|
|
2073
|
+
|
|
2074
|
+
# TODO: Update these functions to use entry type-specific creation criteria once Sapiopylib supports that.
|
|
2075
|
+
# This should take in an entry creation criteria object that handles all the abstract attributes that
|
|
2076
|
+
# every entry type shares.
|
|
2077
|
+
def _create_step(self, entry_type: ElnEntryType, entry_name: str, data_type: DataTypeIdentifier,
|
|
2078
|
+
position: ElnEntryPosition | None = None, **kwargs) \
|
|
2079
|
+
-> ElnEntryStep:
|
|
2080
|
+
"""
|
|
2081
|
+
Create a new entry in the experiment of the given type.
|
|
2082
|
+
"""
|
|
2083
|
+
if position is not None:
|
|
2084
|
+
order: int = position.order
|
|
2085
|
+
tab_id: int = position.tab_id
|
|
2086
|
+
column_order: int = position.column_order
|
|
2087
|
+
column_span: int = position.column_span
|
|
2088
|
+
else:
|
|
2089
|
+
last_tab: ElnExperimentTab = self.get_last_tab()
|
|
2090
|
+
order: int = self.get_next_entry_order_in_tab(last_tab)
|
|
2091
|
+
tab_id: int = last_tab.tab_id
|
|
2092
|
+
column_order: int = 0
|
|
2093
|
+
column_span: int = last_tab.max_number_of_columns
|
|
1744
2094
|
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
2095
|
+
data_type: str = AliasUtil.to_data_type_name(data_type)
|
|
2096
|
+
crit = ElnEntryCriteria(entry_type, entry_name, data_type, order,
|
|
2097
|
+
notebook_experiment_tab_id=tab_id,
|
|
2098
|
+
column_order=column_order,
|
|
2099
|
+
column_span=column_span,
|
|
2100
|
+
**kwargs)
|
|
2101
|
+
entry: ExperimentEntry = self._eln_man.add_experiment_entry(self._exp_id, crit)
|
|
1748
2102
|
|
|
1749
|
-
|
|
1750
|
-
|
|
2103
|
+
self.add_entry_to_caches(entry)
|
|
2104
|
+
return ElnEntryStep(self._protocol, entry)
|
|
1751
2105
|
|
|
1752
|
-
|
|
1753
|
-
|
|
2106
|
+
def _to_field_defs(self, fields: list[ElnDataTypeFields], dt: ElnBaseDataType) \
|
|
2107
|
+
-> list[AbstractVeloxFieldDefinition] | None:
|
|
1754
2108
|
"""
|
|
1755
|
-
|
|
1756
|
-
raise SapioException("The provided position must at least have a tab ID and order.")
|
|
1757
|
-
for step in self.get_steps_in_tab(position.tab_id):
|
|
1758
|
-
entry: ExperimentEntry = step.eln_entry
|
|
1759
|
-
if entry.order != position.order:
|
|
1760
|
-
continue
|
|
1761
|
-
if position.column_span is not None and entry.column_span != position.column_span:
|
|
1762
|
-
continue
|
|
1763
|
-
if position.column_order is not None and entry.column_order != position.column_order:
|
|
1764
|
-
continue
|
|
1765
|
-
return step
|
|
1766
|
-
return None
|
|
1767
|
-
|
|
1768
|
-
# FR-47530: Create a function for adding protocol templates to the experiment.
|
|
1769
|
-
def add_protocol(self, protocol: ProtocolTemplateInfo | int, position: ElnEntryPosition) -> list[ElnEntryStep]:
|
|
2109
|
+
Convert a list of ElnDataTypeField aliases to field definitions.
|
|
1770
2110
|
"""
|
|
1771
|
-
|
|
2111
|
+
if not fields:
|
|
2112
|
+
return None
|
|
2113
|
+
field_defs: list[AbstractVeloxFieldDefinition] = []
|
|
2114
|
+
for field in fields:
|
|
2115
|
+
if isinstance(field, AbstractVeloxFieldDefinition):
|
|
2116
|
+
field_defs.append(field)
|
|
2117
|
+
elif isinstance(field, ElnFieldSetInfo):
|
|
2118
|
+
field_defs.extend(self._eln_man.get_predefined_fields_from_field_set_id(field.field_set_id))
|
|
2119
|
+
elif isinstance(field, str):
|
|
2120
|
+
field_defs.append(self._predefined_field(field, dt))
|
|
2121
|
+
elif isinstance(field, int):
|
|
2122
|
+
field_defs.extend(self._eln_man.get_predefined_fields_from_field_set_id(field))
|
|
2123
|
+
return field_defs
|
|
2124
|
+
|
|
2125
|
+
def _predefined_field(self, field_name: str, data_type: ElnBaseDataType) -> AbstractVeloxFieldDefinition:
|
|
2126
|
+
"""
|
|
2127
|
+
Get the predefined field of the given name for the given ELN data type.
|
|
2128
|
+
"""
|
|
2129
|
+
# TODO: Should this be in some sort of DataTypeCacheManager?
|
|
2130
|
+
if data_type not in self._predefined_fields:
|
|
2131
|
+
fields: list[AbstractVeloxFieldDefinition] = self._eln_man.get_predefined_fields(data_type)
|
|
2132
|
+
self._predefined_fields[data_type.data_type_name] = {x.data_field_name: x for x in fields}
|
|
2133
|
+
return self._predefined_fields[data_type.data_type_name][field_name]
|
|
2134
|
+
|
|
2135
|
+
def __to_eln_step(self, step: Step) -> ElnEntryStep:
|
|
2136
|
+
"""
|
|
2137
|
+
Convert a variable that could be either a string or an ElnEntryStep to just an ElnEntryStep.
|
|
2138
|
+
This will query and cache the steps for the experiment if the input step is a name and the steps have not been
|
|
2139
|
+
cached before.
|
|
1772
2140
|
|
|
1773
|
-
:
|
|
1774
|
-
protocol template.
|
|
1775
|
-
:param position: The position that the protocol's first entry will be placed at.
|
|
1776
|
-
:return: The newly created protocol entries.
|
|
2141
|
+
:return: The input step as an ElnEntryStep.
|
|
1777
2142
|
"""
|
|
1778
|
-
|
|
1779
|
-
new_entries: list[ExperimentEntry] = self._eln_man.add_protocol_template(self._exp_id, protocol, position)
|
|
1780
|
-
return self.add_entries_to_caches(new_entries)
|
|
1781
|
-
|
|
1782
|
-
# CR-47700: Deleted __to_eln_step since it became redundant with the get_step method.
|
|
2143
|
+
return self.get_step(step) if isinstance(step, str) else step
|
|
1783
2144
|
|
|
1784
2145
|
def __to_eln_tab(self, tab: Tab) -> ElnExperimentTab:
|
|
1785
2146
|
"""
|
|
1786
|
-
Convert a variable that could be either a
|
|
2147
|
+
Convert a variable that could be either a string or an ElnExperimentTab to just an ElnExperimentTab.
|
|
1787
2148
|
This will query and cache the tabs for the experiment if the input tab is a name and the tabs have not been
|
|
1788
2149
|
cached before.
|
|
1789
2150
|
|
|
1790
2151
|
:return: The input tab as an ElnExperimentTab.
|
|
1791
2152
|
"""
|
|
1792
|
-
if
|
|
1793
|
-
return self.get_tab(tab)
|
|
1794
|
-
return tab
|
|
2153
|
+
return self.get_tab(tab) if isinstance(tab, str) else tab
|