sapiopycommons 2024.11.11a364__py3-none-any.whl → 2024.11.18a366__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sapiopycommons might be problematic. Click here for more details.
- sapiopycommons/callbacks/callback_util.py +532 -83
- sapiopycommons/callbacks/field_builder.py +537 -0
- sapiopycommons/chem/IndigoMolecules.py +2 -0
- sapiopycommons/chem/Molecules.py +77 -18
- sapiopycommons/customreport/__init__.py +0 -0
- sapiopycommons/customreport/column_builder.py +60 -0
- sapiopycommons/customreport/custom_report_builder.py +130 -0
- sapiopycommons/customreport/term_builder.py +299 -0
- sapiopycommons/datatype/attachment_util.py +11 -10
- sapiopycommons/datatype/data_fields.py +61 -0
- sapiopycommons/datatype/pseudo_data_types.py +440 -0
- sapiopycommons/eln/experiment_handler.py +272 -70
- sapiopycommons/eln/experiment_report_util.py +653 -0
- sapiopycommons/files/complex_data_loader.py +5 -4
- sapiopycommons/files/file_bridge.py +31 -24
- sapiopycommons/files/file_bridge_handler.py +340 -0
- sapiopycommons/files/file_data_handler.py +2 -5
- sapiopycommons/files/file_util.py +59 -9
- sapiopycommons/files/file_validator.py +92 -6
- sapiopycommons/files/file_writer.py +44 -15
- sapiopycommons/flowcyto/flow_cyto.py +77 -0
- sapiopycommons/flowcyto/flowcyto_data.py +75 -0
- sapiopycommons/general/accession_service.py +375 -0
- sapiopycommons/general/aliases.py +207 -6
- sapiopycommons/general/audit_log.py +189 -0
- sapiopycommons/general/custom_report_util.py +212 -37
- sapiopycommons/general/exceptions.py +21 -8
- sapiopycommons/general/popup_util.py +21 -0
- sapiopycommons/general/sapio_links.py +50 -0
- sapiopycommons/general/time_util.py +8 -2
- sapiopycommons/multimodal/multimodal.py +146 -0
- sapiopycommons/multimodal/multimodal_data.py +490 -0
- sapiopycommons/processtracking/custom_workflow_handler.py +406 -0
- sapiopycommons/processtracking/endpoints.py +22 -22
- sapiopycommons/recordmodel/record_handler.py +481 -97
- sapiopycommons/rules/eln_rule_handler.py +34 -25
- sapiopycommons/rules/on_save_rule_handler.py +34 -31
- sapiopycommons/sftpconnect/__init__.py +0 -0
- sapiopycommons/sftpconnect/sftp_builder.py +69 -0
- sapiopycommons/webhook/webhook_context.py +39 -0
- sapiopycommons/webhook/webhook_handlers.py +201 -42
- sapiopycommons/webhook/webservice_handlers.py +67 -0
- {sapiopycommons-2024.11.11a364.dist-info → sapiopycommons-2024.11.18a366.dist-info}/METADATA +5 -2
- sapiopycommons-2024.11.18a366.dist-info/RECORD +59 -0
- {sapiopycommons-2024.11.11a364.dist-info → sapiopycommons-2024.11.18a366.dist-info}/WHEEL +1 -1
- sapiopycommons-2024.11.11a364.dist-info/RECORD +0 -38
- {sapiopycommons-2024.11.11a364.dist-info → sapiopycommons-2024.11.18a366.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import time
|
|
2
4
|
from collections.abc import Mapping, Iterable
|
|
5
|
+
from weakref import WeakValueDictionary
|
|
3
6
|
|
|
7
|
+
from sapiopylib.rest.DataMgmtService import DataMgmtServer
|
|
4
8
|
from sapiopylib.rest.ELNService import ElnManager
|
|
9
|
+
from sapiopylib.rest.User import SapioUser
|
|
5
10
|
from sapiopylib.rest.pojo.DataRecord import DataRecord
|
|
6
11
|
from sapiopylib.rest.pojo.eln.ElnExperiment import ElnExperiment, TemplateExperimentQueryPojo, ElnTemplate, \
|
|
7
12
|
InitializeNotebookExperimentPojo, ElnExperimentUpdateCriteria
|
|
@@ -16,8 +21,11 @@ from sapiopylib.rest.utils.Protocols import ElnEntryStep, ElnExperimentProtocol
|
|
|
16
21
|
from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
|
|
17
22
|
from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelInstanceManager, RecordModelManager
|
|
18
23
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
|
|
24
|
+
from sapiopylib.rest.utils.recordmodel.properties import Child
|
|
19
25
|
|
|
20
|
-
from sapiopycommons.
|
|
26
|
+
from sapiopycommons.eln.experiment_report_util import ExperimentReportUtil
|
|
27
|
+
from sapiopycommons.general.aliases import AliasUtil, SapioRecord, ExperimentIdentifier, UserIdentifier, \
|
|
28
|
+
DataTypeIdentifier, RecordModel
|
|
21
29
|
from sapiopycommons.general.exceptions import SapioException
|
|
22
30
|
|
|
23
31
|
Step = str | ElnEntryStep
|
|
@@ -27,7 +35,8 @@ itself."""
|
|
|
27
35
|
|
|
28
36
|
# FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
|
|
29
37
|
class ExperimentHandler:
|
|
30
|
-
|
|
38
|
+
user: SapioUser
|
|
39
|
+
context: SapioWebhookContext | None
|
|
31
40
|
"""The context that this handler is working from."""
|
|
32
41
|
|
|
33
42
|
# Basic experiment info from the context.
|
|
@@ -48,9 +57,9 @@ class ExperimentHandler:
|
|
|
48
57
|
# additional queries to obtain, but may also be repeatedly accessed. In such cases, cache the information after it
|
|
49
58
|
# has been requested so that the user doesn't need to worry about caching it themselves.
|
|
50
59
|
# CR-46341: Replace class variables with instance variables.
|
|
51
|
-
__exp_record: DataRecord
|
|
60
|
+
__exp_record: DataRecord | None
|
|
52
61
|
"""The data record for this experiment. Only cached when first accessed."""
|
|
53
|
-
__exp_template: ElnTemplate
|
|
62
|
+
__exp_template: ElnTemplate | None
|
|
54
63
|
"""The template for this experiment. Only cached when first accessed."""
|
|
55
64
|
__exp_options: dict[str, str]
|
|
56
65
|
"""Experiment options for this experiment. Only cached when first accessed."""
|
|
@@ -59,9 +68,9 @@ class ExperimentHandler:
|
|
|
59
68
|
"""Whether this ExperimentHandler has queried the system for all steps in the experiment."""
|
|
60
69
|
__steps: dict[str, ElnEntryStep]
|
|
61
70
|
"""Steps from this experiment. All steps are cached the first time any individual step is accessed."""
|
|
62
|
-
__step_options: dict[
|
|
63
|
-
"""Entry options for each step in this experiment.
|
|
64
|
-
The cache is updated whenever the entry options for a step are changed by this handler."""
|
|
71
|
+
__step_options: dict[int, dict[str, str]]
|
|
72
|
+
"""Entry options for each step in this experiment. All entry options are cached the first time any individual step's
|
|
73
|
+
options are queried. The cache is updated whenever the entry options for a step are changed by this handler."""
|
|
65
74
|
|
|
66
75
|
# Constants
|
|
67
76
|
__ENTRY_COMPLETE_STATUSES = [ExperimentEntryStatus.Completed, ExperimentEntryStatus.CompletedApproved]
|
|
@@ -77,45 +86,104 @@ class ExperimentHandler:
|
|
|
77
86
|
ElnExperimentStatus.Canceled]
|
|
78
87
|
"""The set of statuses that an ELN experiment could have and be considered locked."""
|
|
79
88
|
|
|
80
|
-
|
|
89
|
+
__instances: WeakValueDictionary[str, ExperimentHandler] = WeakValueDictionary()
|
|
90
|
+
__initialized: bool
|
|
91
|
+
|
|
92
|
+
def __new__(cls, context: UserIdentifier, experiment: ExperimentIdentifier | SapioRecord | None = None):
|
|
93
|
+
"""
|
|
94
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
95
|
+
:param experiment: If an experiment is provided that is separate from the experiment that is in the context,
|
|
96
|
+
that experiment will be used by this ExperimentHandler instead. An experiment can be provided in various
|
|
97
|
+
forms, including an ElnExperiment, ElnExperimentProtocol, an experiment record, or a notebook experiment ID.
|
|
98
|
+
"""
|
|
99
|
+
param_results = cls.__parse_params(context, experiment)
|
|
100
|
+
user = param_results[0]
|
|
101
|
+
experiment = param_results[2]
|
|
102
|
+
key = f"{user.__hash__()}:{experiment.notebook_experiment_id}"
|
|
103
|
+
obj = cls.__instances.get(key)
|
|
104
|
+
if not obj:
|
|
105
|
+
obj = object.__new__(cls)
|
|
106
|
+
obj.__initialized = False
|
|
107
|
+
cls.__instances[key] = obj
|
|
108
|
+
return obj
|
|
109
|
+
|
|
110
|
+
def __init__(self, context: UserIdentifier, experiment: ExperimentIdentifier | SapioRecord | None = None):
|
|
81
111
|
"""
|
|
82
112
|
Initialization will throw an exception if there is no ELN Experiment in the provided context and no experiment
|
|
83
113
|
is provided.
|
|
84
114
|
|
|
85
|
-
:param context: The current webhook context.
|
|
115
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
86
116
|
:param experiment: If an experiment is provided that is separate from the experiment that is in the context,
|
|
87
|
-
that experiment will be used by this ExperimentHandler instead.
|
|
117
|
+
that experiment will be used by this ExperimentHandler instead. An experiment can be provided in various
|
|
118
|
+
forms, including an ElnExperiment, ElnExperimentProtocol, an experiment record, or a notebook experiment ID.
|
|
88
119
|
"""
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if context.eln_experiment == experiment:
|
|
94
|
-
experiment = None
|
|
95
|
-
self.context = context
|
|
120
|
+
param_results = self.__parse_params(context, experiment)
|
|
121
|
+
self.user = param_results[0]
|
|
122
|
+
self.context = param_results[1]
|
|
123
|
+
experiment = param_results[2]
|
|
96
124
|
|
|
97
125
|
# Get the basic information about this experiment that already exists in the context and is often used.
|
|
98
|
-
self.__eln_exp = experiment
|
|
99
|
-
self.__protocol = ElnExperimentProtocol(experiment,
|
|
126
|
+
self.__eln_exp = experiment
|
|
127
|
+
self.__protocol = ElnExperimentProtocol(experiment, self.user)
|
|
100
128
|
self.__exp_id = self.__protocol.get_id()
|
|
101
129
|
|
|
102
130
|
# Grab various managers that may be used.
|
|
103
|
-
self.__eln_man =
|
|
104
|
-
self.__inst_man = RecordModelManager(
|
|
131
|
+
self.__eln_man = DataMgmtServer.get_eln_manager(self.user)
|
|
132
|
+
self.__inst_man = RecordModelManager(self.user).instance_manager
|
|
105
133
|
|
|
106
134
|
# Create empty caches to fill when necessary.
|
|
107
135
|
self.__steps = {}
|
|
108
136
|
self.__step_options = {}
|
|
109
137
|
# CR-46330: Cache any experiment entry information that might already exist in the context.
|
|
110
138
|
self.__queried_all_steps = False
|
|
111
|
-
# We can only trust the entries in the context if
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
139
|
+
# We can only trust the entries in the context if the experiment that this handler is for is the same as the
|
|
140
|
+
# one from the context.
|
|
141
|
+
if self.context is not None and self.context.eln_experiment == experiment:
|
|
142
|
+
if self.context.experiment_entry is not None:
|
|
143
|
+
self.__steps.update({self.context.active_step.get_name(): self.context.active_step})
|
|
144
|
+
if self.context.experiment_entry_list is not None:
|
|
145
|
+
for entry in self.context.experiment_entry_list:
|
|
117
146
|
self.__steps.update({entry.entry_name: ElnEntryStep(self.__protocol, entry)})
|
|
118
147
|
|
|
148
|
+
@staticmethod
|
|
149
|
+
def __parse_params(context: UserIdentifier, experiment: ExperimentIdentifier | SapioRecord | None = None) \
|
|
150
|
+
-> tuple[SapioUser, SapioWebhookContext | None, ElnExperiment]:
|
|
151
|
+
if isinstance(context, SapioWebhookContext):
|
|
152
|
+
user = context.user
|
|
153
|
+
context = context
|
|
154
|
+
else:
|
|
155
|
+
user = context
|
|
156
|
+
context = None
|
|
157
|
+
if context is not None and context.eln_experiment is not None and experiment is None:
|
|
158
|
+
experiment = context.eln_experiment
|
|
159
|
+
# FR-46495 - Allow the init function of ExperimentHandler to take in an ElnExperiment that is separate from the
|
|
160
|
+
# context.
|
|
161
|
+
# CR-37038 - Allow other experiment object types to be provided. Convert them all down to ElnExperiment.
|
|
162
|
+
if (context is None or context.eln_experiment is None) and experiment is not None:
|
|
163
|
+
eln_manager = DataMgmtServer.get_eln_manager(user)
|
|
164
|
+
# If this object is already an ElnExperiment, do nothing.
|
|
165
|
+
if isinstance(experiment, ElnExperiment):
|
|
166
|
+
pass
|
|
167
|
+
# If this object is an ElnExperimentProtocol, then we can get the ElnExperiment from the object.
|
|
168
|
+
elif isinstance(experiment, ElnExperimentProtocol):
|
|
169
|
+
experiment: ElnExperiment = experiment.eln_experiment
|
|
170
|
+
# If this object is an integer, assume it is a notebook ID that we can query the system with.
|
|
171
|
+
elif isinstance(experiment, int):
|
|
172
|
+
notebook_id: int = experiment
|
|
173
|
+
experiment: ElnExperiment = eln_manager.get_eln_experiment_by_id(notebook_id)
|
|
174
|
+
if not experiment:
|
|
175
|
+
raise SapioException(f"No experiment with notebook ID {notebook_id} located in the system.")
|
|
176
|
+
# If this object is a record, assume it is an experiment record that we can query the system with.
|
|
177
|
+
else:
|
|
178
|
+
record_id: int = AliasUtil.to_record_ids([experiment])[0]
|
|
179
|
+
experiment: ElnExperiment = eln_manager.get_eln_experiment_by_record_id(record_id)
|
|
180
|
+
if not experiment:
|
|
181
|
+
raise SapioException(f"No experiment with record ID {record_id} located in the system.")
|
|
182
|
+
if experiment is None:
|
|
183
|
+
raise SapioException("Cannot initialize ExperimentHandler. No ELN Experiment found in the provided parameters.")
|
|
184
|
+
|
|
185
|
+
return user, context, experiment
|
|
186
|
+
|
|
119
187
|
# FR-46495: Split the creation of the experiment in launch_experiment into a create_experiment function.
|
|
120
188
|
@staticmethod
|
|
121
189
|
def create_experiment(context: SapioWebhookContext,
|
|
@@ -209,6 +277,7 @@ class ExperimentHandler:
|
|
|
209
277
|
"""
|
|
210
278
|
template_id: int | None = self.__eln_exp.template_id
|
|
211
279
|
if template_id is None:
|
|
280
|
+
self.__exp_template = None
|
|
212
281
|
if exception_on_none:
|
|
213
282
|
raise SapioException(f"Experiment with ID {self.__exp_id} has no template ID.")
|
|
214
283
|
return None
|
|
@@ -263,11 +332,7 @@ class ExperimentHandler:
|
|
|
263
332
|
:return: The data record for this experiment. None if it has no record.
|
|
264
333
|
"""
|
|
265
334
|
if not hasattr(self, "_ExperimentHandler__exp_record"):
|
|
266
|
-
|
|
267
|
-
dt = self.__eln_exp.experiment_data_type_name
|
|
268
|
-
results = drm.query_data_records_by_id(dt, [self.__eln_exp.experiment_record_id]).result_list
|
|
269
|
-
# PR-46504: Set the exp_record to None if there are no results.
|
|
270
|
-
self.__exp_record = results[0] if results else None
|
|
335
|
+
self.__exp_record = self.__protocol.get_record()
|
|
271
336
|
if self.__exp_record is None and exception_on_none:
|
|
272
337
|
raise SapioException(f"Experiment record not found for experiment with ID {self.__exp_id}.")
|
|
273
338
|
return self.__exp_record
|
|
@@ -467,7 +532,7 @@ class ExperimentHandler:
|
|
|
467
532
|
ret_list.append(step)
|
|
468
533
|
return ret_list
|
|
469
534
|
|
|
470
|
-
def get_all_steps(self, data_type:
|
|
535
|
+
def get_all_steps(self, data_type: DataTypeIdentifier | None = None) -> list[ElnEntryStep]:
|
|
471
536
|
"""
|
|
472
537
|
Get a list of every entry in the experiment. Optionally filter the returned entries by a data type.
|
|
473
538
|
|
|
@@ -483,10 +548,42 @@ class ExperimentHandler:
|
|
|
483
548
|
all_steps: list[ElnEntryStep] = self.__protocol.get_sorted_step_list()
|
|
484
549
|
if data_type is None:
|
|
485
550
|
return all_steps
|
|
486
|
-
|
|
487
|
-
data_type: str = data_type.get_wrapper_data_type_name()
|
|
551
|
+
data_type: str = AliasUtil.to_data_type_name(data_type)
|
|
488
552
|
return [x for x in all_steps if data_type in x.get_data_type_names()]
|
|
489
553
|
|
|
554
|
+
def get_step_by_option(self, key: str, value: str | None = None) -> ElnEntryStep:
|
|
555
|
+
"""
|
|
556
|
+
Retrieve the step in this experiment that contains an entry option with the provided key and value.
|
|
557
|
+
Throws an exception if no entries or multiple entries in the experiment match.
|
|
558
|
+
|
|
559
|
+
:param key: The key of the entry option to match on.
|
|
560
|
+
:param value: The value of the entry option to match on. If not provided, then only matches on key.
|
|
561
|
+
:return: The entry in this experiment that matches the provided entry option key and value.
|
|
562
|
+
"""
|
|
563
|
+
steps: list[ElnEntryStep] = self.get_steps_by_option(key, value)
|
|
564
|
+
count: int = len(steps)
|
|
565
|
+
if count != 1:
|
|
566
|
+
option = key + ("::" + value if value is not None else "")
|
|
567
|
+
raise SapioException(f"{('No' if count == 0 else 'Multiple')} entries in this experiment match the "
|
|
568
|
+
f"provided option: {option}")
|
|
569
|
+
return steps[0]
|
|
570
|
+
|
|
571
|
+
def get_steps_by_option(self, key: str, value: str | None = None) -> list[ElnEntryStep]:
|
|
572
|
+
"""
|
|
573
|
+
Retrieve every step in this experiment that contains an entry option with the provided key and value.
|
|
574
|
+
|
|
575
|
+
:param key: The key of the entry option to match on.
|
|
576
|
+
:param value: The value of the entry option to match on. If not provided, then only matches on key.
|
|
577
|
+
:return: The entries in this experiment that match the provided entry option key and value.
|
|
578
|
+
"""
|
|
579
|
+
ret_list: list[ElnEntryStep] = []
|
|
580
|
+
for step in self.get_all_steps():
|
|
581
|
+
options: dict[str, str] = self.get_step_options(step)
|
|
582
|
+
if key in options:
|
|
583
|
+
if value is None or options[key] == value:
|
|
584
|
+
ret_list.append(step)
|
|
585
|
+
return ret_list
|
|
586
|
+
|
|
490
587
|
def get_step_records(self, step: Step) -> list[DataRecord]:
|
|
491
588
|
"""
|
|
492
589
|
Query for the data records for the given step. The returned records are not cached by the ExperimentHandler.
|
|
@@ -536,6 +633,15 @@ class ExperimentHandler:
|
|
|
536
633
|
The records may be provided as either DataRecords, PyRecordModels, or WrappedRecordModels.
|
|
537
634
|
"""
|
|
538
635
|
step = self.__to_eln_step(step)
|
|
636
|
+
if not records:
|
|
637
|
+
return
|
|
638
|
+
dt: str = AliasUtil.to_singular_data_type_name(records)
|
|
639
|
+
if ElnBaseDataType.is_base_data_type(dt):
|
|
640
|
+
raise SapioException(f"{dt} is an ELN data type. This function call has no effect on ELN data types. "
|
|
641
|
+
f"Use add_eln_rows or add_sample_details instead.")
|
|
642
|
+
if dt != step.get_data_type_names()[0]:
|
|
643
|
+
raise SapioException(f"Cannot add {dt} records to entry {step.get_name()} of type "
|
|
644
|
+
f"{step.get_data_type_names()[0]}.")
|
|
539
645
|
step.add_records(AliasUtil.to_data_records(records))
|
|
540
646
|
|
|
541
647
|
def remove_step_records(self, step: Step, records: Iterable[SapioRecord]) -> None:
|
|
@@ -554,6 +660,15 @@ class ExperimentHandler:
|
|
|
554
660
|
The records may be provided as either DataRecords, PyRecordModels, or WrappedRecordModels.
|
|
555
661
|
"""
|
|
556
662
|
step = self.__to_eln_step(step)
|
|
663
|
+
if not records:
|
|
664
|
+
return
|
|
665
|
+
dt: str = AliasUtil.to_singular_data_type_name(records)
|
|
666
|
+
if ElnBaseDataType.is_base_data_type(dt):
|
|
667
|
+
raise SapioException(f"{dt} is an ELN data type. This function call has no effect on ELN data types. "
|
|
668
|
+
f"Use remove_eln_rows or remove_sample_details instead.")
|
|
669
|
+
if dt != step.get_data_type_names()[0]:
|
|
670
|
+
raise SapioException(f"Cannot remove {dt} records from entry {step.get_name()} of type "
|
|
671
|
+
f"{step.get_data_type_names()[0]}.")
|
|
557
672
|
step.remove_records(AliasUtil.to_data_records(records))
|
|
558
673
|
|
|
559
674
|
def set_step_records(self, step: Step, records: Iterable[SapioRecord]) -> None:
|
|
@@ -577,11 +692,17 @@ class ExperimentHandler:
|
|
|
577
692
|
The records may be provided as either DataRecords, PyRecordModels, or WrappedRecordModels.
|
|
578
693
|
"""
|
|
579
694
|
step = self.__to_eln_step(step)
|
|
695
|
+
if records:
|
|
696
|
+
dt: str = AliasUtil.to_singular_data_type_name(records)
|
|
697
|
+
if ElnBaseDataType.is_base_data_type(dt):
|
|
698
|
+
raise SapioException(f"{dt} is an ELN data type. This function call has no effect on ELN data types. "
|
|
699
|
+
f"Use add_eln_rows or add_sample_details instead.")
|
|
700
|
+
if dt != step.get_data_type_names()[0]:
|
|
701
|
+
raise SapioException(f"Cannot set {dt} records for entry {step.get_name()} of type "
|
|
702
|
+
f"{step.get_data_type_names()[0]}.")
|
|
580
703
|
step.set_records(AliasUtil.to_data_records(records))
|
|
581
704
|
|
|
582
705
|
# FR-46496 - Provide alias of set_step_records for use with form entries.
|
|
583
|
-
# TODO: Provide a similar aliased function for attachment entries once sapiopylib allows setting multiple
|
|
584
|
-
# attachments to an attachment step.
|
|
585
706
|
def set_form_record(self, step: Step, record: SapioRecord) -> None:
|
|
586
707
|
"""
|
|
587
708
|
Sets the record for a form entry.
|
|
@@ -599,7 +720,8 @@ class ExperimentHandler:
|
|
|
599
720
|
self.set_step_records(step, [record])
|
|
600
721
|
|
|
601
722
|
# FR-46496 - Provide functions for adding and removing rows from an ELN data type entry.
|
|
602
|
-
def add_eln_rows(self, step: Step, count: int
|
|
723
|
+
def add_eln_rows(self, step: Step, count: int, wrapper_type: type[WrappedType] | None = None) \
|
|
724
|
+
-> list[PyRecordModel | WrappedType]:
|
|
603
725
|
"""
|
|
604
726
|
Add rows to an ELNExperimentDetail or ELNSampleDetail table entry. The rows will not appear in the system
|
|
605
727
|
until a record manager store and commit has occurred.
|
|
@@ -611,15 +733,37 @@ class ExperimentHandler:
|
|
|
611
733
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
612
734
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
613
735
|
:param count: The number of new rows to add to the entry.
|
|
736
|
+
:param wrapper_type: Optionally wrap the ELN data type in a record model wrapper. If not provided, returns
|
|
737
|
+
an unwrapped PyRecordModel.
|
|
614
738
|
:return: A list of the newly created rows.
|
|
615
739
|
"""
|
|
616
740
|
step = self.__to_eln_step(step)
|
|
617
741
|
if step.eln_entry.entry_type != ElnEntryType.Table:
|
|
618
742
|
raise SapioException("The provided step is not a table entry.")
|
|
619
743
|
dt: str = step.get_data_type_names()[0]
|
|
620
|
-
if not
|
|
744
|
+
if not ElnBaseDataType.is_eln_type(dt):
|
|
621
745
|
raise SapioException("The provided step is not an ELN data type entry.")
|
|
622
|
-
|
|
746
|
+
records: list[PyRecordModel] = self.__inst_man.add_new_records(dt, count)
|
|
747
|
+
if wrapper_type:
|
|
748
|
+
return self.__inst_man.wrap_list(records, wrapper_type)
|
|
749
|
+
return records
|
|
750
|
+
|
|
751
|
+
def add_eln_row(self, step: Step, wrapper_type: type[WrappedType] | None = None) -> PyRecordModel | WrappedType:
|
|
752
|
+
"""
|
|
753
|
+
Add a row to an ELNExperimentDetail or ELNSampleDetail table entry. The row will not appear in the system
|
|
754
|
+
until a record manager store and commit has occurred.
|
|
755
|
+
|
|
756
|
+
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
757
|
+
list of steps in the experiment and caches them.
|
|
758
|
+
|
|
759
|
+
:param step:
|
|
760
|
+
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
761
|
+
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
762
|
+
:param wrapper_type: Optionally wrap the ELN data type in a record model wrapper. If not provided, returns
|
|
763
|
+
an unwrapped PyRecordModel.
|
|
764
|
+
:return: The newly created row.
|
|
765
|
+
"""
|
|
766
|
+
return self.add_eln_rows(step, 1, wrapper_type)[0]
|
|
623
767
|
|
|
624
768
|
def remove_eln_rows(self, step: Step, records: list[SapioRecord]) -> None:
|
|
625
769
|
"""
|
|
@@ -641,10 +785,14 @@ class ExperimentHandler:
|
|
|
641
785
|
"""
|
|
642
786
|
step = self.__to_eln_step(step)
|
|
643
787
|
dt: str = step.get_data_type_names()[0]
|
|
644
|
-
if not
|
|
788
|
+
if not ElnBaseDataType.is_eln_type(dt):
|
|
645
789
|
raise SapioException("The provided step is not an ELN data type entry.")
|
|
646
|
-
if
|
|
647
|
-
|
|
790
|
+
if not records:
|
|
791
|
+
return
|
|
792
|
+
record_dt: str = AliasUtil.to_singular_data_type_name(records, False)
|
|
793
|
+
if record_dt != dt:
|
|
794
|
+
raise SapioException(f"Cannot remove {dt} records from entry {step.get_name()} of type "
|
|
795
|
+
f"{step.get_data_type_names()[0]}.")
|
|
648
796
|
# If any rows were provided as data records, turn them into record models before deleting them, as otherwise
|
|
649
797
|
# this function would need to make a webservice call to do the deletion.
|
|
650
798
|
data_records: list[DataRecord] = []
|
|
@@ -658,16 +806,59 @@ class ExperimentHandler:
|
|
|
658
806
|
for record in record_models:
|
|
659
807
|
record.delete()
|
|
660
808
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
809
|
+
def remove_eln_row(self, step: Step, record: SapioRecord) -> None:
|
|
810
|
+
"""
|
|
811
|
+
Remove a row from an ELNExperimentDetail or ELNSampleDetail table entry. ELN data type table entries display all
|
|
812
|
+
records in the system that match the entry's data type. This means that removing rows from an ELN data type
|
|
813
|
+
table entry is equivalent to deleting the records for the rows.
|
|
814
|
+
|
|
815
|
+
The row will not be deleted in the system until a record manager store and commit has occurred.
|
|
816
|
+
|
|
817
|
+
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
818
|
+
list of steps in the experiment and caches them.
|
|
819
|
+
|
|
820
|
+
:param step:
|
|
821
|
+
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
822
|
+
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
823
|
+
:param record:
|
|
824
|
+
The record to remove from the given step.
|
|
825
|
+
The record may be provided as either a DataRecord, PyRecordModel, or WrappedRecordModel.
|
|
826
|
+
"""
|
|
827
|
+
self.remove_eln_row(step, [record])
|
|
828
|
+
|
|
829
|
+
def add_sample_details(self, step: Step, samples: list[RecordModel], wrapper_type: type[WrappedType]) \
|
|
830
|
+
-> list[PyRecordModel | WrappedType]:
|
|
831
|
+
"""
|
|
832
|
+
Add sample details to a sample details entry while relating them to the input sample records.
|
|
833
|
+
|
|
834
|
+
:param step:
|
|
835
|
+
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
836
|
+
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
837
|
+
:param samples: The sample records to add the sample details to.
|
|
838
|
+
:param wrapper_type: Optionally wrap the sample details in a record model wrapper. If not provided, returns
|
|
839
|
+
an unwrapped PyRecordModel.
|
|
840
|
+
:return: The newly created sample details. The indices of the samples in the input list match the index of the
|
|
841
|
+
sample details in this list that they are related to.
|
|
842
|
+
"""
|
|
843
|
+
step = self.__to_eln_step(step)
|
|
844
|
+
if step.eln_entry.entry_type != ElnEntryType.Table:
|
|
845
|
+
raise SapioException("The provided step is not a table entry.")
|
|
846
|
+
dt: str = step.get_data_type_names()[0]
|
|
847
|
+
if not ElnBaseDataType.is_eln_type(dt) or ElnBaseDataType.get_base_type(dt) != ElnBaseDataType.SAMPLE_DETAIL:
|
|
848
|
+
raise SapioException("The provided step is not an ELNSampleDetail entry.")
|
|
849
|
+
records: list[PyRecordModel] = []
|
|
850
|
+
for sample in samples:
|
|
851
|
+
if sample.data_type_name != "Sample":
|
|
852
|
+
raise SapioException(f"Received a {sample.data_type_name} record when Sample records were expected.")
|
|
853
|
+
detail: PyRecordModel = sample.add(Child.create_by_name(dt))
|
|
854
|
+
detail.set_field_values({
|
|
855
|
+
"SampleId": sample.get_field_value("SampleId"),
|
|
856
|
+
"OtherSampleId": sample.get_field_value("OtherSampleId")
|
|
857
|
+
})
|
|
858
|
+
records.append(detail)
|
|
859
|
+
if wrapper_type:
|
|
860
|
+
return self.__inst_man.wrap_list(records, wrapper_type)
|
|
861
|
+
return records
|
|
671
862
|
|
|
672
863
|
def update_step(self, step: Step,
|
|
673
864
|
entry_name: str | None = None,
|
|
@@ -819,7 +1010,7 @@ class ExperimentHandler:
|
|
|
819
1010
|
if clear_template_item_fulfilled_timestamp is True:
|
|
820
1011
|
entry.template_item_fulfilled_timestamp = None
|
|
821
1012
|
if entry_options_map is not None:
|
|
822
|
-
self.__step_options.update({step: entry_options_map})
|
|
1013
|
+
self.__step_options.update({step.get_id(): entry_options_map})
|
|
823
1014
|
|
|
824
1015
|
def get_step_option(self, step: Step, option: str) -> str:
|
|
825
1016
|
"""
|
|
@@ -849,8 +1040,8 @@ class ExperimentHandler:
|
|
|
849
1040
|
list of steps in the experiment and caches them.
|
|
850
1041
|
|
|
851
1042
|
Getting the step options requires a webservice query, which is made the first time any step option
|
|
852
|
-
method is called for
|
|
853
|
-
method
|
|
1043
|
+
method is called for any step in this experiment. The step options are cached so that subsequent calls of this
|
|
1044
|
+
method don't make a webservice call.
|
|
854
1045
|
|
|
855
1046
|
:param step:
|
|
856
1047
|
The step to get the options of.
|
|
@@ -858,7 +1049,10 @@ class ExperimentHandler:
|
|
|
858
1049
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
859
1050
|
:return: The map of options for the input step.
|
|
860
1051
|
"""
|
|
861
|
-
|
|
1052
|
+
step = self.__to_eln_step(step)
|
|
1053
|
+
if step not in self.__step_options:
|
|
1054
|
+
self.__step_options.update(ExperimentReportUtil.get_experiment_entry_options(self.user, self.get_all_steps()))
|
|
1055
|
+
return self.__step_options[step.get_id()]
|
|
862
1056
|
|
|
863
1057
|
def add_step_options(self, step: Step, mapping: Mapping[str, str]):
|
|
864
1058
|
"""
|
|
@@ -956,6 +1150,27 @@ class ExperimentHandler:
|
|
|
956
1150
|
step.unlock_step()
|
|
957
1151
|
step.eln_entry.entry_status = ExperimentEntryStatus.UnlockedChangesRequired
|
|
958
1152
|
|
|
1153
|
+
def disable_step(self, step: Step) -> None:
|
|
1154
|
+
"""
|
|
1155
|
+
Set the status of the input step to Disabled. This is the state that entries are in when they are waiting for
|
|
1156
|
+
entries that they are dependent upon to be submitted before they can be enabled. If you have unsubmitted an
|
|
1157
|
+
entry and want its dependent entries to be locked again, then you would use this to set their status to
|
|
1158
|
+
disabled.
|
|
1159
|
+
|
|
1160
|
+
Makes a webservice call to update the step. Checks if the step is already unlocked, and does nothing if so.
|
|
1161
|
+
|
|
1162
|
+
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
1163
|
+
list of steps in the experiment and caches them.
|
|
1164
|
+
|
|
1165
|
+
:param step:
|
|
1166
|
+
The step to disable.
|
|
1167
|
+
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
1168
|
+
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1169
|
+
"""
|
|
1170
|
+
step = self.__to_eln_step(step)
|
|
1171
|
+
if step.eln_entry.entry_status in self.__ENTRY_LOCKED_STATUSES:
|
|
1172
|
+
self.update_step(step, entry_status=ExperimentEntryStatus.Disabled)
|
|
1173
|
+
|
|
959
1174
|
def step_is_submitted(self, step: Step) -> bool:
|
|
960
1175
|
"""
|
|
961
1176
|
Determine if the input step has already been submitted.
|
|
@@ -1007,16 +1222,3 @@ class ExperimentHandler:
|
|
|
1007
1222
|
return self.__exp_options
|
|
1008
1223
|
self.__exp_options = self.__eln_man.get_notebook_experiment_options(self.__exp_id)
|
|
1009
1224
|
return self.__exp_options
|
|
1010
|
-
|
|
1011
|
-
def __get_step_options(self, step: Step) -> dict[str, str]:
|
|
1012
|
-
"""
|
|
1013
|
-
Cache the options for the input step if they haven't been cached yet.
|
|
1014
|
-
|
|
1015
|
-
:return: The entry options for the input step.
|
|
1016
|
-
"""
|
|
1017
|
-
step = self.__to_eln_step(step)
|
|
1018
|
-
if step in self.__step_options:
|
|
1019
|
-
return self.__step_options.get(step)
|
|
1020
|
-
options: dict[str, str] = step.get_options()
|
|
1021
|
-
self.__step_options.update({step: options})
|
|
1022
|
-
return options
|