sapiopycommons 2025.3.21a458__py3-none-any.whl → 2025.3.25a459__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sapiopycommons might be problematic. Click here for more details.
- sapiopycommons/ai/__init__.py +0 -0
- sapiopycommons/ai/tool_of_tools.py +917 -0
- 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/general/html_formatter.py +456 -0
- sapiopycommons/general/sapio_links.py +12 -4
- 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.25a459.dist-info}/METADATA +1 -1
- {sapiopycommons-2025.3.21a458.dist-info → sapiopycommons-2025.3.25a459.dist-info}/RECORD +20 -16
- {sapiopycommons-2025.3.21a458.dist-info → sapiopycommons-2025.3.25a459.dist-info}/WHEEL +0 -0
- {sapiopycommons-2025.3.21a458.dist-info → sapiopycommons-2025.3.25a459.dist-info}/licenses/LICENSE +0 -0
|
@@ -70,56 +70,70 @@ class RecordHandler:
|
|
|
70
70
|
self.rel_man = self.rec_man.relationship_manager
|
|
71
71
|
self.an_man = RecordModelAncestorManager(self.rec_man)
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
# CR-47491: Support not providing a wrapper type to receive PyRecordModels instead of WrappedRecordModels.
|
|
74
|
+
def wrap_model(self, record: DataRecord | PyRecordModel, wrapper_type: type[WrappedType] | None = None) \
|
|
75
|
+
-> WrappedType | PyRecordModel:
|
|
74
76
|
"""
|
|
75
|
-
Shorthand for adding a single data record as a
|
|
77
|
+
Shorthand for adding a single data record or PyRecordModel as a WrappedRecordModel.
|
|
76
78
|
|
|
77
|
-
:param record: The data record to wrap.
|
|
78
|
-
:param wrapper_type: The record model wrapper to use.
|
|
79
|
+
:param record: The data record or PyRecordModel to wrap.
|
|
80
|
+
:param wrapper_type: The record model wrapper to use. If not provided, the record is returned as a
|
|
81
|
+
PyRecordModel instead of a WrappedRecordModel.
|
|
79
82
|
:return: The record model for the input.
|
|
80
83
|
"""
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
if wrapper_type is not None:
|
|
85
|
+
self.__verify_data_type(record, wrapper_type)
|
|
86
|
+
if isinstance(record, PyRecordModel):
|
|
87
|
+
return self.inst_man.wrap(record, wrapper_type)
|
|
88
|
+
return self.inst_man.add_existing_record_of_type(record, wrapper_type)
|
|
89
|
+
if isinstance(record, PyRecordModel):
|
|
90
|
+
return record
|
|
91
|
+
return self.inst_man.add_existing_record(record)
|
|
83
92
|
|
|
84
|
-
def wrap_models(self, records: Iterable[DataRecord
|
|
93
|
+
def wrap_models(self, records: Iterable[DataRecord | PyRecordModel],
|
|
94
|
+
wrapper_type: type[WrappedType] | None = None) \
|
|
95
|
+
-> list[WrappedType] | list[PyRecordModel]:
|
|
85
96
|
"""
|
|
86
|
-
Shorthand for adding a list of data records as
|
|
97
|
+
Shorthand for adding a list of data records or PyRecordModels as a WrappedRecordModels.
|
|
87
98
|
|
|
88
99
|
:param records: The data records to wrap.
|
|
89
|
-
:param wrapper_type: The record model wrapper to use.
|
|
100
|
+
:param wrapper_type: The record model wrapper to use. If not provided, the records are returned as
|
|
101
|
+
PyRecordModels instead of WrappedRecordModels.
|
|
90
102
|
:return: The record models for the input.
|
|
91
103
|
"""
|
|
92
|
-
self.
|
|
93
|
-
return self.inst_man.add_existing_records_of_type(list(records), wrapper_type)
|
|
104
|
+
return [self.wrap_model(x, wrapper_type) for x in records]
|
|
94
105
|
|
|
95
|
-
|
|
96
|
-
|
|
106
|
+
# CR-47491: Support providing a data type name string to receive PyRecordModels instead of requiring a WrapperType.
|
|
107
|
+
def query_models(self, wrapper_type: type[WrappedType] | str, field: FieldIdentifier,
|
|
108
|
+
value_list: Iterable[FieldValue], page_limit: int | None = None, page_size: int | None = None) \
|
|
109
|
+
-> list[WrappedType] | list[PyRecordModel]:
|
|
97
110
|
"""
|
|
98
111
|
Shorthand for using the data record manager to query for a list of data records by field value
|
|
99
112
|
and then converting the results into a list of record models.
|
|
100
113
|
|
|
101
|
-
:param wrapper_type: The record model wrapper to use.
|
|
114
|
+
:param wrapper_type: The record model wrapper to use, or the data type name of the records.
|
|
102
115
|
:param field: The field to query on.
|
|
103
116
|
:param value_list: The values of the field to query on.
|
|
104
117
|
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages. This parameter
|
|
105
118
|
only functions if you set a page size or the platform enforces a page size.
|
|
106
119
|
:param page_size: The size of the pages to query. If None, the page size may be limited by the platform.
|
|
107
|
-
:return: The record models for the queried records.
|
|
120
|
+
:return: The record models for the queried records. If a data type name was used instead of a model wrapper,
|
|
121
|
+
then the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
108
122
|
"""
|
|
109
123
|
criteria: DataRecordPojoPageCriteria | None = None
|
|
110
124
|
if page_size is not None:
|
|
111
125
|
criteria = DataRecordPojoPageCriteria(page_size=page_size)
|
|
112
126
|
return self.query_models_with_criteria(wrapper_type, field, value_list, criteria, page_limit)[0]
|
|
113
127
|
|
|
114
|
-
def query_and_map_models(self, wrapper_type: type[WrappedType], field: FieldIdentifier,
|
|
128
|
+
def query_and_map_models(self, wrapper_type: type[WrappedType] | str, field: FieldIdentifier,
|
|
115
129
|
value_list: Iterable[FieldValue], page_limit: int | None = None,
|
|
116
130
|
page_size: int | None = None, *, mapping_field: FieldIdentifier | None = None) \
|
|
117
|
-
-> dict[FieldValue, list[WrappedType]]:
|
|
131
|
+
-> dict[FieldValue, list[WrappedType] | list[PyRecordModel]]:
|
|
118
132
|
"""
|
|
119
133
|
Shorthand for using query_models to search for records given values on a specific field and then using
|
|
120
134
|
map_by_field to turn the returned list into a dictionary mapping field values to records.
|
|
121
135
|
|
|
122
|
-
:param wrapper_type: The record model wrapper to use.
|
|
136
|
+
:param wrapper_type: The record model wrapper to use, or the data type name of the records.
|
|
123
137
|
:param field: The field to query and map on.
|
|
124
138
|
:param value_list: The values of the field to query on.
|
|
125
139
|
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages. This parameter
|
|
@@ -127,22 +141,24 @@ class RecordHandler:
|
|
|
127
141
|
:param page_size: The size of the pages to query. If None, the page size may be limited by the platform.
|
|
128
142
|
:param mapping_field: If provided, use this field to map against instead of the field that was queried on.
|
|
129
143
|
:return: The record models for the queried records mapped by field values to the records with that value.
|
|
144
|
+
If a data type name was used instead of a model wrapper, then the returned records will be PyRecordModels
|
|
145
|
+
instead of WrappedRecordModels.
|
|
130
146
|
"""
|
|
131
147
|
if mapping_field is None:
|
|
132
148
|
mapping_field = field
|
|
133
149
|
return self.map_by_field(self.query_models(wrapper_type, field, value_list, page_limit, page_size),
|
|
134
150
|
mapping_field)
|
|
135
151
|
|
|
136
|
-
def query_and_unique_map_models(self, wrapper_type: type[WrappedType], field: FieldIdentifier,
|
|
152
|
+
def query_and_unique_map_models(self, wrapper_type: type[WrappedType] | str, field: FieldIdentifier,
|
|
137
153
|
value_list: Iterable[FieldValue], page_limit: int | None = None,
|
|
138
154
|
page_size: int | None = None, *, mapping_field: FieldIdentifier | None = None) \
|
|
139
|
-
-> dict[FieldValue, WrappedType]:
|
|
155
|
+
-> dict[FieldValue, WrappedType | PyRecordModel]:
|
|
140
156
|
"""
|
|
141
157
|
Shorthand for using query_models to search for records given values on a specific field and then using
|
|
142
158
|
map_by_unique_field to turn the returned list into a dictionary mapping field values to records.
|
|
143
159
|
If any two records share the same field value, throws an exception.
|
|
144
160
|
|
|
145
|
-
:param wrapper_type: The record model wrapper to use.
|
|
161
|
+
:param wrapper_type: The record model wrapper to use, or the data type name of the records.
|
|
146
162
|
:param field: The field to query and map on.
|
|
147
163
|
:param value_list: The values of the field to query on.
|
|
148
164
|
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages. This parameter
|
|
@@ -150,134 +166,150 @@ class RecordHandler:
|
|
|
150
166
|
:param page_size: The size of the pages to query. If None, the page size may be limited by the platform.
|
|
151
167
|
:param mapping_field: If provided, use this field to map against instead of the field that was queried on.
|
|
152
168
|
:return: The record models for the queried records mapped by field values to the record with that value.
|
|
169
|
+
If a data type name was used instead of a model wrapper, then the returned records will be PyRecordModels
|
|
170
|
+
instead of WrappedRecordModels.
|
|
153
171
|
"""
|
|
154
172
|
if mapping_field is None:
|
|
155
173
|
mapping_field = field
|
|
156
174
|
return self.map_by_unique_field(self.query_models(wrapper_type, field, value_list, page_limit, page_size),
|
|
157
175
|
mapping_field)
|
|
158
176
|
|
|
159
|
-
def query_models_with_criteria(self, wrapper_type: type[WrappedType], field: FieldIdentifier,
|
|
177
|
+
def query_models_with_criteria(self, wrapper_type: type[WrappedType] | str, field: FieldIdentifier,
|
|
160
178
|
value_list: Iterable[FieldValue],
|
|
161
179
|
paging_criteria: DataRecordPojoPageCriteria | None = None,
|
|
162
180
|
page_limit: int | None = None) \
|
|
163
|
-
-> tuple[list[WrappedType], DataRecordPojoPageCriteria]:
|
|
181
|
+
-> tuple[list[WrappedType] | list[PyRecordModel], DataRecordPojoPageCriteria]:
|
|
164
182
|
"""
|
|
165
183
|
Shorthand for using the data record manager to query for a list of data records by field value
|
|
166
184
|
and then converting the results into a list of record models.
|
|
167
185
|
|
|
168
|
-
:param wrapper_type: The record model wrapper to use.
|
|
186
|
+
:param wrapper_type: The record model wrapper to use, or the data type name of the records.
|
|
169
187
|
:param field: The field to query on.
|
|
170
188
|
:param value_list: The values of the field to query on.
|
|
171
189
|
:param paging_criteria: The paging criteria to start the query with.
|
|
172
190
|
:param page_limit: The maximum number of pages to query from the starting criteria. If None, exhausts all
|
|
173
191
|
possible pages. This parameter only functions if you set a page size in the paging criteria or the platform
|
|
174
192
|
enforces a page size.
|
|
175
|
-
:return: The record models for the queried records and the final paging criteria.
|
|
193
|
+
:return: The record models for the queried records and the final paging criteria. If a data type name was used
|
|
194
|
+
instead of a model wrapper, then the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
176
195
|
"""
|
|
177
|
-
dt: str =
|
|
196
|
+
dt: str = AliasUtil.to_data_type_name(wrapper_type)
|
|
197
|
+
if isinstance(wrapper_type, str):
|
|
198
|
+
wrapper_type = None
|
|
178
199
|
field: str = AliasUtil.to_data_field_name(field)
|
|
179
200
|
pager = QueryDataRecordsAutoPager(dt, field, list(value_list), self.user, paging_criteria)
|
|
180
201
|
pager.max_page = page_limit
|
|
181
202
|
return self.wrap_models(pager.get_all_at_once(), wrapper_type), pager.next_page_criteria
|
|
182
203
|
|
|
183
|
-
def query_models_by_id(self, wrapper_type: type[WrappedType], ids: Iterable[int],
|
|
184
|
-
page_limit: int | None = None, page_size: int | None = None)
|
|
204
|
+
def query_models_by_id(self, wrapper_type: type[WrappedType] | str, ids: Iterable[int],
|
|
205
|
+
page_limit: int | None = None, page_size: int | None = None) \
|
|
206
|
+
-> list[WrappedType] | list[PyRecordModel]:
|
|
185
207
|
"""
|
|
186
208
|
Shorthand for using the data record manager to query for a list of data records by record ID
|
|
187
209
|
and then converting the results into a list of record models.
|
|
188
210
|
|
|
189
|
-
:param wrapper_type: The record model wrapper to use.
|
|
211
|
+
:param wrapper_type: The record model wrapper to use, or the data type name of the records.
|
|
190
212
|
:param ids: The list of record IDs to query.
|
|
191
213
|
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages. This parameter
|
|
192
214
|
only functions if you set a page size or the platform enforces a page size.
|
|
193
215
|
:param page_size: The size of the pages to query. If None, the page size may be limited by the platform.
|
|
194
|
-
:return: The record models for the queried records.
|
|
216
|
+
:return: The record models for the queried records. If a data type name was used instead of a model wrapper,
|
|
217
|
+
then the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
195
218
|
"""
|
|
196
219
|
criteria: DataRecordPojoPageCriteria | None = None
|
|
197
220
|
if page_size is not None:
|
|
198
221
|
criteria = DataRecordPojoPageCriteria(page_size=page_size)
|
|
199
222
|
return self.query_models_by_id_with_criteria(wrapper_type, ids, criteria, page_limit)[0]
|
|
200
223
|
|
|
201
|
-
def query_models_by_id_with_criteria(self, wrapper_type: type[WrappedType], ids: Iterable[int],
|
|
224
|
+
def query_models_by_id_with_criteria(self, wrapper_type: type[WrappedType] | str, ids: Iterable[int],
|
|
202
225
|
paging_criteria: DataRecordPojoPageCriteria | None = None,
|
|
203
226
|
page_limit: int | None = None) \
|
|
204
|
-
-> tuple[list[WrappedType], DataRecordPojoPageCriteria]:
|
|
227
|
+
-> tuple[list[WrappedType] | list[PyRecordModel], DataRecordPojoPageCriteria]:
|
|
205
228
|
"""
|
|
206
229
|
Shorthand for using the data record manager to query for a list of data records by record ID
|
|
207
230
|
and then converting the results into a list of record models.
|
|
208
231
|
|
|
209
|
-
:param wrapper_type: The record model wrapper to use.
|
|
232
|
+
:param wrapper_type: The record model wrapper to use, or the data type name of the records.
|
|
210
233
|
:param ids: The list of record IDs to query.
|
|
211
234
|
:param paging_criteria: The paging criteria to start the query with.
|
|
212
235
|
:param page_limit: The maximum number of pages to query from the starting criteria. If None, exhausts all
|
|
213
236
|
possible pages. This parameter only functions if you set a page size in the paging criteria or the platform
|
|
214
237
|
enforces a page size.
|
|
215
|
-
:return: The record models for the queried records and the final paging criteria.
|
|
238
|
+
:return: The record models for the queried records and the final paging criteria. If a data type name was used
|
|
239
|
+
instead of a model wrapper, then the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
216
240
|
"""
|
|
217
|
-
dt: str =
|
|
241
|
+
dt: str = AliasUtil.to_data_type_name(wrapper_type)
|
|
242
|
+
if isinstance(wrapper_type, str):
|
|
243
|
+
wrapper_type = None
|
|
218
244
|
pager = QueryDataRecordByIdListAutoPager(dt, list(ids), self.user, paging_criteria)
|
|
219
245
|
pager.max_page = page_limit
|
|
220
246
|
return self.wrap_models(pager.get_all_at_once(), wrapper_type), pager.next_page_criteria
|
|
221
247
|
|
|
222
|
-
def query_models_by_id_and_map(self, wrapper_type: type[WrappedType], ids: Iterable[int],
|
|
248
|
+
def query_models_by_id_and_map(self, wrapper_type: type[WrappedType] | str, ids: Iterable[int],
|
|
223
249
|
page_limit: int | None = None, page_size: int | None = None) \
|
|
224
|
-
-> dict[int, WrappedType]:
|
|
250
|
+
-> dict[int, WrappedType | PyRecordModel]:
|
|
225
251
|
"""
|
|
226
252
|
Shorthand for using the data record manager to query for a list of data records by record ID
|
|
227
253
|
and then converting the results into a dictionary of record ID to the record model for that ID.
|
|
228
254
|
|
|
229
|
-
:param wrapper_type: The record model wrapper to use.
|
|
255
|
+
:param wrapper_type: The record model wrapper to use, or the data type name of the records.
|
|
230
256
|
:param ids: The list of record IDs to query.
|
|
231
257
|
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages. This parameter
|
|
232
258
|
only functions if you set a page size or the platform enforces a page size.
|
|
233
259
|
:param page_size: The size of the pages to query. If None, the page size may be limited by the platform.
|
|
234
260
|
:return: The record models for the queried records mapped in a dictionary by their record ID.
|
|
261
|
+
If a data type name was used instead of a model wrapper, then the returned records will be PyRecordModels
|
|
262
|
+
instead of WrappedRecordModels.
|
|
235
263
|
"""
|
|
236
264
|
return {AliasUtil.to_record_id(x): x for x in self.query_models_by_id(wrapper_type, ids, page_limit, page_size)}
|
|
237
265
|
|
|
238
|
-
def query_all_models(self, wrapper_type: type[WrappedType], page_limit: int | None = None,
|
|
239
|
-
page_size: int | None = None) -> list[WrappedType]:
|
|
266
|
+
def query_all_models(self, wrapper_type: type[WrappedType] | str, page_limit: int | None = None,
|
|
267
|
+
page_size: int | None = None) -> list[WrappedType] | list[PyRecordModel]:
|
|
240
268
|
"""
|
|
241
269
|
Shorthand for using the data record manager to query for all data records of a given type
|
|
242
270
|
and then converting the results into a list of record models.
|
|
243
271
|
|
|
244
|
-
:param wrapper_type: The record model wrapper to use.
|
|
272
|
+
:param wrapper_type: The record model wrapper to use, or the data type name of the records.
|
|
245
273
|
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages. This parameter
|
|
246
274
|
only functions if you set a page size or the platform enforces a page size.
|
|
247
275
|
:param page_size: The size of the pages to query. If None, the page size may be limited by the platform.
|
|
248
|
-
:return: The record models for the queried records.
|
|
276
|
+
:return: The record models for the queried records. If a data type name was used instead of a model wrapper,
|
|
277
|
+
then the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
249
278
|
"""
|
|
250
279
|
criteria: DataRecordPojoPageCriteria | None = None
|
|
251
280
|
if page_size is not None:
|
|
252
281
|
criteria = DataRecordPojoPageCriteria(page_size=page_size)
|
|
253
282
|
return self.query_all_models_with_criteria(wrapper_type, criteria, page_limit)[0]
|
|
254
283
|
|
|
255
|
-
def query_all_models_with_criteria(self, wrapper_type: type[WrappedType],
|
|
284
|
+
def query_all_models_with_criteria(self, wrapper_type: type[WrappedType] | str,
|
|
256
285
|
paging_criteria: DataRecordPojoPageCriteria | None = None,
|
|
257
286
|
page_limit: int | None = None) \
|
|
258
|
-
-> tuple[list[WrappedType], DataRecordPojoPageCriteria]:
|
|
287
|
+
-> tuple[list[WrappedType] | list[PyRecordModel], DataRecordPojoPageCriteria]:
|
|
259
288
|
"""
|
|
260
289
|
Shorthand for using the data record manager to query for all data records of a given type
|
|
261
290
|
and then converting the results into a list of record models.
|
|
262
291
|
|
|
263
|
-
:param wrapper_type: The record model wrapper to use.
|
|
292
|
+
:param wrapper_type: The record model wrapper to use, or the data type name of the records.
|
|
264
293
|
:param paging_criteria: The paging criteria to start the query with.
|
|
265
294
|
:param page_limit: The maximum number of pages to query from the starting criteria. If None, exhausts all
|
|
266
295
|
possible pages. This parameter only functions if you set a page size in the paging criteria or the platform
|
|
267
296
|
enforces a page size.
|
|
268
|
-
:return: The record models for the queried records and the final paging criteria.
|
|
297
|
+
:return: The record models for the queried records and the final paging criteria. If a data type name was used
|
|
298
|
+
instead of a model wrapper, then the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
269
299
|
"""
|
|
270
|
-
dt: str =
|
|
300
|
+
dt: str = AliasUtil.to_data_type_name(wrapper_type)
|
|
301
|
+
if isinstance(wrapper_type, str):
|
|
302
|
+
wrapper_type = None
|
|
271
303
|
pager = QueryAllRecordsOfTypeAutoPager(dt, self.user, paging_criteria)
|
|
272
304
|
pager.max_page = page_limit
|
|
273
305
|
return self.wrap_models(pager.get_all_at_once(), wrapper_type), pager.next_page_criteria
|
|
274
306
|
|
|
275
|
-
def query_models_by_report(self, wrapper_type: type[WrappedType],
|
|
307
|
+
def query_models_by_report(self, wrapper_type: type[WrappedType] | str,
|
|
276
308
|
report_name: str | RawReportTerm | CustomReportCriteria,
|
|
277
309
|
filters: dict[FieldIdentifierKey, Iterable[FieldValue]] | None = None,
|
|
278
310
|
page_limit: int | None = None,
|
|
279
311
|
page_size: int | None = None,
|
|
280
|
-
page_number: int | None = None) -> list[WrappedType]:
|
|
312
|
+
page_number: int | None = None) -> list[WrappedType] | list[PyRecordModel]:
|
|
281
313
|
"""
|
|
282
314
|
Run a report and use the results of that report to query for and return the records in the report results.
|
|
283
315
|
First runs the report, then runs a data record manager query on the results of the custom report.
|
|
@@ -287,7 +319,7 @@ class RecordHandler:
|
|
|
287
319
|
|
|
288
320
|
Any given custom report criteria should only have columns from a single data type.
|
|
289
321
|
|
|
290
|
-
:param wrapper_type: The record model wrapper to use.
|
|
322
|
+
:param wrapper_type: The record model wrapper to use, or the data type name of the records.
|
|
291
323
|
:param report_name: The name of a system report, or a raw report term for a quick report, or custom report
|
|
292
324
|
criteria for a custom report.
|
|
293
325
|
:param filters: If provided, filter the results of the report using the given mapping of headers to values to
|
|
@@ -299,7 +331,8 @@ class RecordHandler:
|
|
|
299
331
|
:param page_number: The page number to start the search from, If None, starts on the first page.
|
|
300
332
|
If the input report is a custom report criteria, uses the value from the criteria, unless this value is
|
|
301
333
|
not None, in which case it overwrites the given report's value. Note that the number of the first page is 0.
|
|
302
|
-
:return: The record models for the queried records that matched the given report.
|
|
334
|
+
:return: The record models for the queried records that matched the given report. If a data type name was used
|
|
335
|
+
instead of a model wrapper, then the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
303
336
|
"""
|
|
304
337
|
warnings.warn("Deprecated in favor of the [System/Custom/Quick]ReportRecordAutoPager classes.", DeprecationWarning)
|
|
305
338
|
if isinstance(report_name, str):
|
|
@@ -309,7 +342,7 @@ class RecordHandler:
|
|
|
309
342
|
results: list[dict[str, FieldValue]] = CustomReportUtil.run_quick_report(self.user, report_name, filters,
|
|
310
343
|
page_limit, page_size, page_number)
|
|
311
344
|
elif isinstance(report_name, CustomReportCriteria):
|
|
312
|
-
dt: str =
|
|
345
|
+
dt: str = AliasUtil.to_data_type_name(wrapper_type)
|
|
313
346
|
# Ensure that the root data type is the one we're looking for.
|
|
314
347
|
report_name.root_data_type = dt
|
|
315
348
|
# Raise an exception if any column in the report doesn't match the given data type.
|
|
@@ -329,35 +362,40 @@ class RecordHandler:
|
|
|
329
362
|
ids: list[int] = [row["RecordId"] for row in results]
|
|
330
363
|
return self.query_models_by_id(wrapper_type, ids)
|
|
331
364
|
|
|
332
|
-
def add_model(self, wrapper_type: type[WrappedType]) -> WrappedType:
|
|
365
|
+
def add_model(self, wrapper_type: type[WrappedType] | str) -> WrappedType | PyRecordModel:
|
|
333
366
|
"""
|
|
334
367
|
Shorthand for using the instance manager to add a new record model of the given type.
|
|
335
368
|
|
|
336
|
-
:param wrapper_type: The record model wrapper to use.
|
|
337
|
-
:return: The newly added record model.
|
|
369
|
+
:param wrapper_type: The record model wrapper to use, or the data type name of the record.
|
|
370
|
+
:return: The newly added record model. If a data type name was used instead of a model wrapper, then the
|
|
371
|
+
returned record will be a PyRecordModel instead of a WrappedRecordModel.
|
|
338
372
|
"""
|
|
339
|
-
return self.
|
|
373
|
+
return self.add_models(wrapper_type, 1)[0]
|
|
340
374
|
|
|
341
|
-
def add_models(self, wrapper_type: type[WrappedType], num: int) -> list[WrappedType]:
|
|
375
|
+
def add_models(self, wrapper_type: type[WrappedType] | str, num: int) -> list[WrappedType] | list[PyRecordModel]:
|
|
342
376
|
"""
|
|
343
377
|
Shorthand for using the instance manager to add new record models of the given type.
|
|
344
378
|
|
|
345
|
-
:param wrapper_type: The record model wrapper to use.
|
|
379
|
+
:param wrapper_type: The record model wrapper to use, or the data type name of the records.
|
|
346
380
|
:param num: The number of models to create.
|
|
347
|
-
:return: The newly added record models.
|
|
381
|
+
:return: The newly added record models. If a data type name was used instead of a model wrapper, then the
|
|
382
|
+
returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
348
383
|
"""
|
|
384
|
+
if isinstance(wrapper_type, str):
|
|
385
|
+
return self.inst_man.add_new_records(wrapper_type, num)
|
|
349
386
|
return self.inst_man.add_new_records_of_type(num, wrapper_type)
|
|
350
387
|
|
|
351
|
-
def add_models_with_data(self, wrapper_type: type[WrappedType], fields: list[FieldIdentifierMap]) \
|
|
352
|
-
-> list[WrappedType]:
|
|
388
|
+
def add_models_with_data(self, wrapper_type: type[WrappedType] | str, fields: list[FieldIdentifierMap]) \
|
|
389
|
+
-> list[WrappedType] | list[PyRecordModel]:
|
|
353
390
|
"""
|
|
354
391
|
Shorthand for using the instance manager to add new models of the given type, and then initializing all those
|
|
355
392
|
models with the given fields.
|
|
356
393
|
|
|
357
|
-
:param wrapper_type: The record model wrapper to use.
|
|
394
|
+
:param wrapper_type: The record model wrapper to use, or the data type name of the records.
|
|
358
395
|
:param fields: A list of field maps to initialize the record models with.
|
|
359
396
|
:return: The newly added record models with the provided fields set. The records will be in the same order as
|
|
360
|
-
the fields in the fields list.
|
|
397
|
+
the fields in the fields list. If a data type name was used instead of a model wrapper, then the returned
|
|
398
|
+
records will be PyRecordModels instead of WrappedRecordModels.
|
|
361
399
|
"""
|
|
362
400
|
fields: list[FieldMap] = AliasUtil.to_data_field_names_list_dict(fields)
|
|
363
401
|
models: list[WrappedType] = self.add_models(wrapper_type, len(fields))
|
|
@@ -365,8 +403,9 @@ class RecordHandler:
|
|
|
365
403
|
model.set_field_values(field_list)
|
|
366
404
|
return models
|
|
367
405
|
|
|
368
|
-
def find_or_add_model(self, wrapper_type: type[WrappedType], primary_identifier: FieldIdentifier,
|
|
369
|
-
id_value: FieldValue, secondary_identifiers: FieldIdentifierMap | None = None)
|
|
406
|
+
def find_or_add_model(self, wrapper_type: type[WrappedType] | str, primary_identifier: FieldIdentifier,
|
|
407
|
+
id_value: FieldValue, secondary_identifiers: FieldIdentifierMap | None = None) \
|
|
408
|
+
-> WrappedType | PyRecordModel:
|
|
370
409
|
"""
|
|
371
410
|
Find a unique record that matches the given field values. If no such records exist, add a record model to the
|
|
372
411
|
cache with the identifying fields set to the desired values. This record will be created in the system when
|
|
@@ -377,12 +416,14 @@ class RecordHandler:
|
|
|
377
416
|
|
|
378
417
|
Makes a webservice call to query for the existing record.
|
|
379
418
|
|
|
380
|
-
:param wrapper_type: The record model wrapper to use.
|
|
419
|
+
:param wrapper_type: The record model wrapper to use, or the data type name of the record.
|
|
381
420
|
:param primary_identifier: The data field name of the field to search on.
|
|
382
421
|
:param id_value: The value of the identifying field to search for.
|
|
383
422
|
:param secondary_identifiers: Optional fields used to filter the records that are returned after searching on
|
|
384
423
|
the primary identifier.
|
|
385
424
|
:return: The record model with the identifying field value, either pulled from the system or newly created.
|
|
425
|
+
If a data type name was used instead of a model wrapper, then the returned record will be a PyRecordModel
|
|
426
|
+
instead of a WrappedRecordModel.
|
|
386
427
|
"""
|
|
387
428
|
# PR-46335: Initialize the secondary identifiers parameter if None is provided to avoid an exception.
|
|
388
429
|
# If no secondary identifiers were provided, use an empty dictionary.
|
|
@@ -403,22 +444,25 @@ class RecordHandler:
|
|
|
403
444
|
secondary_identifiers.update({primary_identifier: id_value})
|
|
404
445
|
return self.add_models_with_data(wrapper_type, [secondary_identifiers])[0]
|
|
405
446
|
|
|
406
|
-
def create_models(self, wrapper_type: type[WrappedType], num: int) -> list[WrappedType]:
|
|
447
|
+
def create_models(self, wrapper_type: type[WrappedType] | str, num: int) -> list[WrappedType] | list[PyRecordModel]:
|
|
407
448
|
"""
|
|
408
449
|
Shorthand for creating new records via the data record manager and then returning them as wrapped
|
|
409
450
|
record models. Useful in cases where your record model needs to have a valid record ID.
|
|
410
451
|
|
|
411
452
|
Makes a webservice call to create the data records.
|
|
412
453
|
|
|
413
|
-
:param wrapper_type: The record model wrapper to use.
|
|
454
|
+
:param wrapper_type: The record model wrapper to use, or the data type name of the records.
|
|
414
455
|
:param num: The number of new records to create.
|
|
415
|
-
:return: The newly created record models.
|
|
456
|
+
:return: The newly created record models. If a data type name was used instead of a model wrapper, then the
|
|
457
|
+
returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
416
458
|
"""
|
|
417
|
-
dt: str =
|
|
459
|
+
dt: str = AliasUtil.to_data_type_name(wrapper_type)
|
|
460
|
+
if isinstance(wrapper_type, str):
|
|
461
|
+
wrapper_type = None
|
|
418
462
|
return self.wrap_models(self.dr_man.add_data_records(dt, num), wrapper_type)
|
|
419
463
|
|
|
420
|
-
def create_models_with_data(self, wrapper_type: type[WrappedType], fields: list[FieldIdentifierMap]) \
|
|
421
|
-
-> list[WrappedType]:
|
|
464
|
+
def create_models_with_data(self, wrapper_type: type[WrappedType] | str, fields: list[FieldIdentifierMap]) \
|
|
465
|
+
-> list[WrappedType] | list[PyRecordModel]:
|
|
422
466
|
"""
|
|
423
467
|
Shorthand for creating new records via the data record manager with field data to initialize the records with
|
|
424
468
|
and then returning them as wrapped record models. Useful in cases where your record model needs to have a valid
|
|
@@ -426,17 +470,20 @@ class RecordHandler:
|
|
|
426
470
|
|
|
427
471
|
Makes a webservice call to create the data records.
|
|
428
472
|
|
|
429
|
-
:param wrapper_type: The record model wrapper to use.
|
|
473
|
+
:param wrapper_type: The record model wrapper to use, or the data type name of the records.
|
|
430
474
|
:param fields: The field map list to initialize the new data records with.
|
|
431
|
-
:return: The newly created record models.
|
|
475
|
+
:return: The newly created record models. If a data type name was used instead of a model wrapper, then the
|
|
476
|
+
returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
432
477
|
"""
|
|
433
|
-
dt: str =
|
|
478
|
+
dt: str = AliasUtil.to_data_type_name(wrapper_type)
|
|
479
|
+
if isinstance(wrapper_type, str):
|
|
480
|
+
wrapper_type = None
|
|
434
481
|
fields: list[FieldMap] = AliasUtil.to_data_field_names_list_dict(fields)
|
|
435
482
|
return self.wrap_models(self.dr_man.add_data_records_with_data(dt, fields), wrapper_type)
|
|
436
483
|
|
|
437
|
-
def find_or_create_model(self, wrapper_type: type[WrappedType], primary_identifier: FieldIdentifier,
|
|
484
|
+
def find_or_create_model(self, wrapper_type: type[WrappedType] | str, primary_identifier: FieldIdentifier,
|
|
438
485
|
id_value: FieldValue, secondary_identifiers: FieldIdentifierMap | None = None) \
|
|
439
|
-
-> WrappedType:
|
|
486
|
+
-> WrappedType | PyRecordModel:
|
|
440
487
|
"""
|
|
441
488
|
Find a unique record that matches the given field values. If no such records exist, create one with the
|
|
442
489
|
identifying fields set to the desired values. If more than one record with the identifying values exists,
|
|
@@ -448,12 +495,14 @@ class RecordHandler:
|
|
|
448
495
|
Makes a webservice call to query for the existing record. Makes an additional webservice call if the record
|
|
449
496
|
needs to be created.
|
|
450
497
|
|
|
451
|
-
:param wrapper_type: The record model wrapper to use.
|
|
498
|
+
:param wrapper_type: The record model wrapper to use, or the data type name of the record.
|
|
452
499
|
:param primary_identifier: The data field name of the field to search on.
|
|
453
500
|
:param id_value: The value of the identifying field to search for.
|
|
454
501
|
:param secondary_identifiers: Optional fields used to filter the records that are returned after searching on
|
|
455
502
|
the primary identifier.
|
|
456
503
|
:return: The record model with the identifying field value, either pulled from the system or newly created.
|
|
504
|
+
If a data type name was used instead of a model wrapper, then the returned record will be a PyRecordModel
|
|
505
|
+
instead of a WrappedRecordModel.
|
|
457
506
|
"""
|
|
458
507
|
# PR-46335: Initialize the secondary identifiers parameter if None is provided to avoid an exception.
|
|
459
508
|
# If no secondary identifiers were provided, use an empty dictionary.
|
|
@@ -475,7 +524,8 @@ class RecordHandler:
|
|
|
475
524
|
return self.create_models_with_data(wrapper_type, [secondary_identifiers])[0]
|
|
476
525
|
|
|
477
526
|
@staticmethod
|
|
478
|
-
def map_to_parent(models: Iterable[
|
|
527
|
+
def map_to_parent(models: Iterable[WrappedRecordModel], parent_type: type[WrappedType])\
|
|
528
|
+
-> dict[WrappedRecordModel, WrappedType]:
|
|
479
529
|
"""
|
|
480
530
|
Map a list of record models to a single parent of a given type. The parents must already be loaded.
|
|
481
531
|
|
|
@@ -484,14 +534,14 @@ class RecordHandler:
|
|
|
484
534
|
:return: A dict[ModelType, ParentType]. If an input model doesn't have a parent of the given parent type, then
|
|
485
535
|
it will map to None.
|
|
486
536
|
"""
|
|
487
|
-
return_dict: dict[
|
|
537
|
+
return_dict: dict[WrappedRecordModel, WrappedType] = {}
|
|
488
538
|
for model in models:
|
|
489
539
|
return_dict[model] = model.get_parent_of_type(parent_type)
|
|
490
540
|
return return_dict
|
|
491
541
|
|
|
492
542
|
@staticmethod
|
|
493
|
-
def map_to_parents(models: Iterable[
|
|
494
|
-
-> dict[
|
|
543
|
+
def map_to_parents(models: Iterable[WrappedRecordModel], parent_type: type[WrappedType]) \
|
|
544
|
+
-> dict[WrappedRecordModel, list[WrappedType]]:
|
|
495
545
|
"""
|
|
496
546
|
Map a list of record models to a list parents of a given type. The parents must already be loaded.
|
|
497
547
|
|
|
@@ -500,14 +550,14 @@ class RecordHandler:
|
|
|
500
550
|
:return: A dict[ModelType, list[ParentType]]. If an input model doesn't have a parent of the given parent type,
|
|
501
551
|
then it will map to an empty list.
|
|
502
552
|
"""
|
|
503
|
-
return_dict: dict[
|
|
553
|
+
return_dict: dict[WrappedRecordModel, list[WrappedType]] = {}
|
|
504
554
|
for model in models:
|
|
505
555
|
return_dict[model] = model.get_parents_of_type(parent_type)
|
|
506
556
|
return return_dict
|
|
507
557
|
|
|
508
558
|
@staticmethod
|
|
509
|
-
def map_by_parent(models: Iterable[
|
|
510
|
-
-> dict[WrappedType,
|
|
559
|
+
def map_by_parent(models: Iterable[WrappedRecordModel], parent_type: type[WrappedType]) \
|
|
560
|
+
-> dict[WrappedType, WrappedRecordModel]:
|
|
511
561
|
"""
|
|
512
562
|
Take a list of record models and map them by their parent. Essentially an inversion of map_to_parent.
|
|
513
563
|
If two records share the same parent, an exception will be thrown. The parents must already be loaded.
|
|
@@ -517,8 +567,8 @@ class RecordHandler:
|
|
|
517
567
|
:return: A dict[ParentType, ModelType]. If an input model doesn't have a parent of the given parent type,
|
|
518
568
|
then it will not be in the resulting dictionary.
|
|
519
569
|
"""
|
|
520
|
-
to_parent: dict[
|
|
521
|
-
by_parent: dict[WrappedType,
|
|
570
|
+
to_parent: dict[WrappedRecordModel, WrappedType] = RecordHandler.map_to_parent(models, parent_type)
|
|
571
|
+
by_parent: dict[WrappedType, WrappedRecordModel] = {}
|
|
522
572
|
for record, parent in to_parent.items():
|
|
523
573
|
if parent is None:
|
|
524
574
|
continue
|
|
@@ -529,8 +579,8 @@ class RecordHandler:
|
|
|
529
579
|
return by_parent
|
|
530
580
|
|
|
531
581
|
@staticmethod
|
|
532
|
-
def map_by_parents(models: Iterable[
|
|
533
|
-
-> dict[WrappedType, list[
|
|
582
|
+
def map_by_parents(models: Iterable[WrappedRecordModel], parent_type: type[WrappedType]) \
|
|
583
|
+
-> dict[WrappedType, list[WrappedRecordModel]]:
|
|
534
584
|
"""
|
|
535
585
|
Take a list of record models and map them by their parents. Essentially an inversion of map_to_parents. Input
|
|
536
586
|
models that share a parent will end up in the same list. The parents must already be loaded.
|
|
@@ -540,15 +590,16 @@ class RecordHandler:
|
|
|
540
590
|
:return: A dict[ParentType, list[ModelType]]. If an input model doesn't have a parent of the given parent type,
|
|
541
591
|
then it will not be in the resulting dictionary.
|
|
542
592
|
"""
|
|
543
|
-
to_parents: dict[
|
|
544
|
-
by_parents: dict[WrappedType, list[
|
|
593
|
+
to_parents: dict[WrappedRecordModel, list[WrappedType]] = RecordHandler.map_to_parents(models, parent_type)
|
|
594
|
+
by_parents: dict[WrappedType, list[WrappedRecordModel]] = {}
|
|
545
595
|
for record, parents in to_parents.items():
|
|
546
596
|
for parent in parents:
|
|
547
597
|
by_parents.setdefault(parent, []).append(record)
|
|
548
598
|
return by_parents
|
|
549
599
|
|
|
550
600
|
@staticmethod
|
|
551
|
-
def map_to_child(models: Iterable[
|
|
601
|
+
def map_to_child(models: Iterable[WrappedRecordModel], child_type: type[WrappedType])\
|
|
602
|
+
-> dict[WrappedRecordModel, WrappedType]:
|
|
552
603
|
"""
|
|
553
604
|
Map a list of record models to a single child of a given type. The children must already be loaded.
|
|
554
605
|
|
|
@@ -557,14 +608,14 @@ class RecordHandler:
|
|
|
557
608
|
:return: A dict[ModelType, ChildType]. If an input model doesn't have a child of the given child type, then
|
|
558
609
|
it will map to None.
|
|
559
610
|
"""
|
|
560
|
-
return_dict: dict[
|
|
611
|
+
return_dict: dict[WrappedRecordModel, WrappedType] = {}
|
|
561
612
|
for model in models:
|
|
562
613
|
return_dict[model] = model.get_child_of_type(child_type)
|
|
563
614
|
return return_dict
|
|
564
615
|
|
|
565
616
|
@staticmethod
|
|
566
|
-
def map_to_children(models: Iterable[
|
|
567
|
-
-> dict[
|
|
617
|
+
def map_to_children(models: Iterable[WrappedRecordModel], child_type: type[WrappedType]) \
|
|
618
|
+
-> dict[WrappedRecordModel, list[WrappedType]]:
|
|
568
619
|
"""
|
|
569
620
|
Map a list of record models to a list children of a given type. The children must already be loaded.
|
|
570
621
|
|
|
@@ -573,14 +624,14 @@ class RecordHandler:
|
|
|
573
624
|
:return: A dict[ModelType, list[ChildType]]. If an input model doesn't have children of the given child type,
|
|
574
625
|
then it will map to an empty list.
|
|
575
626
|
"""
|
|
576
|
-
return_dict: dict[
|
|
627
|
+
return_dict: dict[WrappedRecordModel, list[WrappedType]] = {}
|
|
577
628
|
for model in models:
|
|
578
629
|
return_dict[model] = model.get_children_of_type(child_type)
|
|
579
630
|
return return_dict
|
|
580
631
|
|
|
581
632
|
@staticmethod
|
|
582
|
-
def map_by_child(models: Iterable[
|
|
583
|
-
-> dict[WrappedType,
|
|
633
|
+
def map_by_child(models: Iterable[WrappedRecordModel], child_type: type[WrappedType]) \
|
|
634
|
+
-> dict[WrappedType, WrappedRecordModel]:
|
|
584
635
|
"""
|
|
585
636
|
Take a list of record models and map them by their children. Essentially an inversion of map_to_child.
|
|
586
637
|
If two records share the same child, an exception will be thrown. The children must already be loaded.
|
|
@@ -590,8 +641,8 @@ class RecordHandler:
|
|
|
590
641
|
:return: A dict[ChildType, ModelType]. If an input model doesn't have a child of the given child type,
|
|
591
642
|
then it will not be in the resulting dictionary.
|
|
592
643
|
"""
|
|
593
|
-
to_child: dict[
|
|
594
|
-
by_child: dict[WrappedType,
|
|
644
|
+
to_child: dict[WrappedRecordModel, WrappedType] = RecordHandler.map_to_child(models, child_type)
|
|
645
|
+
by_child: dict[WrappedType, WrappedRecordModel] = {}
|
|
595
646
|
for record, child in to_child.items():
|
|
596
647
|
if child is None:
|
|
597
648
|
continue
|
|
@@ -602,8 +653,8 @@ class RecordHandler:
|
|
|
602
653
|
return by_child
|
|
603
654
|
|
|
604
655
|
@staticmethod
|
|
605
|
-
def map_by_children(models: Iterable[
|
|
606
|
-
-> dict[WrappedType, list[
|
|
656
|
+
def map_by_children(models: Iterable[WrappedRecordModel], child_type: type[WrappedType]) \
|
|
657
|
+
-> dict[WrappedType, list[WrappedRecordModel]]:
|
|
607
658
|
"""
|
|
608
659
|
Take a list of record models and map them by their children. Essentially an inversion of map_to_children. Input
|
|
609
660
|
models that share a child will end up in the same list. The children must already be loaded.
|
|
@@ -613,8 +664,8 @@ class RecordHandler:
|
|
|
613
664
|
:return: A dict[ChildType, list[ModelType]]. If an input model doesn't have children of the given child type,
|
|
614
665
|
then it will not be in the resulting dictionary.
|
|
615
666
|
"""
|
|
616
|
-
to_children: dict[
|
|
617
|
-
by_children: dict[WrappedType, list[
|
|
667
|
+
to_children: dict[WrappedRecordModel, list[WrappedType]] = RecordHandler.map_to_children(models, child_type)
|
|
668
|
+
by_children: dict[WrappedType, list[WrappedRecordModel]] = {}
|
|
618
669
|
for record, children in to_children.items():
|
|
619
670
|
for child in children:
|
|
620
671
|
by_children.setdefault(child, []).append(record)
|
|
@@ -921,8 +972,9 @@ class RecordHandler:
|
|
|
921
972
|
|
|
922
973
|
# FR-46155: Update relationship path traversing functions to be non-static and take in a wrapper type so that the
|
|
923
974
|
# output can be wrapped instead of requiring the user to wrap the output.
|
|
924
|
-
def get_linear_path(self, models: Iterable[RecordModel], path: RelationshipPath,
|
|
925
|
-
|
|
975
|
+
def get_linear_path(self, models: Iterable[RecordModel], path: RelationshipPath,
|
|
976
|
+
wrapper_type: type[WrappedType] | None = None) \
|
|
977
|
+
-> dict[RecordModel, WrappedType | PyRecordModel | None]:
|
|
926
978
|
"""
|
|
927
979
|
Given a relationship path, travel the path starting from the input models. Returns the record at the end of the
|
|
928
980
|
path, if any. The hierarchy must be linear (1:1 relationship between data types at every step) and the
|
|
@@ -930,7 +982,8 @@ class RecordHandler:
|
|
|
930
982
|
|
|
931
983
|
:param models: A list of record models.
|
|
932
984
|
:param path: The relationship path to follow.
|
|
933
|
-
:param wrapper_type: The record model wrapper to use.
|
|
985
|
+
:param wrapper_type: The record model wrapper to use on the record at the end of the path. If not provided,
|
|
986
|
+
the record will be a PyRecordModel instead of a WrappedRecordModel.
|
|
934
987
|
:return: Each record model mapped to the record at the end of the path starting from itself. If the end of the
|
|
935
988
|
path couldn't be reached, the record will map to None.
|
|
936
989
|
"""
|
|
@@ -978,11 +1031,12 @@ class RecordHandler:
|
|
|
978
1031
|
current = reverse_links[0]
|
|
979
1032
|
else:
|
|
980
1033
|
raise SapioException("Unsupported path direction.")
|
|
981
|
-
ret_dict.update({model: self.
|
|
1034
|
+
ret_dict.update({model: self.wrap_model(current, wrapper_type) if current else None})
|
|
982
1035
|
return ret_dict
|
|
983
1036
|
|
|
984
1037
|
def get_branching_path(self, models: Iterable[RecordModel], path: RelationshipPath,
|
|
985
|
-
wrapper_type: type[WrappedType]
|
|
1038
|
+
wrapper_type: type[WrappedType] | None = None)\
|
|
1039
|
+
-> dict[RecordModel, list[WrappedType] | list[PyRecordModel]]:
|
|
986
1040
|
"""
|
|
987
1041
|
Given a relationship path, travel the path starting from the input models. Returns the record at the end of the
|
|
988
1042
|
path, if any. The hierarchy may be non-linear (1:Many relationships between data types are allowed) and the
|
|
@@ -990,7 +1044,8 @@ class RecordHandler:
|
|
|
990
1044
|
|
|
991
1045
|
:param models: A list of record models.
|
|
992
1046
|
:param path: The relationship path to follow.
|
|
993
|
-
:param wrapper_type: The record model wrapper to use.
|
|
1047
|
+
:param wrapper_type: The record model wrapper to use on the records at the end of the path. If not provided,
|
|
1048
|
+
the records will be PyRecordModels instead of WrappedRecordModels.
|
|
994
1049
|
:return: Each record model mapped to the records at the end of the path starting from itself. If the end of the
|
|
995
1050
|
path couldn't be reached, the record will map to an empty list.
|
|
996
1051
|
"""
|
|
@@ -1023,13 +1078,14 @@ class RecordHandler:
|
|
|
1023
1078
|
raise SapioException("Unsupported path direction.")
|
|
1024
1079
|
current_search = next_search
|
|
1025
1080
|
next_search = set()
|
|
1026
|
-
ret_dict.update({model: self.
|
|
1081
|
+
ret_dict.update({model: self.wrap_models(current_search, wrapper_type)})
|
|
1027
1082
|
return ret_dict
|
|
1028
1083
|
|
|
1029
1084
|
# FR-46155: Create a relationship traversing function that returns a single function at the end of the path like
|
|
1030
1085
|
# get_linear_path but can handle branching paths in the middle of the search like get_branching_path.
|
|
1031
|
-
def get_flat_path(self, models: Iterable[RecordModel], path: RelationshipPath,
|
|
1032
|
-
|
|
1086
|
+
def get_flat_path(self, models: Iterable[RecordModel], path: RelationshipPath,
|
|
1087
|
+
wrapper_type: type[WrappedType] | None = None) \
|
|
1088
|
+
-> dict[RecordModel, WrappedType | PyRecordModel | None]:
|
|
1033
1089
|
"""
|
|
1034
1090
|
Given a relationship path, travel the path starting from the input models. Returns the record at the end of the
|
|
1035
1091
|
path, if any. The hierarchy may be non-linear (1:Many relationships between data types are allowed) and the
|
|
@@ -1041,7 +1097,8 @@ class RecordHandler:
|
|
|
1041
1097
|
|
|
1042
1098
|
:param models: A list of record models.
|
|
1043
1099
|
:param path: The relationship path to follow.
|
|
1044
|
-
:param wrapper_type: The record model wrapper to use.
|
|
1100
|
+
:param wrapper_type: The record model wrapper to use on the record at the end of the path. If not provided,
|
|
1101
|
+
the record will be a PyRecordModel instead of a WrappedRecordModel.
|
|
1045
1102
|
:return: Each record model mapped to the record at the end of the path starting from itself. If the end of the
|
|
1046
1103
|
path couldn't be reached, the record will map to None.
|
|
1047
1104
|
"""
|
|
@@ -1069,21 +1126,22 @@ class RecordHandler:
|
|
|
1069
1126
|
current = current[0].get_reverse_side_link(data_type, node.data_field_name)
|
|
1070
1127
|
else:
|
|
1071
1128
|
raise SapioException("Unsupported path direction.")
|
|
1072
|
-
ret_dict.update({model: self.
|
|
1129
|
+
ret_dict.update({model: self.wrap_model(current[0], wrapper_type) if current else None})
|
|
1073
1130
|
return ret_dict
|
|
1074
1131
|
|
|
1075
|
-
def __find_model(self, wrapper_type: type[WrappedType], primary_identifier: str, id_value: FieldValue,
|
|
1076
|
-
secondary_identifiers: FieldIdentifierMap | None = None) -> WrappedType | None:
|
|
1132
|
+
def __find_model(self, wrapper_type: type[WrappedType] | str, primary_identifier: str, id_value: FieldValue,
|
|
1133
|
+
secondary_identifiers: FieldIdentifierMap | None = None) -> WrappedType | PyRecordModel | None:
|
|
1077
1134
|
"""
|
|
1078
1135
|
Find a record from the system that matches the given field values. The primary identifier and value is used
|
|
1079
1136
|
to query for the record, then the secondary identifiers may be optionally provided to further filter the
|
|
1080
1137
|
returned results. If no record is found with these filters, returns None.
|
|
1081
1138
|
"""
|
|
1082
1139
|
# Query for all records that match the primary identifier.
|
|
1083
|
-
results: list[WrappedType] = self.query_models(wrapper_type, primary_identifier,
|
|
1140
|
+
results: list[WrappedType] | list[PyRecordModel] = self.query_models(wrapper_type, primary_identifier,
|
|
1141
|
+
[id_value])
|
|
1084
1142
|
|
|
1085
1143
|
# Find the one record, if any, that matches the secondary identifiers.
|
|
1086
|
-
unique_record: WrappedType | None = None
|
|
1144
|
+
unique_record: WrappedType | PyRecordModel | None = None
|
|
1087
1145
|
for result in results:
|
|
1088
1146
|
matches_all: bool = True
|
|
1089
1147
|
for field, value in secondary_identifiers.items():
|
|
@@ -1093,22 +1151,21 @@ class RecordHandler:
|
|
|
1093
1151
|
if matches_all:
|
|
1094
1152
|
# If a previous record in the results already matched all identifiers, then throw an exception.
|
|
1095
1153
|
if unique_record is not None:
|
|
1096
|
-
raise SapioException(f"More than one record of type {
|
|
1154
|
+
raise SapioException(f"More than one record of type {AliasUtil.to_data_type_name(wrapper_type)} "
|
|
1097
1155
|
f"encountered in system that matches all provided identifiers.")
|
|
1098
1156
|
unique_record = result
|
|
1099
1157
|
return unique_record
|
|
1100
1158
|
|
|
1101
1159
|
@staticmethod
|
|
1102
|
-
def __verify_data_type(
|
|
1160
|
+
def __verify_data_type(record: DataRecord | PyRecordModel, wrapper_type: type[WrappedType]) -> None:
|
|
1103
1161
|
"""
|
|
1104
|
-
Throw an exception if the data type of the given
|
|
1162
|
+
Throw an exception if the data type of the given record and wrapper don't match.
|
|
1105
1163
|
"""
|
|
1106
1164
|
model_type: str = wrapper_type.get_wrapper_data_type_name()
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
f"of type {model_type}")
|
|
1165
|
+
record_type: str = AliasUtil.to_data_type_name(record)
|
|
1166
|
+
# Account for ELN data type records.
|
|
1167
|
+
if ElnBaseDataType.is_eln_type(record_type):
|
|
1168
|
+
record_type = ElnBaseDataType.get_base_type(record_type).data_type_name
|
|
1169
|
+
if record_type != model_type:
|
|
1170
|
+
raise SapioException(f"Data record of type {record_type} cannot be wrapped by the record model wrapper "
|
|
1171
|
+
f"of type {model_type}")
|