sapiopycommons 2025.3.21a458__py3-none-any.whl → 2025.3.26a460__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 +25 -17
- sapiopycommons/customreport/auto_pagers.py +28 -18
- sapiopycommons/datatype/attachment_util.py +4 -2
- sapiopycommons/datatype/data_fields.py +22 -0
- sapiopycommons/eln/experiment_handler.py +1112 -184
- sapiopycommons/eln/experiment_report_util.py +8 -3
- sapiopycommons/eln/experiment_tags.py +7 -0
- sapiopycommons/eln/plate_designer.py +159 -59
- sapiopycommons/processtracking/custom_workflow_handler.py +42 -27
- sapiopycommons/recordmodel/record_handler.py +187 -130
- sapiopycommons/rules/eln_rule_handler.py +33 -29
- sapiopycommons/rules/on_save_rule_handler.py +33 -29
- {sapiopycommons-2025.3.21a458.dist-info → sapiopycommons-2025.3.26a460.dist-info}/METADATA +1 -1
- {sapiopycommons-2025.3.21a458.dist-info → sapiopycommons-2025.3.26a460.dist-info}/RECORD +16 -15
- {sapiopycommons-2025.3.21a458.dist-info → sapiopycommons-2025.3.26a460.dist-info}/WHEEL +0 -0
- {sapiopycommons-2025.3.21a458.dist-info → sapiopycommons-2025.3.26a460.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,6 +4,7 @@ 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
|
|
7
8
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
|
|
8
9
|
|
|
9
10
|
from sapiopycommons.customreport.auto_pagers import CustomReportDictAutoPager
|
|
@@ -209,16 +210,20 @@ class ExperimentReportUtil:
|
|
|
209
210
|
ret_val[record] = exp_id
|
|
210
211
|
return ret_val
|
|
211
212
|
|
|
213
|
+
# CR-47491: Support providing a data type name string to receive PyRecordModels instead of requiring a WrapperType.
|
|
212
214
|
@staticmethod
|
|
213
215
|
def map_experiments_to_records_of_type(context: UserIdentifier, exp_ids: list[ExperimentIdentifier],
|
|
214
|
-
wrapper_type: type[WrappedType]
|
|
216
|
+
wrapper_type: type[WrappedType] | str) \
|
|
217
|
+
-> dict[int, list[WrappedType] | list[PyRecordModel]]:
|
|
215
218
|
"""
|
|
216
219
|
Return a dictionary mapping each experiment id to a list of records of the given type that were used in each
|
|
217
220
|
experiment. If an experiment didn't use any records of the given type then it will be mapped to an empty list.
|
|
218
221
|
|
|
219
222
|
:param context: The current webhook context or a user object to send requests from.
|
|
220
223
|
:param exp_ids: A list of experiment identifiers.
|
|
221
|
-
:param wrapper_type: The record model wrapper to use, corresponds to which data type we will
|
|
224
|
+
:param wrapper_type: The record model wrapper or data type name to use, corresponds to which data type we will
|
|
225
|
+
query for. If a data type name is provided, the returned records will be PyRecordModels instead of
|
|
226
|
+
WrappedRecordModels.
|
|
222
227
|
:return: A dictionary mapping each experiment id to a list of records of the given type that were used in that
|
|
223
228
|
experiment.
|
|
224
229
|
"""
|
|
@@ -227,7 +232,7 @@ class ExperimentReportUtil:
|
|
|
227
232
|
|
|
228
233
|
user = AliasUtil.to_sapio_user(context)
|
|
229
234
|
record_handler = RecordHandler(user)
|
|
230
|
-
data_type_name: str =
|
|
235
|
+
data_type_name: str = AliasUtil.to_data_type_name(wrapper_type)
|
|
231
236
|
|
|
232
237
|
exp_ids: list[int] = AliasUtil.to_notebook_ids(exp_ids)
|
|
233
238
|
rows = ExperimentReportUtil.__get_record_experiment_relation_rows(user, data_type_name, exp_ids=exp_ids)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# CSP paths:
|
|
2
|
+
PLATE_DESIGNER_PLUGIN: str = "com.velox.gwt.client.plugins.multilayerplating.core.ElnMultiLayerPlatingClientPlugin"
|
|
3
|
+
|
|
4
|
+
# 3D Plate Designer entry tags:
|
|
5
|
+
PLATE_IDS_TAG: str = "MultiLayerPlating_Plate_RecordIdList"
|
|
6
|
+
PLATE_CONFIG_PREFS_TAG: str = "MultiLayerPlating_Entry_PrePlating_Prefs"
|
|
7
|
+
PLATE_LAYER_PREFS_TAG: str = "MultiLayerPlating_Entry_Prefs"
|
|
@@ -1,43 +1,64 @@
|
|
|
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
|
|
2
4
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
|
|
3
5
|
|
|
6
|
+
from sapiopycommons.datatype.data_fields import PlateDesignerWellElementFields
|
|
4
7
|
from sapiopycommons.eln.experiment_handler import ExperimentHandler
|
|
5
|
-
from sapiopycommons.
|
|
8
|
+
from sapiopycommons.eln.experiment_tags import PLATE_IDS_TAG
|
|
9
|
+
from sapiopycommons.general.aliases import RecordIdentifier, AliasUtil, RecordModel, FieldValue
|
|
6
10
|
from sapiopycommons.general.exceptions import SapioException
|
|
7
11
|
from sapiopycommons.recordmodel.record_handler import RecordHandler
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
# Shorthands for longer type names.
|
|
14
|
+
WellElement = PlateDesignerWellElementFields
|
|
10
15
|
|
|
11
16
|
|
|
12
|
-
class
|
|
17
|
+
# FR-47486: Change the PlateDesignerEntry class to extend ElnEntryStep instead of containing one.
|
|
18
|
+
class PlateDesignerEntry(ElnEntryStep):
|
|
13
19
|
"""
|
|
14
20
|
A wrapper for 3D plate designer entries in experiments, providing functions for common actions when dealing with
|
|
15
21
|
such entries.
|
|
16
22
|
"""
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
_exp_handler: ExperimentHandler
|
|
24
|
+
_rec_handler: RecordHandler
|
|
25
|
+
_plates: list[RecordModel] | None
|
|
26
|
+
_aliquots: list[RecordModel] | None
|
|
27
|
+
_sources: list[RecordModel] | None
|
|
28
|
+
_designer_elements: list[RecordModel] | None
|
|
29
|
+
_designer_elements_by_plate: dict[int, list[RecordModel]] | None
|
|
30
|
+
_plate_ids: list[int] | None
|
|
25
31
|
|
|
26
32
|
def __init__(self, step: ElnEntryStep, exp_handler: ExperimentHandler):
|
|
27
33
|
"""
|
|
28
34
|
:param step: The ElnEntryStep that is the 3D plate designer entry.
|
|
29
35
|
:param exp_handler: An ExperimentHandler for the experiment that this entry comes from.
|
|
30
36
|
"""
|
|
31
|
-
|
|
32
|
-
self.
|
|
33
|
-
self.
|
|
34
|
-
self.
|
|
35
|
-
self.
|
|
36
|
-
self.
|
|
37
|
-
self.
|
|
38
|
-
self.
|
|
37
|
+
super().__init__(exp_handler.protocol, step.eln_entry)
|
|
38
|
+
self._exp_handler = exp_handler
|
|
39
|
+
self._rec_handler = RecordHandler(exp_handler.user)
|
|
40
|
+
self._plates = None
|
|
41
|
+
self._aliquots = None
|
|
42
|
+
self._sources = None
|
|
43
|
+
self._designer_elements = None
|
|
44
|
+
self._designer_elements_by_plate = None
|
|
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
|
|
39
59
|
|
|
40
|
-
|
|
60
|
+
# CR-47491: Support not providing a wrapper type to receive PyRecordModels instead of WrappedRecordModels.
|
|
61
|
+
def get_plates(self, wrapper_type: type[WrappedType] | None = None) -> list[WrappedType] | list[PyRecordModel]:
|
|
41
62
|
"""
|
|
42
63
|
Get the plates that are in the designer entry.
|
|
43
64
|
|
|
@@ -45,13 +66,16 @@ class PlateDesignerEntry:
|
|
|
45
66
|
will be invalidated if a set_plates or add_plates call is made, requiring a new webservice call the next time
|
|
46
67
|
this function is called.
|
|
47
68
|
|
|
48
|
-
:param wrapper_type: The record model wrapper to use.
|
|
69
|
+
:param wrapper_type: The record model wrapper to use on the plates. If not provided, the returned records will
|
|
70
|
+
be PyRecordModels instead of WrappedRecordModels.
|
|
49
71
|
:return: A list of the plates in the designer entry.
|
|
50
72
|
"""
|
|
51
|
-
if self.
|
|
52
|
-
return self.
|
|
53
|
-
|
|
54
|
-
|
|
73
|
+
if self._plates is not None:
|
|
74
|
+
return self._plates
|
|
75
|
+
if wrapper_type is None:
|
|
76
|
+
wrapper_type = "Plate"
|
|
77
|
+
self._plates = self._rec_handler.query_models_by_id(wrapper_type, self.__get_plate_ids())
|
|
78
|
+
return self._plates
|
|
55
79
|
|
|
56
80
|
def set_plates(self, plates: list[RecordIdentifier]) -> None:
|
|
57
81
|
"""
|
|
@@ -76,47 +100,50 @@ class PlateDesignerEntry:
|
|
|
76
100
|
record_ids: list[int] = AliasUtil.to_record_ids(plates)
|
|
77
101
|
self.__set_plate_ids(self.__get_plate_ids() + record_ids)
|
|
78
102
|
|
|
79
|
-
def get_sources(self, wrapper_type: type[WrappedType]) -> list[WrappedType]:
|
|
103
|
+
def get_sources(self, wrapper_type: type[WrappedType] | str) -> list[WrappedType] | list[PyRecordModel]:
|
|
80
104
|
"""
|
|
81
105
|
Get the source records that were used to populate the plate designer entry's sample table. This looks for the
|
|
82
|
-
entries that the plate designer entry is dependent upon and gets their records if they match the
|
|
83
|
-
of the given wrapper.
|
|
106
|
+
entries that the plate designer entry is implicitly dependent upon and gets their records if they match the
|
|
107
|
+
data type name of the given wrapper.
|
|
84
108
|
|
|
85
109
|
Makes a webservice call to retrieve the dependent entries if the experiment handler had not already cached it.
|
|
86
110
|
Makes another webservice call to get the records from the dependent entry and caches them for future calls.
|
|
87
111
|
|
|
88
|
-
:param wrapper_type: The record model wrapper to
|
|
112
|
+
:param wrapper_type: The record model wrapper or data type name of the records to retrieve. If a data type name
|
|
113
|
+
is provided, then the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
89
114
|
:return: A list of the source records that populate the plate designer entry's sample table.
|
|
90
115
|
"""
|
|
91
|
-
if self.
|
|
92
|
-
return self.
|
|
116
|
+
if self._sources is not None:
|
|
117
|
+
return self._sources
|
|
93
118
|
|
|
94
119
|
records: list[WrappedType] = []
|
|
95
|
-
dependent_ids: list[int] = self.step.eln_entry.
|
|
96
|
-
for step in self.
|
|
120
|
+
dependent_ids: list[int] = self.step.eln_entry.related_entry_id_set
|
|
121
|
+
for step in self._exp_handler.get_all_steps(wrapper_type):
|
|
97
122
|
if step.get_id() in dependent_ids:
|
|
98
|
-
records.extend(self.
|
|
123
|
+
records.extend(self._exp_handler.get_step_models(step, wrapper_type))
|
|
99
124
|
|
|
100
|
-
self.
|
|
101
|
-
return self.
|
|
125
|
+
self._sources = records
|
|
126
|
+
return self._sources
|
|
102
127
|
|
|
103
|
-
def get_aliquots(self, wrapper_type: type[WrappedType]) -> list[WrappedType]:
|
|
128
|
+
def get_aliquots(self, wrapper_type: type[WrappedType] | None = None) -> list[WrappedType] | list[PyRecordModel]:
|
|
104
129
|
"""
|
|
105
130
|
Get the aliquots that were created from this plate designer entry upon its submission.
|
|
106
131
|
|
|
107
132
|
Makes a webservice call to retrieve the aliquots from the plate designer entry and caches them for future calls.
|
|
108
133
|
|
|
109
|
-
:param wrapper_type: The record model wrapper to use.
|
|
134
|
+
:param wrapper_type: The record model wrapper to use. If not provided, the returned records will be
|
|
135
|
+
PyRecordModels instead of WrappedRecordModels.
|
|
110
136
|
:return: A list of the aliquots created by the plate designer entry.
|
|
111
137
|
"""
|
|
112
|
-
if not self.
|
|
138
|
+
if not self._exp_handler.step_is_submitted(self.step):
|
|
113
139
|
raise SapioException("The plate designer entry must be submitted before its aliquots can be retrieved.")
|
|
114
|
-
if self.
|
|
115
|
-
return self.
|
|
116
|
-
self.
|
|
117
|
-
return self.
|
|
140
|
+
if self._aliquots is not None:
|
|
141
|
+
return self._aliquots
|
|
142
|
+
self._aliquots = self._exp_handler.get_step_models(self.step, wrapper_type)
|
|
143
|
+
return self._aliquots
|
|
118
144
|
|
|
119
|
-
def get_plate_designer_well_elements(self, wrapper_type: type[WrappedType])
|
|
145
|
+
def get_plate_designer_well_elements(self, wrapper_type: type[WrappedType] | None = None) \
|
|
146
|
+
-> list[WrappedType] | list[PyRecordModel]:
|
|
120
147
|
"""
|
|
121
148
|
Get the plate designer well elements for the plates in the plate designer entry. These are the records in the
|
|
122
149
|
system that determine how wells are displayed on each plate in the entry.
|
|
@@ -125,28 +152,101 @@ class PlateDesignerEntry:
|
|
|
125
152
|
This cache will be invalidated if a set_plates or add_plates call is made, requiring a new webservice call the
|
|
126
153
|
next time this function is called.
|
|
127
154
|
|
|
128
|
-
:param wrapper_type: The record model wrapper to use.
|
|
155
|
+
:param wrapper_type: The record model wrapper to use. If not provided, the returned records will be
|
|
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.
|
|
129
187
|
:return: A list of the plate designer well elements in the designer entry.
|
|
130
188
|
"""
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
189
|
+
plate: int = AliasUtil.to_record_id(plate)
|
|
190
|
+
if plate not in self.__get_plate_ids():
|
|
191
|
+
raise SapioException(f"Plate record ID {plate} is not in this plate designer entry.")
|
|
192
|
+
if self._designer_elements_by_plate is None:
|
|
193
|
+
self.get_plate_designer_well_elements(wrapper_type)
|
|
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
|
|
136
237
|
|
|
137
238
|
def __get_plate_ids(self) -> list[int]:
|
|
138
|
-
if self.
|
|
139
|
-
return self.
|
|
140
|
-
id_tag: str = self.
|
|
239
|
+
if self._plate_ids is not None:
|
|
240
|
+
return self._plate_ids
|
|
241
|
+
id_tag: str = self._exp_handler.get_step_option(self.step, PLATE_IDS_TAG)
|
|
141
242
|
if not id_tag:
|
|
142
243
|
raise SapioException("No plates in the plate designer entry")
|
|
143
|
-
self.
|
|
144
|
-
return self.
|
|
244
|
+
self._plate_ids = [int(x) for x in id_tag.split(",")]
|
|
245
|
+
return self._plate_ids
|
|
145
246
|
|
|
146
247
|
def __set_plate_ids(self, record_ids: list[int]) -> None:
|
|
147
248
|
record_ids.sort()
|
|
148
|
-
self.
|
|
149
|
-
self.
|
|
249
|
+
self._exp_handler.add_step_options(self.step, {PLATE_IDS_TAG: ",".join([str(x) for x in record_ids])})
|
|
250
|
+
self._plate_ids = record_ids
|
|
150
251
|
# The plates and designer elements caches have been invalidated.
|
|
151
|
-
self.
|
|
152
|
-
self.__designer_elements = None
|
|
252
|
+
self.clear_cache()
|
|
@@ -3,6 +3,7 @@ from typing import Iterable
|
|
|
3
3
|
from sapiopylib.rest.User import SapioUser
|
|
4
4
|
from sapiopylib.rest.pojo.CustomReport import CustomReportCriteria
|
|
5
5
|
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
6
|
+
from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
|
|
6
7
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
|
|
7
8
|
|
|
8
9
|
from sapiopycommons.customreport.auto_pagers import CustomReportDictAutoPager, CustomReportRecordAutoPager
|
|
@@ -109,19 +110,22 @@ class QueueItemHandler:
|
|
|
109
110
|
else:
|
|
110
111
|
self.context = None
|
|
111
112
|
|
|
112
|
-
|
|
113
|
+
# CR-47491: Support providing a data type name string to receive PyRecordModels instead of requiring a WrapperType.
|
|
114
|
+
def get_process_queue_items_from_context(self, wrapper: type[WrappedType] | str,
|
|
113
115
|
context: SapioWebhookContext | ProcessQueueContext | None = None) \
|
|
114
|
-
-> list[WrappedType]:
|
|
116
|
+
-> list[WrappedType] | list[PyRecordModel]:
|
|
115
117
|
"""
|
|
116
118
|
When you launch records from a custom process queue, the process queue items related to the selected records
|
|
117
119
|
are provided as record IDs to the process queue context. Using these record IDs, query for the queue item
|
|
118
120
|
records and wrap them as record models.
|
|
119
121
|
|
|
120
|
-
:param wrapper: The record model wrapper for the process queue items.
|
|
122
|
+
:param wrapper: The record model wrapper or data type name for the process queue items.
|
|
121
123
|
:param context: If this handler was not initialized with a context object, or you wish to retrieve
|
|
122
124
|
data from a different context object than the initializing context, then provide the context to retrieve the
|
|
123
125
|
record IDs from.
|
|
124
126
|
:return: The process queue items corresponding to the record IDs from the context wrapped as record models.
|
|
127
|
+
If a data type name was used instead of a model wrapper, then the returned records will be PyRecordModels
|
|
128
|
+
instead of WrappedRecordModels.
|
|
125
129
|
"""
|
|
126
130
|
if context is None and self.context is not None:
|
|
127
131
|
record_ids: list[int] = self.context.process_queue_item_record_ids
|
|
@@ -175,46 +179,52 @@ class QueueItemHandler:
|
|
|
175
179
|
ret_val[queue_item] = record
|
|
176
180
|
return ret_val
|
|
177
181
|
|
|
178
|
-
def get_queue_items_from_report(self, wrapper: type[WrappedType], criteria: QueueItemReportCriteria) \
|
|
179
|
-
-> list[WrappedType]:
|
|
182
|
+
def get_queue_items_from_report(self, wrapper: type[WrappedType] | None, criteria: QueueItemReportCriteria) \
|
|
183
|
+
-> list[WrappedType] | list[PyRecordModel]:
|
|
180
184
|
"""
|
|
181
185
|
Run a custom report that retrieves every queue item in the system for the given search criteria.
|
|
182
186
|
|
|
183
|
-
:param wrapper: The record model wrapper for the process queue items.
|
|
187
|
+
:param wrapper: The record model wrapper for the process queue items. If not provided, the returned records will
|
|
188
|
+
be PyRecordModels instead of WrappedRecordModels.
|
|
184
189
|
:param criteria: The search criteria to query for queue items with.
|
|
185
190
|
:return: A list of every queue item in the system that matches the search criteria.
|
|
186
191
|
"""
|
|
187
192
|
report = self.build_queue_item_report(criteria)
|
|
188
|
-
|
|
193
|
+
dt: type[WrappedType] | str = wrapper if wrapper else ProcessQueueItemFields.DATA_TYPE_NAME
|
|
194
|
+
return CustomReportRecordAutoPager(self.user, report, dt).get_all_at_once()
|
|
189
195
|
|
|
190
|
-
def get_records_from_item_report(self, wrapper: type[WrappedType],
|
|
191
|
-
criteria: QueueItemReportCriteria = QueueItemReportCriteria())
|
|
196
|
+
def get_records_from_item_report(self, wrapper: type[WrappedType] | str,
|
|
197
|
+
criteria: QueueItemReportCriteria = QueueItemReportCriteria()) \
|
|
198
|
+
-> list[WrappedType] | list[PyRecordModel]:
|
|
192
199
|
"""
|
|
193
200
|
Run a custom report that retrieves for queue items that match the given search criteria, then query for the
|
|
194
201
|
data records that those queue items refer to.
|
|
195
202
|
|
|
196
|
-
:param wrapper: The record model wrapper for the records being queried for.
|
|
203
|
+
:param wrapper: The record model wrapper or data type name for the records being queried for.
|
|
197
204
|
:param criteria: Additional search criteria to filter the results. This function forces the data_type_names
|
|
198
205
|
parameter of the criteria to match the data type of the given record model wrapper.
|
|
199
206
|
:return: A list of all records related to the queue items in the system that match the search criteria.
|
|
207
|
+
If a data type name was used instead of a model wrapper, then the returned records will be PyRecordModels
|
|
208
|
+
instead of WrappedRecordModels.
|
|
200
209
|
"""
|
|
201
210
|
# Don't try to query for process queue items that don't match the data type of this wrapper.
|
|
202
|
-
criteria.data_type_names = [
|
|
211
|
+
criteria.data_type_names = [AliasUtil.to_data_type_name(wrapper)]
|
|
203
212
|
criteria.not_data_type_names = None
|
|
204
213
|
report = self.build_queue_item_report(criteria)
|
|
205
214
|
record_ids: list[int] = [x[ProcessQueueItemFields.DATA_RECORD_ID__FIELD.field_name]
|
|
206
215
|
for x in CustomReportDictAutoPager(self.user, report)]
|
|
207
216
|
return self.rec_handler.query_models_by_id(wrapper, record_ids)
|
|
208
217
|
|
|
209
|
-
def get_queue_items_for_records(self, records: Iterable[SapioRecord], wrapper: type[WrappedType],
|
|
218
|
+
def get_queue_items_for_records(self, records: Iterable[SapioRecord], wrapper: type[WrappedType] | None = None,
|
|
210
219
|
criteria: QueueItemReportCriteria = QueueItemReportCriteria()) \
|
|
211
|
-
-> dict[SapioRecord, list[WrappedType]]:
|
|
220
|
+
-> dict[SapioRecord, list[WrappedType] | list[PyRecordModel]]:
|
|
212
221
|
"""
|
|
213
222
|
Given a list of records, query the system for every process queue item that refers to those records and matches
|
|
214
223
|
the provided search criteria.
|
|
215
224
|
|
|
216
225
|
:param records: The queued records to query for the process queue items of.
|
|
217
|
-
:param wrapper: The record model wrapper for the returned process queue item records.
|
|
226
|
+
:param wrapper: The record model wrapper for the returned process queue item records. If not provided, the
|
|
227
|
+
returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
218
228
|
:param criteria: Additional search criteria to filter the results. This function forces the data_record_ids and
|
|
219
229
|
data_type_names parameters on the criteria to match the given records.
|
|
220
230
|
:return: A dictionary mapping the input records to a list of the process queue items that refer to them. If a
|
|
@@ -226,40 +236,43 @@ class QueueItemHandler:
|
|
|
226
236
|
criteria.not_data_record_ids = None
|
|
227
237
|
criteria.data_type_names = AliasUtil.to_data_type_names(records)
|
|
228
238
|
criteria.not_data_type_names = None
|
|
229
|
-
items: list[WrappedType] = self.get_queue_items_from_report(wrapper, criteria)
|
|
239
|
+
items: list[WrappedType] | list[PyRecordModel] = self.get_queue_items_from_report(wrapper, criteria)
|
|
230
240
|
return self.map_records_to_queue_items(records, items)
|
|
231
241
|
|
|
232
|
-
def get_records_for_queue_items(self, queue_items: Iterable[SapioRecord], wrapper: type[WrappedType]) \
|
|
233
|
-
-> dict[SapioRecord, WrappedType]:
|
|
242
|
+
def get_records_for_queue_items(self, queue_items: Iterable[SapioRecord], wrapper: type[WrappedType] | str) \
|
|
243
|
+
-> dict[SapioRecord, WrappedType | PyRecordModel]:
|
|
234
244
|
"""
|
|
235
245
|
Given a list of process queue items, query the system for the records that those queue items refer to.
|
|
236
246
|
|
|
237
247
|
:param queue_items: The process queue items to query for the referenced records of.
|
|
238
|
-
:param wrapper: The record model wrapper for the records being queried.
|
|
239
|
-
:return: A dictionary mapping the input process queue items to the record tht they refer to.
|
|
248
|
+
:param wrapper: The record model wrapper or data type name for the records being queried.
|
|
249
|
+
:return: A dictionary mapping the input process queue items to the record tht they refer to. If a data type
|
|
250
|
+
name was used instead of a model wrapper, then the returned records will be PyRecordModels instead of
|
|
251
|
+
WrappedRecordModels.
|
|
240
252
|
"""
|
|
241
253
|
record_ids: set[int] = {x.get_field_value(ProcessQueueItemFields.DATA_RECORD_ID__FIELD) for x in queue_items}
|
|
242
|
-
records: list[WrappedType] = self.rec_handler.query_models_by_id(wrapper, record_ids)
|
|
254
|
+
records: list[WrappedType] | list[PyRecordModel] = self.rec_handler.query_models_by_id(wrapper, record_ids)
|
|
243
255
|
return self.map_queue_items_to_records(queue_items, records)
|
|
244
256
|
|
|
245
257
|
def queue_records_for_process(self, records: Iterable[SapioRecord], process: str, step: str,
|
|
246
|
-
wrapper: type[WrappedType]) -> dict[SapioRecord, WrappedType]:
|
|
258
|
+
wrapper: type[WrappedType] | None = None) -> dict[SapioRecord, WrappedType | PyRecordModel]:
|
|
247
259
|
"""
|
|
248
260
|
Given a list of records, create process queue item records for them at the provided process and step names.
|
|
249
261
|
You must store and commit using the record model manager in order for these changes to take effect.
|
|
250
262
|
|
|
251
263
|
:param records: The records to create process queue items for.
|
|
252
|
-
:param wrapper: The record model wrapper for the process queue items being created.
|
|
253
264
|
:param process: The name of the process to queue for.
|
|
254
265
|
:param step: The name of the step in the above process to queue for. This is the "Workflow Name" field of the
|
|
255
266
|
Process Workflow record corresponding to the step you want to assign these records to. For steps that
|
|
256
267
|
launch an experiment, this is the name of the template that will be launched. For the other types of custom
|
|
257
268
|
process steps, this is the "Workflow Name" as defined in the process manager config.
|
|
269
|
+
:param wrapper: The record model wrapper for the process queue items being created. If not provided, the
|
|
270
|
+
returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
258
271
|
:return: A dictionary mapping each input record to the newly created process queue item for that record.
|
|
259
272
|
"""
|
|
260
273
|
ret_val: dict[SapioRecord, WrappedType] = {}
|
|
261
274
|
for record in records:
|
|
262
|
-
item = self.rec_handler.add_model(wrapper)
|
|
275
|
+
item = self.rec_handler.add_model(wrapper if wrapper else ProcessQueueItemFields.DATA_TYPE_NAME)
|
|
263
276
|
item.set_field_values({
|
|
264
277
|
ProcessQueueItemFields.PROCESS_HEADER_NAME__FIELD.field_name: process,
|
|
265
278
|
ProcessQueueItemFields.WORKFLOW_HEADER_NAME__FIELD.field_name: step,
|
|
@@ -271,16 +284,17 @@ class QueueItemHandler:
|
|
|
271
284
|
ret_val[record] = item
|
|
272
285
|
return ret_val
|
|
273
286
|
|
|
274
|
-
def dequeue_records_for_process(self, records: Iterable[SapioRecord], wrapper: type[WrappedType],
|
|
287
|
+
def dequeue_records_for_process(self, records: Iterable[SapioRecord], wrapper: type[WrappedType] | None = None,
|
|
275
288
|
criteria: QueueItemReportCriteria = QueueItemReportCriteria()) \
|
|
276
|
-
-> dict[SapioRecord, list[WrappedType]]:
|
|
289
|
+
-> dict[SapioRecord, list[WrappedType] | list[PyRecordModel]]:
|
|
277
290
|
"""
|
|
278
291
|
Given a list of records, locate the process queue items that refer to them and that match the given search
|
|
279
292
|
criteria and remove them from the queue by setting the ShowInQueue field on the process queue items to false.
|
|
280
293
|
You must store and commit using the record model manager in order for these changes to take effect.
|
|
281
294
|
|
|
282
295
|
:param records: The records to remove from the queue.
|
|
283
|
-
:param wrapper: The record model wrapper for the process queue items being updated.
|
|
296
|
+
:param wrapper: The record model wrapper for the process queue items being updated. If not provided, the
|
|
297
|
+
returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
284
298
|
:param criteria: Additional search criteria to filter the results. This function forces the show_in_queue
|
|
285
299
|
parameter on the criteria to True.
|
|
286
300
|
:return: A dictionary mapping each input record to the queue item records that refer to that record and were
|
|
@@ -289,7 +303,8 @@ class QueueItemHandler:
|
|
|
289
303
|
"""
|
|
290
304
|
# Only locate queue items that are currently visible in the queue.
|
|
291
305
|
criteria.shown_in_queue = True
|
|
292
|
-
dequeue: dict[SapioRecord, list[WrappedType]
|
|
306
|
+
dequeue: dict[SapioRecord, list[WrappedType] | list[PyRecordModel]]
|
|
307
|
+
dequeue = self.get_queue_items_for_records(records, wrapper, criteria)
|
|
293
308
|
for record, items in dequeue.items():
|
|
294
309
|
for item in items:
|
|
295
310
|
item.set_field_value(ProcessQueueItemFields.SHOW_IN_QUEUE__FIELD.field_name, False)
|