sapiopycommons 2025.4.8a473__py3-none-any.whl → 2025.4.9a150__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sapiopycommons might be problematic. Click here for more details.
- sapiopycommons/callbacks/callback_util.py +392 -1262
- sapiopycommons/callbacks/field_builder.py +0 -2
- sapiopycommons/chem/Molecules.py +2 -0
- sapiopycommons/customreport/term_builder.py +1 -1
- sapiopycommons/datatype/attachment_util.py +2 -4
- sapiopycommons/datatype/data_fields.py +1 -23
- sapiopycommons/eln/experiment_handler.py +279 -933
- sapiopycommons/eln/experiment_report_util.py +10 -15
- sapiopycommons/eln/plate_designer.py +59 -159
- sapiopycommons/files/file_bridge.py +0 -76
- sapiopycommons/files/file_bridge_handler.py +110 -325
- sapiopycommons/files/file_data_handler.py +2 -2
- sapiopycommons/files/file_util.py +15 -40
- sapiopycommons/files/file_validator.py +5 -6
- sapiopycommons/files/file_writer.py +1 -1
- sapiopycommons/flowcyto/flow_cyto.py +1 -1
- sapiopycommons/general/accession_service.py +3 -3
- sapiopycommons/general/aliases.py +28 -51
- sapiopycommons/general/audit_log.py +2 -2
- sapiopycommons/general/custom_report_util.py +1 -24
- sapiopycommons/general/exceptions.py +2 -41
- sapiopycommons/general/popup_util.py +2 -2
- sapiopycommons/multimodal/multimodal.py +0 -1
- sapiopycommons/processtracking/custom_workflow_handler.py +30 -46
- sapiopycommons/recordmodel/record_handler.py +159 -547
- sapiopycommons/rules/eln_rule_handler.py +30 -41
- sapiopycommons/rules/on_save_rule_handler.py +30 -41
- sapiopycommons/webhook/webhook_handlers.py +55 -448
- sapiopycommons/webhook/webservice_handlers.py +2 -2
- {sapiopycommons-2025.4.8a473.dist-info → sapiopycommons-2025.4.9a150.dist-info}/METADATA +1 -1
- sapiopycommons-2025.4.9a150.dist-info/RECORD +59 -0
- sapiopycommons/customreport/auto_pagers.py +0 -281
- sapiopycommons/eln/experiment_cache.py +0 -173
- sapiopycommons/eln/experiment_step_factory.py +0 -474
- sapiopycommons/eln/experiment_tags.py +0 -7
- sapiopycommons/eln/step_creation.py +0 -235
- sapiopycommons/general/data_structure_util.py +0 -115
- sapiopycommons/general/directive_util.py +0 -86
- sapiopycommons/samples/aliquot.py +0 -48
- sapiopycommons-2025.4.8a473.dist-info/RECORD +0 -67
- {sapiopycommons-2025.4.8a473.dist-info → sapiopycommons-2025.4.9a150.dist-info}/WHEEL +0 -0
- {sapiopycommons-2025.4.8a473.dist-info → sapiopycommons-2025.4.9a150.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,16 +4,15 @@ from sapiopylib.rest.pojo.CustomReport import CustomReportCriteria, AbstractRepo
|
|
|
4
4
|
from sapiopylib.rest.pojo.datatype.FieldDefinition import FieldType
|
|
5
5
|
from sapiopylib.rest.pojo.eln.ElnExperiment import ElnExperiment, ElnExperimentQueryCriteria
|
|
6
6
|
from sapiopylib.rest.pojo.eln.SapioELNEnums import ElnExperimentStatus, ElnBaseDataType
|
|
7
|
-
from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
|
|
8
7
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
|
|
9
8
|
|
|
10
|
-
from sapiopycommons.customreport.auto_pagers import CustomReportDictAutoPager
|
|
11
9
|
from sapiopycommons.customreport.custom_report_builder import CustomReportBuilder
|
|
12
10
|
from sapiopycommons.customreport.term_builder import TermBuilder
|
|
13
11
|
from sapiopycommons.datatype.pseudo_data_types import EnbEntryOptionsPseudoDef, NotebookExperimentOptionPseudoDef, \
|
|
14
12
|
NotebookExperimentPseudoDef, ExperimentEntryRecordPseudoDef, EnbEntryPseudoDef
|
|
15
13
|
from sapiopycommons.general.aliases import SapioRecord, UserIdentifier, AliasUtil, FieldValue, \
|
|
16
14
|
ExperimentEntryIdentifier, ExperimentIdentifier
|
|
15
|
+
from sapiopycommons.general.custom_report_util import CustomReportUtil
|
|
17
16
|
from sapiopycommons.general.exceptions import SapioException
|
|
18
17
|
from sapiopycommons.recordmodel.record_handler import RecordHandler
|
|
19
18
|
|
|
@@ -202,7 +201,7 @@ class ExperimentReportUtil:
|
|
|
202
201
|
criteria = report_builder.build_report_criteria()
|
|
203
202
|
|
|
204
203
|
ret_val: dict[SapioRecord, int] = {}
|
|
205
|
-
rows: list[dict[str, FieldValue]] =
|
|
204
|
+
rows: list[dict[str, FieldValue]] = CustomReportUtil.run_custom_report(context, criteria)
|
|
206
205
|
for row in rows:
|
|
207
206
|
dt: str = row[EnbEntryPseudoDef.DATA_TYPE_NAME__FIELD_NAME.field_name]
|
|
208
207
|
exp_id: int = row[EnbEntryPseudoDef.EXPERIMENT_ID__FIELD_NAME.field_name]
|
|
@@ -210,20 +209,16 @@ class ExperimentReportUtil:
|
|
|
210
209
|
ret_val[record] = exp_id
|
|
211
210
|
return ret_val
|
|
212
211
|
|
|
213
|
-
# CR-47491: Support providing a data type name string to receive PyRecordModels instead of requiring a WrapperType.
|
|
214
212
|
@staticmethod
|
|
215
213
|
def map_experiments_to_records_of_type(context: UserIdentifier, exp_ids: list[ExperimentIdentifier],
|
|
216
|
-
wrapper_type: type[WrappedType]
|
|
217
|
-
-> dict[int, list[WrappedType] | list[PyRecordModel]]:
|
|
214
|
+
wrapper_type: type[WrappedType]) -> dict[int, list[WrappedType]]:
|
|
218
215
|
"""
|
|
219
216
|
Return a dictionary mapping each experiment id to a list of records of the given type that were used in each
|
|
220
217
|
experiment. If an experiment didn't use any records of the given type then it will be mapped to an empty list.
|
|
221
218
|
|
|
222
219
|
:param context: The current webhook context or a user object to send requests from.
|
|
223
220
|
:param exp_ids: A list of experiment identifiers.
|
|
224
|
-
:param wrapper_type: The record model wrapper
|
|
225
|
-
query for. If a data type name is provided, the returned records will be PyRecordModels instead of
|
|
226
|
-
WrappedRecordModels.
|
|
221
|
+
:param wrapper_type: The record model wrapper to use, corresponds to which data type we will query for.
|
|
227
222
|
:return: A dictionary mapping each experiment id to a list of records of the given type that were used in that
|
|
228
223
|
experiment.
|
|
229
224
|
"""
|
|
@@ -232,7 +227,7 @@ class ExperimentReportUtil:
|
|
|
232
227
|
|
|
233
228
|
user = AliasUtil.to_sapio_user(context)
|
|
234
229
|
record_handler = RecordHandler(user)
|
|
235
|
-
data_type_name: str =
|
|
230
|
+
data_type_name: str = wrapper_type.get_wrapper_data_type_name()
|
|
236
231
|
|
|
237
232
|
exp_ids: list[int] = AliasUtil.to_notebook_ids(exp_ids)
|
|
238
233
|
rows = ExperimentReportUtil.__get_record_experiment_relation_rows(user, data_type_name, exp_ids=exp_ids)
|
|
@@ -273,7 +268,7 @@ class ExperimentReportUtil:
|
|
|
273
268
|
|
|
274
269
|
# Ensure that each experiment appears in the dictionary, even if it has no experiment options.
|
|
275
270
|
options: dict[int, dict[str, str]] = {x: {} for x in exp_ids}
|
|
276
|
-
results: list[dict[str, FieldValue]] =
|
|
271
|
+
results: list[dict[str, FieldValue]] = CustomReportUtil.run_custom_report(context, report)
|
|
277
272
|
for row in results:
|
|
278
273
|
exp_id: int = row[NotebookExperimentOptionPseudoDef.EXPERIMENT_ID__FIELD_NAME.field_name]
|
|
279
274
|
key: str = row[NotebookExperimentOptionPseudoDef.OPTION_KEY__FIELD_NAME.field_name]
|
|
@@ -304,7 +299,7 @@ class ExperimentReportUtil:
|
|
|
304
299
|
|
|
305
300
|
# Ensure that each entry appears in the dictionary, even if it has no entry options.
|
|
306
301
|
options: dict[int, dict[str, str]] = {x: {} for x in entries}
|
|
307
|
-
results: list[dict[str, FieldValue]] =
|
|
302
|
+
results: list[dict[str, FieldValue]] = CustomReportUtil.run_custom_report(context, report)
|
|
308
303
|
for row in results:
|
|
309
304
|
entry_id: int = row[EnbEntryOptionsPseudoDef.ENTRY_ID__FIELD_NAME.field_name]
|
|
310
305
|
key: str = row[EnbEntryOptionsPseudoDef.ENTRY_OPTION_KEY__FIELD_NAME.field_name]
|
|
@@ -337,7 +332,7 @@ class ExperimentReportUtil:
|
|
|
337
332
|
report = report_builder.build_report_criteria()
|
|
338
333
|
|
|
339
334
|
ret_val: dict[int, str] = {}
|
|
340
|
-
results: list[dict[str, FieldValue]] =
|
|
335
|
+
results: list[dict[str, FieldValue]] = CustomReportUtil.run_custom_report(context, report)
|
|
341
336
|
for row in results:
|
|
342
337
|
exp_id: int = row[NotebookExperimentPseudoDef.EXPERIMENT_ID__FIELD_NAME.field_name]
|
|
343
338
|
name: str = row["TemplateExperimentName"]
|
|
@@ -586,7 +581,7 @@ class ExperimentReportUtil:
|
|
|
586
581
|
"""
|
|
587
582
|
user = AliasUtil.to_sapio_user(context)
|
|
588
583
|
exp_ids: list[int] = []
|
|
589
|
-
for row in
|
|
584
|
+
for row in CustomReportUtil.run_custom_report(user, report):
|
|
590
585
|
exp_ids.append(row[NotebookExperimentPseudoDef.EXPERIMENT_ID__FIELD_NAME.field_name])
|
|
591
586
|
if not exp_ids:
|
|
592
587
|
return []
|
|
@@ -651,4 +646,4 @@ class ExperimentReportUtil:
|
|
|
651
646
|
report_builder.add_join(records_entry_join, ExperimentEntryRecordPseudoDef.DATA_TYPE_NAME)
|
|
652
647
|
report_builder.add_join(experiment_entry_enb_entry_join, EnbEntryPseudoDef.DATA_TYPE_NAME)
|
|
653
648
|
report_builder.add_join(enb_entry_experiment_join, NotebookExperimentPseudoDef.DATA_TYPE_NAME)
|
|
654
|
-
return
|
|
649
|
+
return CustomReportUtil.run_custom_report(user, report_builder.build_report_criteria())
|
|
@@ -1,64 +1,43 @@
|
|
|
1
1
|
from sapiopylib.rest.utils.Protocols import ElnEntryStep
|
|
2
|
-
from sapiopylib.rest.utils.plates.PlatingUtils import PlateLocation
|
|
3
|
-
from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
|
|
4
2
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
|
|
5
3
|
|
|
6
|
-
from sapiopycommons.datatype.data_fields import PlateDesignerWellElementFields
|
|
7
4
|
from sapiopycommons.eln.experiment_handler import ExperimentHandler
|
|
8
|
-
from sapiopycommons.
|
|
9
|
-
from sapiopycommons.general.aliases import RecordIdentifier, AliasUtil, RecordModel, FieldValue
|
|
5
|
+
from sapiopycommons.general.aliases import SapioRecord, RecordIdentifier, AliasUtil
|
|
10
6
|
from sapiopycommons.general.exceptions import SapioException
|
|
11
7
|
from sapiopycommons.recordmodel.record_handler import RecordHandler
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
WellElement = PlateDesignerWellElementFields
|
|
9
|
+
PLATE_IDS_TAG: str = "MultiLayerPlating_Plate_RecordIdList"
|
|
15
10
|
|
|
16
11
|
|
|
17
|
-
|
|
18
|
-
class PlateDesignerEntry(ElnEntryStep):
|
|
12
|
+
class PlateDesignerEntry:
|
|
19
13
|
"""
|
|
20
14
|
A wrapper for 3D plate designer entries in experiments, providing functions for common actions when dealing with
|
|
21
15
|
such entries.
|
|
22
16
|
"""
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
17
|
+
step: ElnEntryStep
|
|
18
|
+
__exp_handler: ExperimentHandler
|
|
19
|
+
__rec_handler: RecordHandler
|
|
20
|
+
__plates: list[SapioRecord] | None
|
|
21
|
+
__aliquots: list[SapioRecord] | None
|
|
22
|
+
__sources: list[SapioRecord] | None
|
|
23
|
+
__designer_elements: list[SapioRecord] | None
|
|
24
|
+
__plate_ids: list[int] | None
|
|
31
25
|
|
|
32
26
|
def __init__(self, step: ElnEntryStep, exp_handler: ExperimentHandler):
|
|
33
27
|
"""
|
|
34
28
|
:param step: The ElnEntryStep that is the 3D plate designer entry.
|
|
35
29
|
:param exp_handler: An ExperimentHandler for the experiment that this entry comes from.
|
|
36
30
|
"""
|
|
37
|
-
|
|
38
|
-
self.
|
|
39
|
-
self.
|
|
40
|
-
self.
|
|
41
|
-
self.
|
|
42
|
-
self.
|
|
43
|
-
self.
|
|
44
|
-
self.
|
|
45
|
-
self._plate_ids = None
|
|
46
|
-
|
|
47
|
-
@property
|
|
48
|
-
def step(self) -> ElnEntryStep:
|
|
49
|
-
return self
|
|
50
|
-
|
|
51
|
-
def clear_cache(self) -> None:
|
|
52
|
-
"""
|
|
53
|
-
Clear the caches for the plates and plate designer well elements in this plate designer entry. This will require
|
|
54
|
-
a new webservice call to get the plates and designer elements the next time they are requested.
|
|
55
|
-
"""
|
|
56
|
-
self._plates = None
|
|
57
|
-
self._designer_elements = None
|
|
58
|
-
self._designer_elements_by_plate = None
|
|
31
|
+
self.step = step
|
|
32
|
+
self.__exp_handler = exp_handler
|
|
33
|
+
self.__rec_handler = RecordHandler(exp_handler.context)
|
|
34
|
+
self.__plates = None
|
|
35
|
+
self.__aliquots = None
|
|
36
|
+
self.__sources = None
|
|
37
|
+
self.__designer_elements = None
|
|
38
|
+
self.__plate_ids = None
|
|
59
39
|
|
|
60
|
-
|
|
61
|
-
def get_plates(self, wrapper_type: type[WrappedType] | None = None) -> list[WrappedType] | list[PyRecordModel]:
|
|
40
|
+
def get_plates(self, wrapper_type: type[WrappedType]) -> list[WrappedType]:
|
|
62
41
|
"""
|
|
63
42
|
Get the plates that are in the designer entry.
|
|
64
43
|
|
|
@@ -66,16 +45,13 @@ class PlateDesignerEntry(ElnEntryStep):
|
|
|
66
45
|
will be invalidated if a set_plates or add_plates call is made, requiring a new webservice call the next time
|
|
67
46
|
this function is called.
|
|
68
47
|
|
|
69
|
-
:param wrapper_type: The record model wrapper to use
|
|
70
|
-
be PyRecordModels instead of WrappedRecordModels.
|
|
48
|
+
:param wrapper_type: The record model wrapper to use.
|
|
71
49
|
:return: A list of the plates in the designer entry.
|
|
72
50
|
"""
|
|
73
|
-
if self.
|
|
74
|
-
return self.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
self._plates = self._rec_handler.query_models_by_id(wrapper_type, self.__get_plate_ids())
|
|
78
|
-
return self._plates
|
|
51
|
+
if self.__plates is not None:
|
|
52
|
+
return self.__plates
|
|
53
|
+
self.__plates = self.__rec_handler.query_models_by_id(wrapper_type, self.__get_plate_ids())
|
|
54
|
+
return self.__plates
|
|
79
55
|
|
|
80
56
|
def set_plates(self, plates: list[RecordIdentifier]) -> None:
|
|
81
57
|
"""
|
|
@@ -100,50 +76,47 @@ class PlateDesignerEntry(ElnEntryStep):
|
|
|
100
76
|
record_ids: list[int] = AliasUtil.to_record_ids(plates)
|
|
101
77
|
self.__set_plate_ids(self.__get_plate_ids() + record_ids)
|
|
102
78
|
|
|
103
|
-
def get_sources(self, wrapper_type: type[WrappedType]
|
|
79
|
+
def get_sources(self, wrapper_type: type[WrappedType]) -> list[WrappedType]:
|
|
104
80
|
"""
|
|
105
81
|
Get the source records that were used to populate the plate designer entry's sample table. This looks for the
|
|
106
|
-
entries that the plate designer entry is
|
|
107
|
-
|
|
82
|
+
entries that the plate designer entry is dependent upon and gets their records if they match the data type name
|
|
83
|
+
of the given wrapper.
|
|
108
84
|
|
|
109
85
|
Makes a webservice call to retrieve the dependent entries if the experiment handler had not already cached it.
|
|
110
86
|
Makes another webservice call to get the records from the dependent entry and caches them for future calls.
|
|
111
87
|
|
|
112
|
-
:param wrapper_type: The record model wrapper
|
|
113
|
-
is provided, then the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
88
|
+
:param wrapper_type: The record model wrapper to use.
|
|
114
89
|
:return: A list of the source records that populate the plate designer entry's sample table.
|
|
115
90
|
"""
|
|
116
|
-
if self.
|
|
117
|
-
return self.
|
|
91
|
+
if self.__sources is not None:
|
|
92
|
+
return self.__sources
|
|
118
93
|
|
|
119
94
|
records: list[WrappedType] = []
|
|
120
|
-
dependent_ids: list[int] = self.step.eln_entry.
|
|
121
|
-
for step in self.
|
|
95
|
+
dependent_ids: list[int] = self.step.eln_entry.dependency_set
|
|
96
|
+
for step in self.__exp_handler.get_all_steps(wrapper_type):
|
|
122
97
|
if step.get_id() in dependent_ids:
|
|
123
|
-
records.extend(self.
|
|
98
|
+
records.extend(self.__exp_handler.get_step_models(step, wrapper_type))
|
|
124
99
|
|
|
125
|
-
self.
|
|
126
|
-
return self.
|
|
100
|
+
self.__sources = records
|
|
101
|
+
return self.__sources
|
|
127
102
|
|
|
128
|
-
def get_aliquots(self, wrapper_type: type[WrappedType]
|
|
103
|
+
def get_aliquots(self, wrapper_type: type[WrappedType]) -> list[WrappedType]:
|
|
129
104
|
"""
|
|
130
105
|
Get the aliquots that were created from this plate designer entry upon its submission.
|
|
131
106
|
|
|
132
107
|
Makes a webservice call to retrieve the aliquots from the plate designer entry and caches them for future calls.
|
|
133
108
|
|
|
134
|
-
:param wrapper_type: The record model wrapper to use.
|
|
135
|
-
PyRecordModels instead of WrappedRecordModels.
|
|
109
|
+
:param wrapper_type: The record model wrapper to use.
|
|
136
110
|
:return: A list of the aliquots created by the plate designer entry.
|
|
137
111
|
"""
|
|
138
|
-
if not self.
|
|
112
|
+
if not self.__exp_handler.step_is_submitted(self.step):
|
|
139
113
|
raise SapioException("The plate designer entry must be submitted before its aliquots can be retrieved.")
|
|
140
|
-
if self.
|
|
141
|
-
return self.
|
|
142
|
-
self.
|
|
143
|
-
return self.
|
|
114
|
+
if self.__aliquots is not None:
|
|
115
|
+
return self.__aliquots
|
|
116
|
+
self.__aliquots = self.__exp_handler.get_step_models(self.step, wrapper_type)
|
|
117
|
+
return self.__aliquots
|
|
144
118
|
|
|
145
|
-
def get_plate_designer_well_elements(self, wrapper_type: type[WrappedType]
|
|
146
|
-
-> list[WrappedType] | list[PyRecordModel]:
|
|
119
|
+
def get_plate_designer_well_elements(self, wrapper_type: type[WrappedType]) -> list[WrappedType]:
|
|
147
120
|
"""
|
|
148
121
|
Get the plate designer well elements for the plates in the plate designer entry. These are the records in the
|
|
149
122
|
system that determine how wells are displayed on each plate in the entry.
|
|
@@ -152,101 +125,28 @@ class PlateDesignerEntry(ElnEntryStep):
|
|
|
152
125
|
This cache will be invalidated if a set_plates or add_plates call is made, requiring a new webservice call the
|
|
153
126
|
next time this function is called.
|
|
154
127
|
|
|
155
|
-
:param wrapper_type: The record model wrapper to use.
|
|
156
|
-
PyRecordModels instead of WrappedRecordModels.
|
|
157
|
-
:return: A list of the plate designer well elements in the designer entry.
|
|
158
|
-
"""
|
|
159
|
-
if self._designer_elements is not None:
|
|
160
|
-
return self._designer_elements
|
|
161
|
-
if wrapper_type is None:
|
|
162
|
-
wrapper_type: str = WellElement.DATA_TYPE_NAME
|
|
163
|
-
self._designer_elements = self._rec_handler.query_models(wrapper_type,
|
|
164
|
-
WellElement.PLATE_RECORD_ID__FIELD,
|
|
165
|
-
self.__get_plate_ids())
|
|
166
|
-
if self._designer_elements_by_plate is None:
|
|
167
|
-
self._designer_elements_by_plate = {}
|
|
168
|
-
self._designer_elements_by_plate.clear()
|
|
169
|
-
for element in self._designer_elements:
|
|
170
|
-
plate_id: int = element.get(WellElement.PLATE_RECORD_ID__FIELD)
|
|
171
|
-
self._designer_elements_by_plate.setdefault(plate_id, []).append(element)
|
|
172
|
-
return self._designer_elements
|
|
173
|
-
|
|
174
|
-
def get_well_elements_by_plate(self, plate: RecordIdentifier, wrapper_type: type[WrappedType] | None = None) \
|
|
175
|
-
-> list[WrappedType] | list[PyRecordModel]:
|
|
176
|
-
"""
|
|
177
|
-
Get the plate designer well elements for the given plate in the plate designer entry. These are the records in
|
|
178
|
-
the system that determine how wells are displayed on the plate in the entry.
|
|
179
|
-
|
|
180
|
-
Makes a webservice call to get the plate designer well elements of the entry and caches them for future calls.
|
|
181
|
-
This cache will be invalidated if a set_plates or add_plates call is made, requiring a new webservice call the
|
|
182
|
-
next time this function is called.
|
|
183
|
-
|
|
184
|
-
:param plate: The plate to get the well elements for.
|
|
185
|
-
:param wrapper_type: The record model wrapper to use. If not provided, the returned records will be
|
|
186
|
-
PyRecordModels instead of WrappedRecordModels.
|
|
128
|
+
:param wrapper_type: The record model wrapper to use.
|
|
187
129
|
:return: A list of the plate designer well elements in the designer entry.
|
|
188
130
|
"""
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
return self._designer_elements_by_plate[plate]
|
|
195
|
-
|
|
196
|
-
def create_well_element(self, sample: RecordModel, plate: RecordModel, location: PlateLocation | None = None,
|
|
197
|
-
wrapper_type: type[WrappedType] | None = None) -> WrappedType | PyRecordModel:
|
|
198
|
-
"""
|
|
199
|
-
Create a new plate designer well element for the input sample and plate. A record model manager store and commit
|
|
200
|
-
must be called to save this new well element to the server.
|
|
201
|
-
|
|
202
|
-
:param sample: The sample that the element is for. Must exist in the system (i.e. have a >0 record ID).
|
|
203
|
-
:param plate: The plate that the element is for. Must exist in the system (i.e. have a >0 record ID).
|
|
204
|
-
:param location: The location of the well element. If not provided, the row and column position fields of the
|
|
205
|
-
sample will be used.
|
|
206
|
-
:param wrapper_type: The record model wrapper to use for the plate designer well element. If not provided, the
|
|
207
|
-
returned record will be a PyRecordModel instead of a WrappedRecordModel.
|
|
208
|
-
:return: The newly created PlateDesignerWellElementModel.
|
|
209
|
-
"""
|
|
210
|
-
# Confirm that we can actually make a designer element for the input records.
|
|
211
|
-
if AliasUtil.to_record_id(sample) <= 0:
|
|
212
|
-
raise SapioException("Cannot create plate designer well element for sample without a record ID.")
|
|
213
|
-
if AliasUtil.to_record_id(plate) <= 0:
|
|
214
|
-
raise SapioException("Cannot create plate designer well element for plate without a record ID.")
|
|
215
|
-
if AliasUtil.to_data_type_name(sample) != "Sample":
|
|
216
|
-
raise SapioException("Sample record must be of type Sample.")
|
|
217
|
-
if AliasUtil.to_data_type_name(plate) != "Plate":
|
|
218
|
-
raise SapioException("Plate record must be of type Plate.")
|
|
219
|
-
|
|
220
|
-
dt: type[WrappedType] | str = wrapper_type if wrapper_type else WellElement.DATA_TYPE_NAME
|
|
221
|
-
plate_id: int = AliasUtil.to_record_id(plate)
|
|
222
|
-
fields: dict[str, FieldValue] = {
|
|
223
|
-
WellElement.SOURCE_RECORD_ID__FIELD: AliasUtil.to_record_id(sample),
|
|
224
|
-
WellElement.PLATE_RECORD_ID__FIELD: plate_id,
|
|
225
|
-
WellElement.ROW_POSITION__FIELD: location.row_pos if location else sample.get_field_value("RowPosition"),
|
|
226
|
-
WellElement.COL_POSITION__FIELD: str(location.col_pos) if location else sample.get_field_value("ColPosition"),
|
|
227
|
-
WellElement.SOURCE_DATA_TYPE_NAME__FIELD: "Sample",
|
|
228
|
-
}
|
|
229
|
-
element = self._rec_handler.add_models_with_data(dt, [fields])[0]
|
|
230
|
-
|
|
231
|
-
# Add the new element to the cache.
|
|
232
|
-
if self._designer_elements is not None:
|
|
233
|
-
self._designer_elements.append(element)
|
|
234
|
-
if self._designer_elements_by_plate is not None and plate_id in self._designer_elements_by_plate:
|
|
235
|
-
self._designer_elements_by_plate.setdefault(plate_id, []).append(element)
|
|
236
|
-
return element
|
|
131
|
+
if self.__designer_elements is not None:
|
|
132
|
+
return self.__designer_elements
|
|
133
|
+
self.__designer_elements = self.__rec_handler.query_models(wrapper_type, "PlateRecordId",
|
|
134
|
+
self.__get_plate_ids())
|
|
135
|
+
return self.__designer_elements
|
|
237
136
|
|
|
238
137
|
def __get_plate_ids(self) -> list[int]:
|
|
239
|
-
if self.
|
|
240
|
-
return self.
|
|
241
|
-
id_tag: str = self.
|
|
138
|
+
if self.__plate_ids is not None:
|
|
139
|
+
return self.__plate_ids
|
|
140
|
+
id_tag: str = self.__exp_handler.get_step_option(self.step, PLATE_IDS_TAG)
|
|
242
141
|
if not id_tag:
|
|
243
142
|
raise SapioException("No plates in the plate designer entry")
|
|
244
|
-
self.
|
|
245
|
-
return self.
|
|
143
|
+
self.__plate_ids = [int(x) for x in id_tag.split(",")]
|
|
144
|
+
return self.__plate_ids
|
|
246
145
|
|
|
247
146
|
def __set_plate_ids(self, record_ids: list[int]) -> None:
|
|
248
147
|
record_ids.sort()
|
|
249
|
-
self.
|
|
250
|
-
self.
|
|
148
|
+
self.__exp_handler.add_step_options(self.step, {PLATE_IDS_TAG: ",".join([str(x) for x in record_ids])})
|
|
149
|
+
self.__plate_ids = record_ids
|
|
251
150
|
# The plates and designer elements caches have been invalidated.
|
|
252
|
-
self.
|
|
151
|
+
self.__plates = None
|
|
152
|
+
self.__designer_elements = None
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
import io
|
|
3
3
|
import urllib.parse
|
|
4
|
-
from typing import Any
|
|
5
4
|
|
|
6
5
|
from requests import Response
|
|
7
6
|
from sapiopylib.rest.User import SapioUser
|
|
@@ -9,36 +8,6 @@ from sapiopylib.rest.User import SapioUser
|
|
|
9
8
|
from sapiopycommons.general.aliases import UserIdentifier, AliasUtil
|
|
10
9
|
|
|
11
10
|
|
|
12
|
-
# FR-47387: Add support for the metadata endpoints in FileBridge.
|
|
13
|
-
class FileBridgeMetadata:
|
|
14
|
-
"""
|
|
15
|
-
Metadata for a file or directory in FileBridge.
|
|
16
|
-
"""
|
|
17
|
-
file_name: str
|
|
18
|
-
"""The name of the file or directory."""
|
|
19
|
-
is_file: bool
|
|
20
|
-
"""True if the metadata is for a file, False if it is for a directory."""
|
|
21
|
-
is_directory: bool
|
|
22
|
-
"""True if the metadata is for a directory, False if it is for a file."""
|
|
23
|
-
size: int
|
|
24
|
-
"""The size of the file in bytes. For directories, this value will always be zero."""
|
|
25
|
-
creation_time: int
|
|
26
|
-
"""The time the file or directory was created, in milliseconds since the epoch."""
|
|
27
|
-
last_accessed_time: int
|
|
28
|
-
"""The time the file or directory was last accessed, in milliseconds since the epoch."""
|
|
29
|
-
last_modified_time: int
|
|
30
|
-
"""The time the file or directory was last modified, in milliseconds since the epoch."""
|
|
31
|
-
|
|
32
|
-
def __init__(self, json_dict: dict[str, Any]):
|
|
33
|
-
self.file_name = json_dict['fileName']
|
|
34
|
-
self.is_file = json_dict['isFile']
|
|
35
|
-
self.is_directory = json_dict['isDirectory']
|
|
36
|
-
self.size = json_dict['size']
|
|
37
|
-
self.creation_time = json_dict['creationTime']
|
|
38
|
-
self.last_accessed_time = json_dict['lastAccessTime']
|
|
39
|
-
self.last_modified_time = json_dict['lastModifiedTime']
|
|
40
|
-
|
|
41
|
-
|
|
42
11
|
# FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
|
|
43
12
|
class FileBridge:
|
|
44
13
|
@staticmethod
|
|
@@ -168,48 +137,3 @@ class FileBridge:
|
|
|
168
137
|
user: SapioUser = AliasUtil.to_sapio_user(context)
|
|
169
138
|
response = user.delete(sub_path, params=params)
|
|
170
139
|
user.raise_for_status(response)
|
|
171
|
-
|
|
172
|
-
@staticmethod
|
|
173
|
-
def file_metadata(context: UserIdentifier, bridge_name: str, file_path: str) -> FileBridgeMetadata:
|
|
174
|
-
"""
|
|
175
|
-
Get metadata for a file or directory in FileBridge.
|
|
176
|
-
|
|
177
|
-
The file path may be to a directory, in which case only the metadata for that directory will be returned. If you
|
|
178
|
-
want the metadata for the contents of a directory, then use the directory_metadata function.
|
|
179
|
-
|
|
180
|
-
:param context: The current webhook context or a user object to send requests from.
|
|
181
|
-
:param bridge_name: The name of the bridge to use. This is the "connection name" in the
|
|
182
|
-
file bridge configurations.
|
|
183
|
-
:param file_path: The path to the file to retrieve the metadata from.
|
|
184
|
-
:return: The metadata for the file.
|
|
185
|
-
"""
|
|
186
|
-
sub_path = '/ext/filebridge/file/metadata'
|
|
187
|
-
params = {
|
|
188
|
-
'Filepath': f"bridge://{bridge_name}/{file_path}"
|
|
189
|
-
}
|
|
190
|
-
user: SapioUser = AliasUtil.to_sapio_user(context)
|
|
191
|
-
response = user.get(sub_path, params=params)
|
|
192
|
-
user.raise_for_status(response)
|
|
193
|
-
response_body: dict[str, Any] = response.json()
|
|
194
|
-
return FileBridgeMetadata(response_body)
|
|
195
|
-
|
|
196
|
-
@staticmethod
|
|
197
|
-
def directory_metadata(context: UserIdentifier, bridge_name: str, file_path: str) -> list[FileBridgeMetadata]:
|
|
198
|
-
"""
|
|
199
|
-
Get metadata for every file or nested directory in a directory in FileBridge.
|
|
200
|
-
|
|
201
|
-
:param context: The current webhook context or a user object to send requests from.
|
|
202
|
-
:param bridge_name: The name of the bridge to use. This is the "connection name" in the
|
|
203
|
-
file bridge configurations.
|
|
204
|
-
:param file_path: The path to the directory to retrieve the metadata of the contents.
|
|
205
|
-
:return: A list of the metadata for the contents of the directory.
|
|
206
|
-
"""
|
|
207
|
-
sub_path = '/ext/filebridge/directory/metadata'
|
|
208
|
-
params = {
|
|
209
|
-
'Filepath': f"bridge://{bridge_name}/{file_path}"
|
|
210
|
-
}
|
|
211
|
-
user: SapioUser = AliasUtil.to_sapio_user(context)
|
|
212
|
-
response = user.get(sub_path, params=params)
|
|
213
|
-
user.raise_for_status(response)
|
|
214
|
-
response_body: list[dict[str, Any]] = response.json()
|
|
215
|
-
return [FileBridgeMetadata(x) for x in response_body]
|