sapiopycommons 2024.3.18a156__py3-none-any.whl → 2025.1.17a402__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/__init__.py +0 -0
- sapiopycommons/callbacks/callback_util.py +2041 -0
- sapiopycommons/callbacks/field_builder.py +545 -0
- sapiopycommons/chem/IndigoMolecules.py +52 -5
- sapiopycommons/chem/Molecules.py +114 -30
- sapiopycommons/customreport/__init__.py +0 -0
- sapiopycommons/customreport/column_builder.py +60 -0
- sapiopycommons/customreport/custom_report_builder.py +137 -0
- sapiopycommons/customreport/term_builder.py +315 -0
- sapiopycommons/datatype/attachment_util.py +17 -15
- sapiopycommons/datatype/data_fields.py +61 -0
- sapiopycommons/datatype/pseudo_data_types.py +440 -0
- sapiopycommons/eln/experiment_handler.py +390 -90
- sapiopycommons/eln/experiment_report_util.py +649 -0
- sapiopycommons/eln/plate_designer.py +152 -0
- sapiopycommons/files/complex_data_loader.py +31 -0
- sapiopycommons/files/file_bridge.py +153 -25
- sapiopycommons/files/file_bridge_handler.py +555 -0
- sapiopycommons/files/file_data_handler.py +633 -0
- sapiopycommons/files/file_util.py +270 -158
- sapiopycommons/files/file_validator.py +569 -0
- sapiopycommons/files/file_writer.py +377 -0
- 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 +259 -18
- sapiopycommons/general/audit_log.py +185 -0
- sapiopycommons/general/custom_report_util.py +252 -31
- sapiopycommons/general/directive_util.py +86 -0
- sapiopycommons/general/exceptions.py +69 -7
- sapiopycommons/general/popup_util.py +85 -18
- sapiopycommons/general/sapio_links.py +50 -0
- sapiopycommons/general/storage_util.py +148 -0
- sapiopycommons/general/time_util.py +97 -7
- sapiopycommons/multimodal/multimodal.py +146 -0
- sapiopycommons/multimodal/multimodal_data.py +490 -0
- sapiopycommons/processtracking/__init__.py +0 -0
- sapiopycommons/processtracking/custom_workflow_handler.py +406 -0
- sapiopycommons/processtracking/endpoints.py +192 -0
- sapiopycommons/recordmodel/record_handler.py +653 -149
- sapiopycommons/rules/eln_rule_handler.py +89 -8
- sapiopycommons/rules/on_save_rule_handler.py +89 -12
- sapiopycommons/sftpconnect/__init__.py +0 -0
- sapiopycommons/sftpconnect/sftp_builder.py +70 -0
- sapiopycommons/webhook/webhook_context.py +39 -0
- sapiopycommons/webhook/webhook_handlers.py +617 -69
- sapiopycommons/webhook/webservice_handlers.py +317 -0
- {sapiopycommons-2024.3.18a156.dist-info → sapiopycommons-2025.1.17a402.dist-info}/METADATA +5 -4
- sapiopycommons-2025.1.17a402.dist-info/RECORD +60 -0
- {sapiopycommons-2024.3.18a156.dist-info → sapiopycommons-2025.1.17a402.dist-info}/WHEEL +1 -1
- sapiopycommons-2024.3.18a156.dist-info/RECORD +0 -28
- {sapiopycommons-2024.3.18a156.dist-info → sapiopycommons-2025.1.17a402.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import time
|
|
2
4
|
from collections.abc import Mapping, Iterable
|
|
5
|
+
from typing import TypeAlias
|
|
6
|
+
from weakref import WeakValueDictionary
|
|
3
7
|
|
|
8
|
+
from sapiopylib.rest.DataMgmtService import DataMgmtServer
|
|
4
9
|
from sapiopylib.rest.ELNService import ElnManager
|
|
10
|
+
from sapiopylib.rest.User import SapioUser
|
|
5
11
|
from sapiopylib.rest.pojo.DataRecord import DataRecord
|
|
6
12
|
from sapiopylib.rest.pojo.eln.ElnExperiment import ElnExperiment, TemplateExperimentQueryPojo, ElnTemplate, \
|
|
7
13
|
InitializeNotebookExperimentPojo, ElnExperimentUpdateCriteria
|
|
@@ -16,18 +22,22 @@ from sapiopylib.rest.utils.Protocols import ElnEntryStep, ElnExperimentProtocol
|
|
|
16
22
|
from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
|
|
17
23
|
from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelInstanceManager, RecordModelManager
|
|
18
24
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
|
|
25
|
+
from sapiopylib.rest.utils.recordmodel.properties import Child
|
|
19
26
|
|
|
20
|
-
from sapiopycommons.
|
|
27
|
+
from sapiopycommons.eln.experiment_report_util import ExperimentReportUtil
|
|
28
|
+
from sapiopycommons.general.aliases import AliasUtil, SapioRecord, ExperimentIdentifier, UserIdentifier, \
|
|
29
|
+
DataTypeIdentifier, RecordModel
|
|
21
30
|
from sapiopycommons.general.exceptions import SapioException
|
|
22
31
|
|
|
23
|
-
Step = str | ElnEntryStep
|
|
32
|
+
Step: TypeAlias = str | ElnEntryStep
|
|
24
33
|
"""An object representing an identifier to an ElnEntryStep. May be either the name of the step or the ElnEntryStep
|
|
25
34
|
itself."""
|
|
26
35
|
|
|
27
36
|
|
|
28
37
|
# FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
|
|
29
38
|
class ExperimentHandler:
|
|
30
|
-
|
|
39
|
+
user: SapioUser
|
|
40
|
+
context: SapioWebhookContext | None
|
|
31
41
|
"""The context that this handler is working from."""
|
|
32
42
|
|
|
33
43
|
# Basic experiment info from the context.
|
|
@@ -48,9 +58,9 @@ class ExperimentHandler:
|
|
|
48
58
|
# additional queries to obtain, but may also be repeatedly accessed. In such cases, cache the information after it
|
|
49
59
|
# has been requested so that the user doesn't need to worry about caching it themselves.
|
|
50
60
|
# CR-46341: Replace class variables with instance variables.
|
|
51
|
-
__exp_record: DataRecord
|
|
61
|
+
__exp_record: DataRecord | None
|
|
52
62
|
"""The data record for this experiment. Only cached when first accessed."""
|
|
53
|
-
__exp_template: ElnTemplate
|
|
63
|
+
__exp_template: ElnTemplate | None
|
|
54
64
|
"""The template for this experiment. Only cached when first accessed."""
|
|
55
65
|
__exp_options: dict[str, str]
|
|
56
66
|
"""Experiment options for this experiment. Only cached when first accessed."""
|
|
@@ -59,59 +69,122 @@ class ExperimentHandler:
|
|
|
59
69
|
"""Whether this ExperimentHandler has queried the system for all steps in the experiment."""
|
|
60
70
|
__steps: dict[str, ElnEntryStep]
|
|
61
71
|
"""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."""
|
|
72
|
+
__step_options: dict[int, dict[str, str]]
|
|
73
|
+
"""Entry options for each step in this experiment. All entry options are cached the first time any individual step's
|
|
74
|
+
options are queried. The cache is updated whenever the entry options for a step are changed by this handler."""
|
|
65
75
|
|
|
66
76
|
# Constants
|
|
67
|
-
|
|
68
|
-
# against the value of the enums instead of the enums themselves. The ".value"s can be removed once sapiopylib is
|
|
69
|
-
# fixed.
|
|
70
|
-
__COMPLETE_STATUSES = [ExperimentEntryStatus.Completed.value, ExperimentEntryStatus.CompletedApproved.value]
|
|
77
|
+
__ENTRY_COMPLETE_STATUSES = [ExperimentEntryStatus.Completed, ExperimentEntryStatus.CompletedApproved]
|
|
71
78
|
"""The set of statuses that an ELN entry could have and be considered completed/submitted."""
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
79
|
+
__ENTRY_LOCKED_STATUSES = [ExperimentEntryStatus.Completed, ExperimentEntryStatus.CompletedApproved,
|
|
80
|
+
ExperimentEntryStatus.Disabled, ExperimentEntryStatus.LockedAwaitingApproval,
|
|
81
|
+
ExperimentEntryStatus.LockedRejected]
|
|
75
82
|
"""The set of statuses that an ELN entry could have and be considered locked."""
|
|
83
|
+
__EXPERIMENT_COMPLETE_STATUSES = [ElnExperimentStatus.Completed, ElnExperimentStatus.CompletedApproved]
|
|
84
|
+
"""The set of statuses that an ELN experiment could have and be considered completed."""
|
|
85
|
+
__EXPERIMENT_LOCKED_STATUSES = [ElnExperimentStatus.Completed, ElnExperimentStatus.CompletedApproved,
|
|
86
|
+
ElnExperimentStatus.LockedRejected, ElnExperimentStatus.LockedAwaitingApproval,
|
|
87
|
+
ElnExperimentStatus.Canceled]
|
|
88
|
+
"""The set of statuses that an ELN experiment could have and be considered locked."""
|
|
89
|
+
|
|
90
|
+
__instances: WeakValueDictionary[str, ExperimentHandler] = WeakValueDictionary()
|
|
91
|
+
__initialized: bool
|
|
76
92
|
|
|
77
|
-
def
|
|
93
|
+
def __new__(cls, context: UserIdentifier, experiment: ExperimentIdentifier | SapioRecord | None = None):
|
|
94
|
+
"""
|
|
95
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
96
|
+
:param experiment: If an experiment is provided that is separate from the experiment that is in the context,
|
|
97
|
+
that experiment will be used by this ExperimentHandler instead. An experiment can be provided in various
|
|
98
|
+
forms, including an ElnExperiment, ElnExperimentProtocol, an experiment record, or a notebook experiment ID.
|
|
99
|
+
"""
|
|
100
|
+
param_results = cls.__parse_params(context, experiment)
|
|
101
|
+
user = param_results[0]
|
|
102
|
+
experiment = param_results[2]
|
|
103
|
+
key = f"{user.__hash__()}:{experiment.notebook_experiment_id}"
|
|
104
|
+
obj = cls.__instances.get(key)
|
|
105
|
+
if not obj:
|
|
106
|
+
obj = object.__new__(cls)
|
|
107
|
+
obj.__initialized = False
|
|
108
|
+
cls.__instances[key] = obj
|
|
109
|
+
return obj
|
|
110
|
+
|
|
111
|
+
def __init__(self, context: UserIdentifier, experiment: ExperimentIdentifier | SapioRecord | None = None):
|
|
78
112
|
"""
|
|
79
113
|
Initialization will throw an exception if there is no ELN Experiment in the provided context and no experiment
|
|
80
114
|
is provided.
|
|
81
|
-
|
|
115
|
+
|
|
116
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
82
117
|
:param experiment: If an experiment is provided that is separate from the experiment that is in the context,
|
|
83
|
-
that experiment will be used by this ExperimentHandler instead.
|
|
118
|
+
that experiment will be used by this ExperimentHandler instead. An experiment can be provided in various
|
|
119
|
+
forms, including an ElnExperiment, ElnExperimentProtocol, an experiment record, or a notebook experiment ID.
|
|
84
120
|
"""
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if context.eln_experiment == experiment:
|
|
90
|
-
experiment = None
|
|
91
|
-
self.__context = context
|
|
121
|
+
param_results = self.__parse_params(context, experiment)
|
|
122
|
+
self.user = param_results[0]
|
|
123
|
+
self.context = param_results[1]
|
|
124
|
+
experiment = param_results[2]
|
|
92
125
|
|
|
93
126
|
# Get the basic information about this experiment that already exists in the context and is often used.
|
|
94
|
-
self.__eln_exp = experiment
|
|
95
|
-
self.__protocol = ElnExperimentProtocol(experiment,
|
|
127
|
+
self.__eln_exp = experiment
|
|
128
|
+
self.__protocol = ElnExperimentProtocol(experiment, self.user)
|
|
96
129
|
self.__exp_id = self.__protocol.get_id()
|
|
97
130
|
|
|
98
131
|
# Grab various managers that may be used.
|
|
99
|
-
self.__eln_man =
|
|
100
|
-
self.__inst_man = RecordModelManager(
|
|
132
|
+
self.__eln_man = DataMgmtServer.get_eln_manager(self.user)
|
|
133
|
+
self.__inst_man = RecordModelManager(self.user).instance_manager
|
|
101
134
|
|
|
102
135
|
# Create empty caches to fill when necessary.
|
|
103
136
|
self.__steps = {}
|
|
104
137
|
self.__step_options = {}
|
|
105
138
|
# CR-46330: Cache any experiment entry information that might already exist in the context.
|
|
106
139
|
self.__queried_all_steps = False
|
|
107
|
-
# We can only trust the entries in the context if
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
140
|
+
# We can only trust the entries in the context if the experiment that this handler is for is the same as the
|
|
141
|
+
# one from the context.
|
|
142
|
+
if self.context is not None and self.context.eln_experiment == experiment:
|
|
143
|
+
if self.context.experiment_entry is not None:
|
|
144
|
+
self.__steps.update({self.context.active_step.get_name(): self.context.active_step})
|
|
145
|
+
if self.context.experiment_entry_list is not None:
|
|
146
|
+
for entry in self.context.experiment_entry_list:
|
|
113
147
|
self.__steps.update({entry.entry_name: ElnEntryStep(self.__protocol, entry)})
|
|
114
148
|
|
|
149
|
+
@staticmethod
|
|
150
|
+
def __parse_params(context: UserIdentifier, experiment: ExperimentIdentifier | SapioRecord | None = None) \
|
|
151
|
+
-> tuple[SapioUser, SapioWebhookContext | None, ElnExperiment]:
|
|
152
|
+
if isinstance(context, SapioWebhookContext):
|
|
153
|
+
user = context.user
|
|
154
|
+
context = context
|
|
155
|
+
else:
|
|
156
|
+
user = context
|
|
157
|
+
context = None
|
|
158
|
+
if context is not None and context.eln_experiment is not None and experiment is None:
|
|
159
|
+
experiment = context.eln_experiment
|
|
160
|
+
# FR-46495 - Allow the init function of ExperimentHandler to take in an ElnExperiment that is separate from the
|
|
161
|
+
# context.
|
|
162
|
+
# CR-37038 - Allow other experiment object types to be provided. Convert them all down to ElnExperiment.
|
|
163
|
+
if (context is None or context.eln_experiment is None) and experiment is not None:
|
|
164
|
+
eln_manager = DataMgmtServer.get_eln_manager(user)
|
|
165
|
+
# If this object is already an ElnExperiment, do nothing.
|
|
166
|
+
if isinstance(experiment, ElnExperiment):
|
|
167
|
+
pass
|
|
168
|
+
# If this object is an ElnExperimentProtocol, then we can get the ElnExperiment from the object.
|
|
169
|
+
elif isinstance(experiment, ElnExperimentProtocol):
|
|
170
|
+
experiment: ElnExperiment = experiment.eln_experiment
|
|
171
|
+
# If this object is an integer, assume it is a notebook ID that we can query the system with.
|
|
172
|
+
elif isinstance(experiment, int):
|
|
173
|
+
notebook_id: int = experiment
|
|
174
|
+
experiment: ElnExperiment = eln_manager.get_eln_experiment_by_id(notebook_id)
|
|
175
|
+
if not experiment:
|
|
176
|
+
raise SapioException(f"No experiment with notebook ID {notebook_id} located in the system.")
|
|
177
|
+
# If this object is a record, assume it is an experiment record that we can query the system with.
|
|
178
|
+
else:
|
|
179
|
+
record_id: int = AliasUtil.to_record_ids([experiment])[0]
|
|
180
|
+
experiment: ElnExperiment = eln_manager.get_eln_experiment_by_record_id(record_id)
|
|
181
|
+
if not experiment:
|
|
182
|
+
raise SapioException(f"No experiment with record ID {record_id} located in the system.")
|
|
183
|
+
if experiment is None:
|
|
184
|
+
raise SapioException("Cannot initialize ExperimentHandler. No ELN Experiment found in the provided parameters.")
|
|
185
|
+
|
|
186
|
+
return user, context, experiment
|
|
187
|
+
|
|
115
188
|
# FR-46495: Split the creation of the experiment in launch_experiment into a create_experiment function.
|
|
116
189
|
@staticmethod
|
|
117
190
|
def create_experiment(context: SapioWebhookContext,
|
|
@@ -125,6 +198,7 @@ class ExperimentHandler:
|
|
|
125
198
|
Makes a webservice request to query for all the templates matching the provided criteria. Note that if multiple
|
|
126
199
|
templates match the same criteria, the first template that is encountered in the query is used. Throws an
|
|
127
200
|
exception if no template is found. Also makes a webservice request to create the experiment.
|
|
201
|
+
|
|
128
202
|
:param context: The current webhook context.
|
|
129
203
|
:param template_name: The name of the template to create the experiment from.
|
|
130
204
|
:param experiment_name: The name to give to the experiment after it is created. If not provided, defaults to the
|
|
@@ -174,6 +248,7 @@ class ExperimentHandler:
|
|
|
174
248
|
Makes a webservice request to query for all the templates matching the provided criteria. Note that if multiple
|
|
175
249
|
templates match the same criteria, the first template that is encountered in the query is used. Throws an
|
|
176
250
|
exception if no template is found. Also makes a webservice request to create the experiment.
|
|
251
|
+
|
|
177
252
|
:param context: The current webhook context.
|
|
178
253
|
:param template_name: The name of the template to create the experiment from.
|
|
179
254
|
:param experiment_name: The name to give to the experiment after it is created. If not provided, defaults to the
|
|
@@ -196,12 +271,14 @@ class ExperimentHandler:
|
|
|
196
271
|
"""
|
|
197
272
|
Query for the experiment template for the current experiment. The returned template is cached by the
|
|
198
273
|
ExperimentHandler.
|
|
274
|
+
|
|
199
275
|
:param exception_on_none: If false, returns None if there is no experiment template. If true, raises an exception
|
|
200
276
|
when the experiment template doesn't exist.
|
|
201
277
|
:return: This experiment's template. None if it has no template.
|
|
202
278
|
"""
|
|
203
279
|
template_id: int | None = self.__eln_exp.template_id
|
|
204
280
|
if template_id is None:
|
|
281
|
+
self.__exp_template = None
|
|
205
282
|
if exception_on_none:
|
|
206
283
|
raise SapioException(f"Experiment with ID {self.__exp_id} has no template ID.")
|
|
207
284
|
return None
|
|
@@ -228,6 +305,7 @@ class ExperimentHandler:
|
|
|
228
305
|
The template name is determined by either the experiment template or the experiment record, whichever is
|
|
229
306
|
already cached. If neither are cached, queries for the experiment template. If no experiment template exists,
|
|
230
307
|
queries for the experiment record.
|
|
308
|
+
|
|
231
309
|
:param exception_on_none: If false, returns None if there is no template name. If true, raises an exception
|
|
232
310
|
when the template name doesn't exist.
|
|
233
311
|
:return: The template name of the current experiment. None if it has no template name.
|
|
@@ -249,16 +327,13 @@ class ExperimentHandler:
|
|
|
249
327
|
def get_experiment_record(self, exception_on_none: bool = True) -> DataRecord | None:
|
|
250
328
|
"""
|
|
251
329
|
Query for the data record of this experiment. The returned record is cached by the ExperimentHandler.
|
|
330
|
+
|
|
252
331
|
:param exception_on_none: If false, returns None if there is no experiment record. If true, raises an exception
|
|
253
332
|
when the experiment record doesn't exist.
|
|
254
333
|
:return: The data record for this experiment. None if it has no record.
|
|
255
334
|
"""
|
|
256
335
|
if not hasattr(self, "_ExperimentHandler__exp_record"):
|
|
257
|
-
|
|
258
|
-
dt = self.__eln_exp.experiment_data_type_name
|
|
259
|
-
results = drm.query_data_records_by_id(dt, [self.__eln_exp.experiment_record_id]).result_list
|
|
260
|
-
# PR-46504: Set the exp_record to None if there are no results.
|
|
261
|
-
self.__exp_record = results[0] if results else None
|
|
336
|
+
self.__exp_record = self.__protocol.get_record()
|
|
262
337
|
if self.__exp_record is None and exception_on_none:
|
|
263
338
|
raise SapioException(f"Experiment record not found for experiment with ID {self.__exp_id}.")
|
|
264
339
|
return self.__exp_record
|
|
@@ -267,6 +342,7 @@ class ExperimentHandler:
|
|
|
267
342
|
"""
|
|
268
343
|
Query for the data record of this experiment and wrap it as a record model with the given wrapper.
|
|
269
344
|
The returned record is cached by the ExperimentHandler.
|
|
345
|
+
|
|
270
346
|
:param wrapper_type: The record model wrapper to use.
|
|
271
347
|
:return: The record model for this experiment.
|
|
272
348
|
"""
|
|
@@ -279,6 +355,7 @@ class ExperimentHandler:
|
|
|
279
355
|
"""
|
|
280
356
|
Make a webservice call to update the experiment for this ExperimentHandler. If any parameter is not provided,
|
|
281
357
|
then no change is made to it.
|
|
358
|
+
|
|
282
359
|
:param experiment_name: The new name of the experiment.
|
|
283
360
|
:param experiment_status: The new status of this experiment.
|
|
284
361
|
:param experiment_option_map:
|
|
@@ -307,6 +384,7 @@ class ExperimentHandler:
|
|
|
307
384
|
Getting the experiment options requires a webservice query, which is made the first time any experiment option
|
|
308
385
|
method is called by this ExperimentHandler. The experiment options are cached so that subsequent calls of this
|
|
309
386
|
method don't make a webservice call.
|
|
387
|
+
|
|
310
388
|
:param option: The experiment option to search for.
|
|
311
389
|
:return: The value of the input experiment options.
|
|
312
390
|
"""
|
|
@@ -319,6 +397,7 @@ class ExperimentHandler:
|
|
|
319
397
|
Getting the experiment options requires a webservice query, which is made the first time any experiment option
|
|
320
398
|
method is called by this ExperimentHandler. The experiment options are cached so that subsequent calls of this
|
|
321
399
|
method don't make a webservice call.
|
|
400
|
+
|
|
322
401
|
:return: The map of options for this experiment.
|
|
323
402
|
"""
|
|
324
403
|
return self.__get_experiment_options()
|
|
@@ -331,6 +410,7 @@ class ExperimentHandler:
|
|
|
331
410
|
Getting the experiment options requires a webservice query, which is made the first time any experiment option
|
|
332
411
|
method is called by this ExperimentHandler. The experiment options are cached so that subsequent calls of this
|
|
333
412
|
method don't make a webservice call.
|
|
413
|
+
|
|
334
414
|
:param mapping: The new options and values to add to the existing experiment options, provided as some Mapping
|
|
335
415
|
(e.g. a Dict). If an option key already exists and is provided in the mapping, overwrites the existing value
|
|
336
416
|
for that key.
|
|
@@ -339,12 +419,60 @@ class ExperimentHandler:
|
|
|
339
419
|
options.update(mapping)
|
|
340
420
|
self.update_experiment(experiment_option_map=options)
|
|
341
421
|
|
|
422
|
+
def experiment_is_complete(self) -> bool:
|
|
423
|
+
"""
|
|
424
|
+
Determine if the experiment has been completed.
|
|
425
|
+
|
|
426
|
+
:return: True if the experiment's status is Completed or CompletedApproved. False otherwise.
|
|
427
|
+
"""
|
|
428
|
+
return self.__eln_exp.notebook_experiment_status in self.__EXPERIMENT_COMPLETE_STATUSES
|
|
429
|
+
|
|
430
|
+
def experiment_is_canceled(self) -> bool:
|
|
431
|
+
"""
|
|
432
|
+
Determine if the experiment has been canceled.
|
|
433
|
+
|
|
434
|
+
:return: True if the experiment's status is Canceled. False otherwise.
|
|
435
|
+
"""
|
|
436
|
+
return self.__eln_exp.notebook_experiment_status == ElnExperimentStatus.Canceled
|
|
437
|
+
|
|
438
|
+
def experiment_is_locked(self) -> bool:
|
|
439
|
+
"""
|
|
440
|
+
Determine if the experiment has been locked in any way.
|
|
441
|
+
|
|
442
|
+
:return: True if the experiment's status is Completed, CompletedApproved, Canceled, LockedAwaitingApproval,
|
|
443
|
+
or LockedRejected. False otherwise.
|
|
444
|
+
"""
|
|
445
|
+
return self.__eln_exp.notebook_experiment_status in self.__EXPERIMENT_LOCKED_STATUSES
|
|
446
|
+
|
|
447
|
+
def complete_experiment(self) -> None:
|
|
448
|
+
"""
|
|
449
|
+
Set the experiment's status to Completed. Makes a webservice call to update the experiment. Checks if the
|
|
450
|
+
experiment is already completed, and does nothing if so.
|
|
451
|
+
"""
|
|
452
|
+
if not self.experiment_is_complete():
|
|
453
|
+
self.__protocol.complete_protocol()
|
|
454
|
+
self.__eln_exp.notebook_experiment_status = ElnExperimentStatus.Completed
|
|
455
|
+
|
|
456
|
+
def cancel_experiment(self) -> None:
|
|
457
|
+
"""
|
|
458
|
+
Set the experiment's status to Canceled. Makes a webservice call to update the experiment. Checks if the
|
|
459
|
+
experiment is already canceled, and does nothing if so.
|
|
460
|
+
|
|
461
|
+
NOTE: This will not run the usual logic around canceling an experiment that you'd see if canceling the
|
|
462
|
+
experiment using the "Cancel Experiment" toolbar button, such as moving in process samples back to the queue,
|
|
463
|
+
as those changes are tied to the button instead of being on the experiment status change.
|
|
464
|
+
"""
|
|
465
|
+
if not self.experiment_is_canceled():
|
|
466
|
+
self.__protocol.cancel_protocol()
|
|
467
|
+
self.__eln_exp.notebook_experiment_status = ElnExperimentStatus.Canceled
|
|
468
|
+
|
|
342
469
|
def step_exists(self, step_name: str) -> bool:
|
|
343
470
|
"""
|
|
344
471
|
Determine if a step by the given name exists in the experiment.
|
|
345
472
|
|
|
346
473
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
347
474
|
list of steps in the experiment and caches them.
|
|
475
|
+
|
|
348
476
|
:param step_name: The name of the step to search for.
|
|
349
477
|
:return: True if the step exists, false otherwise.
|
|
350
478
|
"""
|
|
@@ -356,6 +484,7 @@ class ExperimentHandler:
|
|
|
356
484
|
|
|
357
485
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
358
486
|
list of steps in the experiment and caches them.
|
|
487
|
+
|
|
359
488
|
:param step_names: The names of the steps to search for.
|
|
360
489
|
:return: True if every step exists, false if at least one does not exist.
|
|
361
490
|
"""
|
|
@@ -367,6 +496,7 @@ class ExperimentHandler:
|
|
|
367
496
|
|
|
368
497
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
369
498
|
list of steps in the experiment and caches them.
|
|
499
|
+
|
|
370
500
|
:param step_name: The name for the step to return.
|
|
371
501
|
:param exception_on_none: If false, returns None if the entry can't be found. If true, raises an exception
|
|
372
502
|
when the named entry doesn't exist in the experiment.
|
|
@@ -381,6 +511,7 @@ class ExperimentHandler:
|
|
|
381
511
|
|
|
382
512
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
383
513
|
list of steps in the experiment and caches them.
|
|
514
|
+
|
|
384
515
|
:param step_names: A list of names for the entries to return and the order to return them in.
|
|
385
516
|
:param exception_on_none: If false, returns None for entries that can't be found. If true, raises an exception
|
|
386
517
|
when the named entry doesn't exist in the experiment.
|
|
@@ -402,12 +533,65 @@ class ExperimentHandler:
|
|
|
402
533
|
ret_list.append(step)
|
|
403
534
|
return ret_list
|
|
404
535
|
|
|
536
|
+
def get_all_steps(self, data_type: DataTypeIdentifier | None = None) -> list[ElnEntryStep]:
|
|
537
|
+
"""
|
|
538
|
+
Get a list of every entry in the experiment. Optionally filter the returned entries by a data type.
|
|
539
|
+
|
|
540
|
+
Makes a webservice call to retrieve every entry in the experiment if they were not already previously cached.
|
|
541
|
+
|
|
542
|
+
:param data_type: A data type used to filter the returned entries. If None is given, returns all entries. If
|
|
543
|
+
a data type name or wrapper is given, only returns entries that match that data type name or wrapper.
|
|
544
|
+
:return: Every entry in the experiment in order of appearance that match the provided data type, if any.
|
|
545
|
+
"""
|
|
546
|
+
if self.__queried_all_steps is False:
|
|
547
|
+
self.__queried_all_steps = True
|
|
548
|
+
self.__steps.update({step.get_name(): step for step in self.__protocol.get_sorted_step_list()})
|
|
549
|
+
all_steps: list[ElnEntryStep] = self.__protocol.get_sorted_step_list()
|
|
550
|
+
if data_type is None:
|
|
551
|
+
return all_steps
|
|
552
|
+
data_type: str = AliasUtil.to_data_type_name(data_type)
|
|
553
|
+
return [x for x in all_steps if data_type in x.get_data_type_names()]
|
|
554
|
+
|
|
555
|
+
def get_step_by_option(self, key: str, value: str | None = None) -> ElnEntryStep:
|
|
556
|
+
"""
|
|
557
|
+
Retrieve the step in this experiment that contains an entry option with the provided key and value.
|
|
558
|
+
Throws an exception if no entries or multiple entries in the experiment match.
|
|
559
|
+
|
|
560
|
+
:param key: The key of the entry option to match on.
|
|
561
|
+
:param value: The value of the entry option to match on. If not provided, then only matches on key.
|
|
562
|
+
:return: The entry in this experiment that matches the provided entry option key and value.
|
|
563
|
+
"""
|
|
564
|
+
steps: list[ElnEntryStep] = self.get_steps_by_option(key, value)
|
|
565
|
+
count: int = len(steps)
|
|
566
|
+
if count != 1:
|
|
567
|
+
option = key + ("::" + value if value is not None else "")
|
|
568
|
+
raise SapioException(f"{('No' if count == 0 else 'Multiple')} entries in this experiment match the "
|
|
569
|
+
f"provided option: {option}")
|
|
570
|
+
return steps[0]
|
|
571
|
+
|
|
572
|
+
def get_steps_by_option(self, key: str, value: str | None = None) -> list[ElnEntryStep]:
|
|
573
|
+
"""
|
|
574
|
+
Retrieve every step in this experiment that contains an entry option with the provided key and value.
|
|
575
|
+
|
|
576
|
+
:param key: The key of the entry option to match on.
|
|
577
|
+
:param value: The value of the entry option to match on. If not provided, then only matches on key.
|
|
578
|
+
:return: The entries in this experiment that match the provided entry option key and value.
|
|
579
|
+
"""
|
|
580
|
+
ret_list: list[ElnEntryStep] = []
|
|
581
|
+
for step in self.get_all_steps():
|
|
582
|
+
options: dict[str, str] = self.get_step_options(step)
|
|
583
|
+
if key in options:
|
|
584
|
+
if value is None or options[key] == value:
|
|
585
|
+
ret_list.append(step)
|
|
586
|
+
return ret_list
|
|
587
|
+
|
|
405
588
|
def get_step_records(self, step: Step) -> list[DataRecord]:
|
|
406
589
|
"""
|
|
407
590
|
Query for the data records for the given step. The returned records are not cached by the ExperimentHandler.
|
|
408
591
|
|
|
409
592
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
410
593
|
list of steps in the experiment and caches them.
|
|
594
|
+
|
|
411
595
|
:param step:
|
|
412
596
|
The step to get the data records for.
|
|
413
597
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
@@ -423,6 +607,7 @@ class ExperimentHandler:
|
|
|
423
607
|
|
|
424
608
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
425
609
|
list of steps in the experiment and caches them.
|
|
610
|
+
|
|
426
611
|
:param step:
|
|
427
612
|
The step to get the data records for.
|
|
428
613
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
@@ -439,6 +624,7 @@ class ExperimentHandler:
|
|
|
439
624
|
|
|
440
625
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
441
626
|
list of steps in the experiment and caches them.
|
|
627
|
+
|
|
442
628
|
:param step:
|
|
443
629
|
The step to add the records to.
|
|
444
630
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
@@ -448,6 +634,15 @@ class ExperimentHandler:
|
|
|
448
634
|
The records may be provided as either DataRecords, PyRecordModels, or WrappedRecordModels.
|
|
449
635
|
"""
|
|
450
636
|
step = self.__to_eln_step(step)
|
|
637
|
+
if not records:
|
|
638
|
+
return
|
|
639
|
+
dt: str = AliasUtil.to_singular_data_type_name(records)
|
|
640
|
+
if ElnBaseDataType.is_base_data_type(dt):
|
|
641
|
+
raise SapioException(f"{dt} is an ELN data type. This function call has no effect on ELN data types. "
|
|
642
|
+
f"Use add_eln_rows or add_sample_details instead.")
|
|
643
|
+
if dt != step.get_data_type_names()[0]:
|
|
644
|
+
raise SapioException(f"Cannot add {dt} records to entry {step.get_name()} of type "
|
|
645
|
+
f"{step.get_data_type_names()[0]}.")
|
|
451
646
|
step.add_records(AliasUtil.to_data_records(records))
|
|
452
647
|
|
|
453
648
|
def remove_step_records(self, step: Step, records: Iterable[SapioRecord]) -> None:
|
|
@@ -457,6 +652,7 @@ class ExperimentHandler:
|
|
|
457
652
|
|
|
458
653
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
459
654
|
list of steps in the experiment and caches them.
|
|
655
|
+
|
|
460
656
|
:param step:
|
|
461
657
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
462
658
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
@@ -465,6 +661,15 @@ class ExperimentHandler:
|
|
|
465
661
|
The records may be provided as either DataRecords, PyRecordModels, or WrappedRecordModels.
|
|
466
662
|
"""
|
|
467
663
|
step = self.__to_eln_step(step)
|
|
664
|
+
if not records:
|
|
665
|
+
return
|
|
666
|
+
dt: str = AliasUtil.to_singular_data_type_name(records)
|
|
667
|
+
if ElnBaseDataType.is_base_data_type(dt):
|
|
668
|
+
raise SapioException(f"{dt} is an ELN data type. This function call has no effect on ELN data types. "
|
|
669
|
+
f"Use remove_eln_rows or remove_sample_details instead.")
|
|
670
|
+
if dt != step.get_data_type_names()[0]:
|
|
671
|
+
raise SapioException(f"Cannot remove {dt} records from entry {step.get_name()} of type "
|
|
672
|
+
f"{step.get_data_type_names()[0]}.")
|
|
468
673
|
step.remove_records(AliasUtil.to_data_records(records))
|
|
469
674
|
|
|
470
675
|
def set_step_records(self, step: Step, records: Iterable[SapioRecord]) -> None:
|
|
@@ -479,6 +684,7 @@ class ExperimentHandler:
|
|
|
479
684
|
|
|
480
685
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
481
686
|
list of steps in the experiment and caches them.
|
|
687
|
+
|
|
482
688
|
:param step:
|
|
483
689
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
484
690
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
@@ -487,17 +693,24 @@ class ExperimentHandler:
|
|
|
487
693
|
The records may be provided as either DataRecords, PyRecordModels, or WrappedRecordModels.
|
|
488
694
|
"""
|
|
489
695
|
step = self.__to_eln_step(step)
|
|
696
|
+
if records:
|
|
697
|
+
dt: str = AliasUtil.to_singular_data_type_name(records)
|
|
698
|
+
if ElnBaseDataType.is_base_data_type(dt):
|
|
699
|
+
raise SapioException(f"{dt} is an ELN data type. This function call has no effect on ELN data types. "
|
|
700
|
+
f"Use add_eln_rows or add_sample_details instead.")
|
|
701
|
+
if dt != step.get_data_type_names()[0]:
|
|
702
|
+
raise SapioException(f"Cannot set {dt} records for entry {step.get_name()} of type "
|
|
703
|
+
f"{step.get_data_type_names()[0]}.")
|
|
490
704
|
step.set_records(AliasUtil.to_data_records(records))
|
|
491
705
|
|
|
492
706
|
# FR-46496 - Provide alias of set_step_records for use with form entries.
|
|
493
|
-
# TODO: Provide a similar aliased function for attachment entries once sapiopylib allows setting multiple
|
|
494
|
-
# attachments to an attachment step.
|
|
495
707
|
def set_form_record(self, step: Step, record: SapioRecord) -> None:
|
|
496
708
|
"""
|
|
497
709
|
Sets the record for a form entry.
|
|
498
710
|
|
|
499
711
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
500
712
|
list of steps in the experiment and caches them.
|
|
713
|
+
|
|
501
714
|
:param step:
|
|
502
715
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
503
716
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
@@ -508,26 +721,50 @@ class ExperimentHandler:
|
|
|
508
721
|
self.set_step_records(step, [record])
|
|
509
722
|
|
|
510
723
|
# FR-46496 - Provide functions for adding and removing rows from an ELN data type entry.
|
|
511
|
-
def add_eln_rows(self, step: Step, count: int
|
|
724
|
+
def add_eln_rows(self, step: Step, count: int, wrapper_type: type[WrappedType] | None = None) \
|
|
725
|
+
-> list[PyRecordModel | WrappedType]:
|
|
512
726
|
"""
|
|
513
727
|
Add rows to an ELNExperimentDetail or ELNSampleDetail table entry. The rows will not appear in the system
|
|
514
728
|
until a record manager store and commit has occurred.
|
|
515
729
|
|
|
516
730
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
517
731
|
list of steps in the experiment and caches them.
|
|
732
|
+
|
|
518
733
|
:param step:
|
|
519
734
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
520
735
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
521
736
|
:param count: The number of new rows to add to the entry.
|
|
737
|
+
:param wrapper_type: Optionally wrap the ELN data type in a record model wrapper. If not provided, returns
|
|
738
|
+
an unwrapped PyRecordModel.
|
|
522
739
|
:return: A list of the newly created rows.
|
|
523
740
|
"""
|
|
524
741
|
step = self.__to_eln_step(step)
|
|
525
742
|
if step.eln_entry.entry_type != ElnEntryType.Table:
|
|
526
743
|
raise SapioException("The provided step is not a table entry.")
|
|
527
744
|
dt: str = step.get_data_type_names()[0]
|
|
528
|
-
if not
|
|
745
|
+
if not ElnBaseDataType.is_eln_type(dt):
|
|
529
746
|
raise SapioException("The provided step is not an ELN data type entry.")
|
|
530
|
-
|
|
747
|
+
records: list[PyRecordModel] = self.__inst_man.add_new_records(dt, count)
|
|
748
|
+
if wrapper_type:
|
|
749
|
+
return self.__inst_man.wrap_list(records, wrapper_type)
|
|
750
|
+
return records
|
|
751
|
+
|
|
752
|
+
def add_eln_row(self, step: Step, wrapper_type: type[WrappedType] | None = None) -> PyRecordModel | WrappedType:
|
|
753
|
+
"""
|
|
754
|
+
Add a row to an ELNExperimentDetail or ELNSampleDetail table entry. The row will not appear in the system
|
|
755
|
+
until a record manager store and commit has occurred.
|
|
756
|
+
|
|
757
|
+
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
758
|
+
list of steps in the experiment and caches them.
|
|
759
|
+
|
|
760
|
+
:param step:
|
|
761
|
+
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
762
|
+
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
763
|
+
:param wrapper_type: Optionally wrap the ELN data type in a record model wrapper. If not provided, returns
|
|
764
|
+
an unwrapped PyRecordModel.
|
|
765
|
+
:return: The newly created row.
|
|
766
|
+
"""
|
|
767
|
+
return self.add_eln_rows(step, 1, wrapper_type)[0]
|
|
531
768
|
|
|
532
769
|
def remove_eln_rows(self, step: Step, records: list[SapioRecord]) -> None:
|
|
533
770
|
"""
|
|
@@ -539,6 +776,7 @@ class ExperimentHandler:
|
|
|
539
776
|
|
|
540
777
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
541
778
|
list of steps in the experiment and caches them.
|
|
779
|
+
|
|
542
780
|
:param step:
|
|
543
781
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
544
782
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
@@ -548,10 +786,14 @@ class ExperimentHandler:
|
|
|
548
786
|
"""
|
|
549
787
|
step = self.__to_eln_step(step)
|
|
550
788
|
dt: str = step.get_data_type_names()[0]
|
|
551
|
-
if not
|
|
789
|
+
if not ElnBaseDataType.is_eln_type(dt):
|
|
552
790
|
raise SapioException("The provided step is not an ELN data type entry.")
|
|
553
|
-
if
|
|
554
|
-
|
|
791
|
+
if not records:
|
|
792
|
+
return
|
|
793
|
+
record_dt: str = AliasUtil.to_singular_data_type_name(records, False)
|
|
794
|
+
if record_dt != dt:
|
|
795
|
+
raise SapioException(f"Cannot remove {dt} records from entry {step.get_name()} of type "
|
|
796
|
+
f"{step.get_data_type_names()[0]}.")
|
|
555
797
|
# If any rows were provided as data records, turn them into record models before deleting them, as otherwise
|
|
556
798
|
# this function would need to make a webservice call to do the deletion.
|
|
557
799
|
data_records: list[DataRecord] = []
|
|
@@ -565,16 +807,59 @@ class ExperimentHandler:
|
|
|
565
807
|
for record in record_models:
|
|
566
808
|
record.delete()
|
|
567
809
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
810
|
+
def remove_eln_row(self, step: Step, record: SapioRecord) -> None:
|
|
811
|
+
"""
|
|
812
|
+
Remove a row from an ELNExperimentDetail or ELNSampleDetail table entry. ELN data type table entries display all
|
|
813
|
+
records in the system that match the entry's data type. This means that removing rows from an ELN data type
|
|
814
|
+
table entry is equivalent to deleting the records for the rows.
|
|
815
|
+
|
|
816
|
+
The row will not be deleted in the system until a record manager store and commit has occurred.
|
|
817
|
+
|
|
818
|
+
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
819
|
+
list of steps in the experiment and caches them.
|
|
820
|
+
|
|
821
|
+
:param step:
|
|
822
|
+
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
823
|
+
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
824
|
+
:param record:
|
|
825
|
+
The record to remove from the given step.
|
|
826
|
+
The record may be provided as either a DataRecord, PyRecordModel, or WrappedRecordModel.
|
|
827
|
+
"""
|
|
828
|
+
self.remove_eln_rows(step, [record])
|
|
829
|
+
|
|
830
|
+
def add_sample_details(self, step: Step, samples: list[RecordModel], wrapper_type: type[WrappedType]) \
|
|
831
|
+
-> list[PyRecordModel | WrappedType]:
|
|
832
|
+
"""
|
|
833
|
+
Add sample details to a sample details entry while relating them to the input sample records.
|
|
834
|
+
|
|
835
|
+
:param step:
|
|
836
|
+
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
837
|
+
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
838
|
+
:param samples: The sample records to add the sample details to.
|
|
839
|
+
:param wrapper_type: Optionally wrap the sample details in a record model wrapper. If not provided, returns
|
|
840
|
+
an unwrapped PyRecordModel.
|
|
841
|
+
:return: The newly created sample details. The indices of the samples in the input list match the index of the
|
|
842
|
+
sample details in this list that they are related to.
|
|
843
|
+
"""
|
|
844
|
+
step = self.__to_eln_step(step)
|
|
845
|
+
if step.eln_entry.entry_type != ElnEntryType.Table:
|
|
846
|
+
raise SapioException("The provided step is not a table entry.")
|
|
847
|
+
dt: str = step.get_data_type_names()[0]
|
|
848
|
+
if not ElnBaseDataType.is_eln_type(dt) or ElnBaseDataType.get_base_type(dt) != ElnBaseDataType.SAMPLE_DETAIL:
|
|
849
|
+
raise SapioException("The provided step is not an ELNSampleDetail entry.")
|
|
850
|
+
records: list[PyRecordModel] = []
|
|
851
|
+
for sample in samples:
|
|
852
|
+
if sample.data_type_name != "Sample":
|
|
853
|
+
raise SapioException(f"Received a {sample.data_type_name} record when Sample records were expected.")
|
|
854
|
+
detail: PyRecordModel = sample.add(Child.create_by_name(dt))
|
|
855
|
+
detail.set_field_values({
|
|
856
|
+
"SampleId": sample.get_field_value("SampleId"),
|
|
857
|
+
"OtherSampleId": sample.get_field_value("OtherSampleId")
|
|
858
|
+
})
|
|
859
|
+
records.append(detail)
|
|
860
|
+
if wrapper_type:
|
|
861
|
+
return self.__inst_man.wrap_list(records, wrapper_type)
|
|
862
|
+
return records
|
|
578
863
|
|
|
579
864
|
def update_step(self, step: Step,
|
|
580
865
|
entry_name: str | None = None,
|
|
@@ -605,6 +890,7 @@ class ExperimentHandler:
|
|
|
605
890
|
|
|
606
891
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
607
892
|
list of steps in the experiment and caches them.
|
|
893
|
+
|
|
608
894
|
:param step:
|
|
609
895
|
The entry step to update.
|
|
610
896
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
@@ -689,10 +975,7 @@ class ExperimentHandler:
|
|
|
689
975
|
if dependency_set is not None:
|
|
690
976
|
entry.dependency_set = dependency_set
|
|
691
977
|
if entry_status is not None:
|
|
692
|
-
|
|
693
|
-
# breaking comparisons to enums that expect this behavior, also setting the status to the enum's string.
|
|
694
|
-
# The ".value" can be removed once sapiopylib is fixed.
|
|
695
|
-
entry.entry_status = entry_status.value
|
|
978
|
+
entry.entry_status = entry_status
|
|
696
979
|
if order is not None:
|
|
697
980
|
entry.order = order
|
|
698
981
|
if description is not None:
|
|
@@ -728,7 +1011,7 @@ class ExperimentHandler:
|
|
|
728
1011
|
if clear_template_item_fulfilled_timestamp is True:
|
|
729
1012
|
entry.template_item_fulfilled_timestamp = None
|
|
730
1013
|
if entry_options_map is not None:
|
|
731
|
-
self.__step_options.update({step: entry_options_map})
|
|
1014
|
+
self.__step_options.update({step.get_id(): entry_options_map})
|
|
732
1015
|
|
|
733
1016
|
def get_step_option(self, step: Step, option: str) -> str:
|
|
734
1017
|
"""
|
|
@@ -740,6 +1023,7 @@ class ExperimentHandler:
|
|
|
740
1023
|
Getting the step options requires a webservice query, which is made the first time any step option
|
|
741
1024
|
method is called for a specific step. The step options are cached so that subsequent calls of this
|
|
742
1025
|
method for that step don't make a webservice call.
|
|
1026
|
+
|
|
743
1027
|
:param step:
|
|
744
1028
|
The step to check the options of.
|
|
745
1029
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
@@ -757,15 +1041,19 @@ class ExperimentHandler:
|
|
|
757
1041
|
list of steps in the experiment and caches them.
|
|
758
1042
|
|
|
759
1043
|
Getting the step options requires a webservice query, which is made the first time any step option
|
|
760
|
-
method is called for
|
|
761
|
-
method
|
|
1044
|
+
method is called for any step in this experiment. The step options are cached so that subsequent calls of this
|
|
1045
|
+
method don't make a webservice call.
|
|
1046
|
+
|
|
762
1047
|
:param step:
|
|
763
1048
|
The step to get the options of.
|
|
764
1049
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
765
1050
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
766
1051
|
:return: The map of options for the input step.
|
|
767
1052
|
"""
|
|
768
|
-
|
|
1053
|
+
step = self.__to_eln_step(step)
|
|
1054
|
+
if step not in self.__step_options:
|
|
1055
|
+
self.__step_options.update(ExperimentReportUtil.get_experiment_entry_options(self.user, self.get_all_steps()))
|
|
1056
|
+
return self.__step_options[step.get_id()]
|
|
769
1057
|
|
|
770
1058
|
def add_step_options(self, step: Step, mapping: Mapping[str, str]):
|
|
771
1059
|
"""
|
|
@@ -778,6 +1066,7 @@ class ExperimentHandler:
|
|
|
778
1066
|
Getting the step options requires a webservice query, which is made the first time any step option
|
|
779
1067
|
method is called for a specific step. The step options are cached so that subsequent calls of this
|
|
780
1068
|
method for that step don't make a webservice call.
|
|
1069
|
+
|
|
781
1070
|
:param step:
|
|
782
1071
|
The step to update the options of.
|
|
783
1072
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
@@ -797,6 +1086,7 @@ class ExperimentHandler:
|
|
|
797
1086
|
|
|
798
1087
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
799
1088
|
list of steps in the experiment and caches them.
|
|
1089
|
+
|
|
800
1090
|
:param step:
|
|
801
1091
|
The step to initialize.
|
|
802
1092
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
@@ -814,6 +1104,7 @@ class ExperimentHandler:
|
|
|
814
1104
|
|
|
815
1105
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
816
1106
|
list of steps in the experiment and caches them.
|
|
1107
|
+
|
|
817
1108
|
:param step:
|
|
818
1109
|
The step to uninitialize.
|
|
819
1110
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
@@ -831,18 +1122,16 @@ class ExperimentHandler:
|
|
|
831
1122
|
|
|
832
1123
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
833
1124
|
list of steps in the experiment and caches them.
|
|
1125
|
+
|
|
834
1126
|
:param step:
|
|
835
1127
|
The step to complete.
|
|
836
1128
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
837
1129
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
838
1130
|
"""
|
|
839
1131
|
step = self.__to_eln_step(step)
|
|
840
|
-
if step.eln_entry.entry_status not in self.
|
|
1132
|
+
if step.eln_entry.entry_status not in self.__ENTRY_COMPLETE_STATUSES:
|
|
841
1133
|
step.complete_step()
|
|
842
|
-
|
|
843
|
-
# breaking comparisons to enums that expect this behavior, also setting the status to the enum's string.
|
|
844
|
-
# The ".value" can be removed once sapiopylib is fixed.
|
|
845
|
-
step.eln_entry.entry_status = ExperimentEntryStatus.Completed.value
|
|
1134
|
+
step.eln_entry.entry_status = ExperimentEntryStatus.Completed
|
|
846
1135
|
|
|
847
1136
|
def unlock_step(self, step: Step) -> None:
|
|
848
1137
|
"""
|
|
@@ -851,18 +1140,37 @@ class ExperimentHandler:
|
|
|
851
1140
|
|
|
852
1141
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
853
1142
|
list of steps in the experiment and caches them.
|
|
1143
|
+
|
|
854
1144
|
:param step:
|
|
855
1145
|
The step to unlock.
|
|
856
1146
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
857
1147
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
858
1148
|
"""
|
|
859
1149
|
step = self.__to_eln_step(step)
|
|
860
|
-
if step.eln_entry.entry_status in self.
|
|
1150
|
+
if step.eln_entry.entry_status in self.__ENTRY_LOCKED_STATUSES:
|
|
861
1151
|
step.unlock_step()
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
1152
|
+
step.eln_entry.entry_status = ExperimentEntryStatus.UnlockedChangesRequired
|
|
1153
|
+
|
|
1154
|
+
def disable_step(self, step: Step) -> None:
|
|
1155
|
+
"""
|
|
1156
|
+
Set the status of the input step to Disabled. This is the state that entries are in when they are waiting for
|
|
1157
|
+
entries that they are dependent upon to be submitted before they can be enabled. If you have unsubmitted an
|
|
1158
|
+
entry and want its dependent entries to be locked again, then you would use this to set their status to
|
|
1159
|
+
disabled.
|
|
1160
|
+
|
|
1161
|
+
Makes a webservice call to update the step. Checks if the step is already unlocked, and does nothing if so.
|
|
1162
|
+
|
|
1163
|
+
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
1164
|
+
list of steps in the experiment and caches them.
|
|
1165
|
+
|
|
1166
|
+
:param step:
|
|
1167
|
+
The step to disable.
|
|
1168
|
+
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
1169
|
+
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1170
|
+
"""
|
|
1171
|
+
step = self.__to_eln_step(step)
|
|
1172
|
+
if step.eln_entry.entry_status in self.__ENTRY_LOCKED_STATUSES:
|
|
1173
|
+
self.update_step(step, entry_status=ExperimentEntryStatus.Disabled)
|
|
866
1174
|
|
|
867
1175
|
def step_is_submitted(self, step: Step) -> bool:
|
|
868
1176
|
"""
|
|
@@ -870,13 +1178,14 @@ class ExperimentHandler:
|
|
|
870
1178
|
|
|
871
1179
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
872
1180
|
list of steps in the experiment and caches them.
|
|
1181
|
+
|
|
873
1182
|
:param step:
|
|
874
1183
|
The step to check.
|
|
875
1184
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
876
1185
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
877
1186
|
:return: True if the step's status is Completed or CompletedApproved. False otherwise.
|
|
878
1187
|
"""
|
|
879
|
-
return self.__to_eln_step(step).eln_entry.entry_status in self.
|
|
1188
|
+
return self.__to_eln_step(step).eln_entry.entry_status in self.__ENTRY_COMPLETE_STATUSES
|
|
880
1189
|
|
|
881
1190
|
def step_is_locked(self, step: Step) -> bool:
|
|
882
1191
|
"""
|
|
@@ -884,6 +1193,7 @@ class ExperimentHandler:
|
|
|
884
1193
|
|
|
885
1194
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
886
1195
|
list of steps in the experiment and caches them.
|
|
1196
|
+
|
|
887
1197
|
:param step:
|
|
888
1198
|
The step to check.
|
|
889
1199
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
@@ -891,13 +1201,14 @@ class ExperimentHandler:
|
|
|
891
1201
|
:return: True if the step's status is Completed, CompletedApproved, Disabled, LockedAwaitingApproval,
|
|
892
1202
|
or LockedRejected. False otherwise.
|
|
893
1203
|
"""
|
|
894
|
-
return self.__to_eln_step(step).eln_entry.entry_status in self.
|
|
1204
|
+
return self.__to_eln_step(step).eln_entry.entry_status in self.__ENTRY_LOCKED_STATUSES
|
|
895
1205
|
|
|
896
1206
|
def __to_eln_step(self, step: Step) -> ElnEntryStep:
|
|
897
1207
|
"""
|
|
898
1208
|
Convert a variable that could be either a string or an ElnEntryStep to just an ElnEntryStep.
|
|
899
1209
|
This will query and cache the steps for the experiment if the input step is a name and the steps have not been
|
|
900
1210
|
cached before.
|
|
1211
|
+
|
|
901
1212
|
:return: The input step as an ElnEntryStep.
|
|
902
1213
|
"""
|
|
903
1214
|
return self.get_step(step) if isinstance(step, str) else step
|
|
@@ -905,21 +1216,10 @@ class ExperimentHandler:
|
|
|
905
1216
|
def __get_experiment_options(self) -> dict[str, str]:
|
|
906
1217
|
"""
|
|
907
1218
|
Cache the experiment options if they haven't been cached yet.
|
|
1219
|
+
|
|
908
1220
|
:return: The options for this experiment.
|
|
909
1221
|
"""
|
|
910
1222
|
if hasattr(self, "_ExperimentHandler__exp_options"):
|
|
911
1223
|
return self.__exp_options
|
|
912
1224
|
self.__exp_options = self.__eln_man.get_notebook_experiment_options(self.__exp_id)
|
|
913
1225
|
return self.__exp_options
|
|
914
|
-
|
|
915
|
-
def __get_step_options(self, step: Step) -> dict[str, str]:
|
|
916
|
-
"""
|
|
917
|
-
Cache the options for the input step if they haven't been cached yet.
|
|
918
|
-
:return: The entry options for the input step.
|
|
919
|
-
"""
|
|
920
|
-
step = self.__to_eln_step(step)
|
|
921
|
-
if step in self.__step_options:
|
|
922
|
-
return self.__step_options.get(step)
|
|
923
|
-
options: dict[str, str] = step.get_options()
|
|
924
|
-
self.__step_options.update({step: options})
|
|
925
|
-
return options
|