sapiopycommons 2025.4.8a474__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.8a474.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.8a474.dist-info/RECORD +0 -67
- {sapiopycommons-2025.4.8a474.dist-info → sapiopycommons-2025.4.9a150.dist-info}/WHEEL +0 -0
- {sapiopycommons-2025.4.8a474.dist-info → sapiopycommons-2025.4.9a150.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import io
|
|
4
|
-
import warnings
|
|
5
3
|
from collections.abc import Iterable
|
|
6
|
-
from typing import Collection
|
|
7
4
|
from weakref import WeakValueDictionary
|
|
8
5
|
|
|
9
6
|
from sapiopylib.rest.DataRecordManagerService import DataRecordManager
|
|
@@ -15,28 +12,19 @@ from sapiopylib.rest.pojo.datatype.FieldDefinition import FieldType
|
|
|
15
12
|
from sapiopylib.rest.pojo.eln.SapioELNEnums import ElnBaseDataType
|
|
16
13
|
from sapiopylib.rest.utils.autopaging import QueryDataRecordsAutoPager, QueryDataRecordByIdListAutoPager, \
|
|
17
14
|
QueryAllRecordsOfTypeAutoPager
|
|
18
|
-
from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
|
|
19
|
-
RecordModelPropertyType, AbstractRecordModelPropertyAdder, AbstractRecordModelPropertySetter, \
|
|
20
|
-
AbstractRecordModelPropertyRemover
|
|
15
|
+
from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
|
|
21
16
|
from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelManager, RecordModelInstanceManager, \
|
|
22
17
|
RecordModelRelationshipManager
|
|
23
18
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType, WrappedRecordModel
|
|
24
19
|
from sapiopylib.rest.utils.recordmodel.RelationshipPath import RelationshipPath, RelationshipNode, \
|
|
25
20
|
RelationshipNodeType
|
|
26
21
|
from sapiopylib.rest.utils.recordmodel.ancestry import RecordModelAncestorManager
|
|
27
|
-
from sapiopylib.rest.utils.recordmodel.properties import Parents, Parent, Children, Child, ForwardSideLink
|
|
28
22
|
|
|
29
23
|
from sapiopycommons.general.aliases import RecordModel, SapioRecord, FieldMap, FieldIdentifier, AliasUtil, \
|
|
30
|
-
FieldIdentifierMap, FieldValue, UserIdentifier, FieldIdentifierKey
|
|
24
|
+
FieldIdentifierMap, FieldValue, UserIdentifier, FieldIdentifierKey
|
|
31
25
|
from sapiopycommons.general.custom_report_util import CustomReportUtil
|
|
32
26
|
from sapiopycommons.general.exceptions import SapioException
|
|
33
27
|
|
|
34
|
-
# Aliases for longer name.
|
|
35
|
-
_PropertyGetter = AbstractRecordModelPropertyGetter
|
|
36
|
-
_PropertyAdder = AbstractRecordModelPropertyAdder
|
|
37
|
-
_PropertyRemover = AbstractRecordModelPropertyRemover
|
|
38
|
-
_PropertySetter = AbstractRecordModelPropertySetter
|
|
39
|
-
_PropertyType = RecordModelPropertyType
|
|
40
28
|
|
|
41
29
|
# FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
|
|
42
30
|
class RecordHandler:
|
|
@@ -69,273 +57,226 @@ class RecordHandler:
|
|
|
69
57
|
"""
|
|
70
58
|
:param context: The current webhook context or a user object to send requests from.
|
|
71
59
|
"""
|
|
60
|
+
self.user = AliasUtil.to_sapio_user(context)
|
|
72
61
|
if self.__initialized:
|
|
73
62
|
return
|
|
74
63
|
self.__initialized = True
|
|
75
64
|
|
|
76
|
-
self.user =
|
|
65
|
+
self.user = context if isinstance(context, SapioUser) else context.user
|
|
77
66
|
self.dr_man = DataRecordManager(self.user)
|
|
78
67
|
self.rec_man = RecordModelManager(self.user)
|
|
79
68
|
self.inst_man = self.rec_man.instance_manager
|
|
80
69
|
self.rel_man = self.rec_man.relationship_manager
|
|
81
70
|
self.an_man = RecordModelAncestorManager(self.rec_man)
|
|
82
71
|
|
|
83
|
-
|
|
84
|
-
def wrap_model(self, record: DataRecord | PyRecordModel, wrapper_type: type[WrappedType] | None = None) \
|
|
85
|
-
-> WrappedType | PyRecordModel:
|
|
72
|
+
def wrap_model(self, record: DataRecord, wrapper_type: type[WrappedType]) -> WrappedType:
|
|
86
73
|
"""
|
|
87
|
-
Shorthand for adding a single data record
|
|
74
|
+
Shorthand for adding a single data record as a record model.
|
|
88
75
|
|
|
89
|
-
:param record: The data record
|
|
90
|
-
:param wrapper_type: The record model wrapper to use.
|
|
91
|
-
PyRecordModel instead of a WrappedRecordModel.
|
|
76
|
+
:param record: The data record to wrap.
|
|
77
|
+
:param wrapper_type: The record model wrapper to use.
|
|
92
78
|
:return: The record model for the input.
|
|
93
79
|
"""
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if isinstance(record, PyRecordModel):
|
|
97
|
-
return self.inst_man.wrap(record, wrapper_type)
|
|
98
|
-
return self.inst_man.add_existing_record_of_type(record, wrapper_type)
|
|
99
|
-
if isinstance(record, PyRecordModel):
|
|
100
|
-
return record
|
|
101
|
-
return self.inst_man.add_existing_record(record)
|
|
80
|
+
self.__verify_data_type([record], wrapper_type)
|
|
81
|
+
return self.inst_man.add_existing_record_of_type(record, wrapper_type)
|
|
102
82
|
|
|
103
|
-
def wrap_models(self, records: Iterable[DataRecord
|
|
104
|
-
wrapper_type: type[WrappedType] | None = None) \
|
|
105
|
-
-> list[WrappedType] | list[PyRecordModel]:
|
|
83
|
+
def wrap_models(self, records: Iterable[DataRecord], wrapper_type: type[WrappedType]) -> list[WrappedType]:
|
|
106
84
|
"""
|
|
107
|
-
Shorthand for adding a list of data records
|
|
85
|
+
Shorthand for adding a list of data records as record models.
|
|
108
86
|
|
|
109
87
|
:param records: The data records to wrap.
|
|
110
|
-
:param wrapper_type: The record model wrapper to use.
|
|
111
|
-
PyRecordModels instead of WrappedRecordModels.
|
|
88
|
+
:param wrapper_type: The record model wrapper to use.
|
|
112
89
|
:return: The record models for the input.
|
|
113
90
|
"""
|
|
114
|
-
|
|
91
|
+
self.__verify_data_type(records, wrapper_type)
|
|
92
|
+
return self.inst_man.add_existing_records_of_type(list(records), wrapper_type)
|
|
115
93
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def query_models(self, wrapper_type: type[WrappedType] | str, field: FieldIdentifier,
|
|
119
|
-
value_list: Iterable[FieldValue] | FieldValue,
|
|
120
|
-
page_limit: int | None = None, page_size: int | None = None) \
|
|
121
|
-
-> list[WrappedType] | list[PyRecordModel]:
|
|
94
|
+
def query_models(self, wrapper_type: type[WrappedType], field: FieldIdentifier, value_list: Iterable[FieldValue],
|
|
95
|
+
page_limit: int | None = None, page_size: int | None = None) -> list[WrappedType]:
|
|
122
96
|
"""
|
|
123
97
|
Shorthand for using the data record manager to query for a list of data records by field value
|
|
124
98
|
and then converting the results into a list of record models.
|
|
125
99
|
|
|
126
|
-
:param wrapper_type: The record model wrapper to use
|
|
100
|
+
:param wrapper_type: The record model wrapper to use.
|
|
127
101
|
:param field: The field to query on.
|
|
128
|
-
:param value_list: The values of the field to query on
|
|
129
|
-
converted to a singleton list. Note that field values of None are not supported by this method and will be
|
|
130
|
-
ignored. If you need to query for records with a null field value, use a custom report.
|
|
102
|
+
:param value_list: The values of the field to query on.
|
|
131
103
|
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages. This parameter
|
|
132
104
|
only functions if you set a page size or the platform enforces a page size.
|
|
133
105
|
:param page_size: The size of the pages to query. If None, the page size may be limited by the platform.
|
|
134
|
-
:return: The record models for the queried records.
|
|
135
|
-
then the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
106
|
+
:return: The record models for the queried records.
|
|
136
107
|
"""
|
|
137
108
|
criteria: DataRecordPojoPageCriteria | None = None
|
|
138
109
|
if page_size is not None:
|
|
139
110
|
criteria = DataRecordPojoPageCriteria(page_size=page_size)
|
|
140
111
|
return self.query_models_with_criteria(wrapper_type, field, value_list, criteria, page_limit)[0]
|
|
141
112
|
|
|
142
|
-
def query_and_map_models(self, wrapper_type: type[WrappedType]
|
|
143
|
-
value_list: Iterable[FieldValue] |
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
mapping_field: FieldIdentifier | None = None) \
|
|
147
|
-
-> dict[FieldValue, list[WrappedType] | list[PyRecordModel]]:
|
|
113
|
+
def query_and_map_models(self, wrapper_type: type[WrappedType], field: FieldIdentifier,
|
|
114
|
+
value_list: Iterable[FieldValue], page_limit: int | None = None,
|
|
115
|
+
page_size: int | None = None, *, mapping_field: FieldIdentifier | None = None) \
|
|
116
|
+
-> dict[FieldValue, list[WrappedType]]:
|
|
148
117
|
"""
|
|
149
118
|
Shorthand for using query_models to search for records given values on a specific field and then using
|
|
150
119
|
map_by_field to turn the returned list into a dictionary mapping field values to records.
|
|
151
120
|
|
|
152
|
-
:param wrapper_type: The record model wrapper to use
|
|
121
|
+
:param wrapper_type: The record model wrapper to use.
|
|
153
122
|
:param field: The field to query and map on.
|
|
154
|
-
:param value_list: The values of the field to query on
|
|
155
|
-
converted to a singleton list. Note that field values of None are not supported by this method and will be
|
|
156
|
-
ignored. If you need to query for records with a null field value, use a custom report.
|
|
123
|
+
:param value_list: The values of the field to query on.
|
|
157
124
|
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages. This parameter
|
|
158
125
|
only functions if you set a page size or the platform enforces a page size.
|
|
159
126
|
:param page_size: The size of the pages to query. If None, the page size may be limited by the platform.
|
|
160
127
|
:param mapping_field: If provided, use this field to map against instead of the field that was queried on.
|
|
161
128
|
:return: The record models for the queried records mapped by field values to the records with that value.
|
|
162
|
-
If a data type name was used instead of a model wrapper, then the returned records will be PyRecordModels
|
|
163
|
-
instead of WrappedRecordModels.
|
|
164
129
|
"""
|
|
165
130
|
if mapping_field is None:
|
|
166
131
|
mapping_field = field
|
|
167
132
|
return self.map_by_field(self.query_models(wrapper_type, field, value_list, page_limit, page_size),
|
|
168
133
|
mapping_field)
|
|
169
134
|
|
|
170
|
-
def query_and_unique_map_models(self, wrapper_type: type[WrappedType]
|
|
171
|
-
value_list: Iterable[FieldValue] |
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
mapping_field: FieldIdentifier | None = None) \
|
|
175
|
-
-> dict[FieldValue, WrappedType | PyRecordModel]:
|
|
135
|
+
def query_and_unique_map_models(self, wrapper_type: type[WrappedType], field: FieldIdentifier,
|
|
136
|
+
value_list: Iterable[FieldValue], page_limit: int | None = None,
|
|
137
|
+
page_size: int | None = None, *, mapping_field: FieldIdentifier | None = None) \
|
|
138
|
+
-> dict[FieldValue, WrappedType]:
|
|
176
139
|
"""
|
|
177
140
|
Shorthand for using query_models to search for records given values on a specific field and then using
|
|
178
141
|
map_by_unique_field to turn the returned list into a dictionary mapping field values to records.
|
|
179
142
|
If any two records share the same field value, throws an exception.
|
|
180
143
|
|
|
181
|
-
:param wrapper_type: The record model wrapper to use
|
|
144
|
+
:param wrapper_type: The record model wrapper to use.
|
|
182
145
|
:param field: The field to query and map on.
|
|
183
|
-
:param value_list: The values of the field to query on
|
|
184
|
-
converted to a singleton list. Note that field values of None are not supported by this method and will be
|
|
185
|
-
ignored. If you need to query for records with a null field value, use a custom report.
|
|
146
|
+
:param value_list: The values of the field to query on.
|
|
186
147
|
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages. This parameter
|
|
187
148
|
only functions if you set a page size or the platform enforces a page size.
|
|
188
149
|
:param page_size: The size of the pages to query. If None, the page size may be limited by the platform.
|
|
189
150
|
:param mapping_field: If provided, use this field to map against instead of the field that was queried on.
|
|
190
151
|
:return: The record models for the queried records mapped by field values to the record with that value.
|
|
191
|
-
If a data type name was used instead of a model wrapper, then the returned records will be PyRecordModels
|
|
192
|
-
instead of WrappedRecordModels.
|
|
193
152
|
"""
|
|
194
153
|
if mapping_field is None:
|
|
195
154
|
mapping_field = field
|
|
196
155
|
return self.map_by_unique_field(self.query_models(wrapper_type, field, value_list, page_limit, page_size),
|
|
197
156
|
mapping_field)
|
|
198
157
|
|
|
199
|
-
def query_models_with_criteria(self, wrapper_type: type[WrappedType]
|
|
200
|
-
value_list: Iterable[FieldValue]
|
|
158
|
+
def query_models_with_criteria(self, wrapper_type: type[WrappedType], field: FieldIdentifier,
|
|
159
|
+
value_list: Iterable[FieldValue],
|
|
201
160
|
paging_criteria: DataRecordPojoPageCriteria | None = None,
|
|
202
161
|
page_limit: int | None = None) \
|
|
203
|
-
-> tuple[list[WrappedType]
|
|
162
|
+
-> tuple[list[WrappedType], DataRecordPojoPageCriteria]:
|
|
204
163
|
"""
|
|
205
164
|
Shorthand for using the data record manager to query for a list of data records by field value
|
|
206
165
|
and then converting the results into a list of record models.
|
|
207
166
|
|
|
208
|
-
:param wrapper_type: The record model wrapper to use
|
|
167
|
+
:param wrapper_type: The record model wrapper to use.
|
|
209
168
|
:param field: The field to query on.
|
|
210
|
-
:param value_list: The values of the field to query on
|
|
211
|
-
converted to a singleton list. Note that field values of None are not supported by this method and will be
|
|
212
|
-
ignored. If you need to query for records with a null field value, use a custom report.
|
|
169
|
+
:param value_list: The values of the field to query on.
|
|
213
170
|
:param paging_criteria: The paging criteria to start the query with.
|
|
214
171
|
:param page_limit: The maximum number of pages to query from the starting criteria. If None, exhausts all
|
|
215
172
|
possible pages. This parameter only functions if you set a page size in the paging criteria or the platform
|
|
216
173
|
enforces a page size.
|
|
217
|
-
:return: The record models for the queried records and the final paging criteria.
|
|
218
|
-
instead of a model wrapper, then the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
174
|
+
:return: The record models for the queried records and the final paging criteria.
|
|
219
175
|
"""
|
|
220
|
-
dt: str =
|
|
221
|
-
if isinstance(wrapper_type, str):
|
|
222
|
-
wrapper_type = None
|
|
176
|
+
dt: str = wrapper_type.get_wrapper_data_type_name()
|
|
223
177
|
field: str = AliasUtil.to_data_field_name(field)
|
|
224
|
-
if isinstance(value_list, FieldValue):
|
|
225
|
-
value_list: list[FieldValue] = [value_list]
|
|
226
178
|
pager = QueryDataRecordsAutoPager(dt, field, list(value_list), self.user, paging_criteria)
|
|
227
179
|
pager.max_page = page_limit
|
|
228
180
|
return self.wrap_models(pager.get_all_at_once(), wrapper_type), pager.next_page_criteria
|
|
229
181
|
|
|
230
|
-
def query_models_by_id(self, wrapper_type: type[WrappedType]
|
|
231
|
-
page_limit: int | None = None, page_size: int | None = None)
|
|
232
|
-
-> list[WrappedType] | list[PyRecordModel]:
|
|
182
|
+
def query_models_by_id(self, wrapper_type: type[WrappedType], ids: Iterable[int],
|
|
183
|
+
page_limit: int | None = None, page_size: int | None = None) -> list[WrappedType]:
|
|
233
184
|
"""
|
|
234
185
|
Shorthand for using the data record manager to query for a list of data records by record ID
|
|
235
186
|
and then converting the results into a list of record models.
|
|
236
187
|
|
|
237
|
-
:param wrapper_type: The record model wrapper to use
|
|
188
|
+
:param wrapper_type: The record model wrapper to use.
|
|
238
189
|
:param ids: The list of record IDs to query.
|
|
239
190
|
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages. This parameter
|
|
240
191
|
only functions if you set a page size or the platform enforces a page size.
|
|
241
192
|
:param page_size: The size of the pages to query. If None, the page size may be limited by the platform.
|
|
242
|
-
:return: The record models for the queried records.
|
|
243
|
-
then the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
193
|
+
:return: The record models for the queried records.
|
|
244
194
|
"""
|
|
245
195
|
criteria: DataRecordPojoPageCriteria | None = None
|
|
246
196
|
if page_size is not None:
|
|
247
197
|
criteria = DataRecordPojoPageCriteria(page_size=page_size)
|
|
248
198
|
return self.query_models_by_id_with_criteria(wrapper_type, ids, criteria, page_limit)[0]
|
|
249
199
|
|
|
250
|
-
def query_models_by_id_with_criteria(self, wrapper_type: type[WrappedType]
|
|
200
|
+
def query_models_by_id_with_criteria(self, wrapper_type: type[WrappedType], ids: Iterable[int],
|
|
251
201
|
paging_criteria: DataRecordPojoPageCriteria | None = None,
|
|
252
202
|
page_limit: int | None = None) \
|
|
253
|
-
-> tuple[list[WrappedType]
|
|
203
|
+
-> tuple[list[WrappedType], DataRecordPojoPageCriteria]:
|
|
254
204
|
"""
|
|
255
205
|
Shorthand for using the data record manager to query for a list of data records by record ID
|
|
256
206
|
and then converting the results into a list of record models.
|
|
257
207
|
|
|
258
|
-
:param wrapper_type: The record model wrapper to use
|
|
208
|
+
:param wrapper_type: The record model wrapper to use.
|
|
259
209
|
:param ids: The list of record IDs to query.
|
|
260
210
|
:param paging_criteria: The paging criteria to start the query with.
|
|
261
211
|
:param page_limit: The maximum number of pages to query from the starting criteria. If None, exhausts all
|
|
262
212
|
possible pages. This parameter only functions if you set a page size in the paging criteria or the platform
|
|
263
213
|
enforces a page size.
|
|
264
|
-
:return: The record models for the queried records and the final paging criteria.
|
|
265
|
-
instead of a model wrapper, then the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
214
|
+
:return: The record models for the queried records and the final paging criteria.
|
|
266
215
|
"""
|
|
267
|
-
dt: str =
|
|
268
|
-
if isinstance(wrapper_type, str):
|
|
269
|
-
wrapper_type = None
|
|
216
|
+
dt: str = wrapper_type.get_wrapper_data_type_name()
|
|
270
217
|
pager = QueryDataRecordByIdListAutoPager(dt, list(ids), self.user, paging_criteria)
|
|
271
218
|
pager.max_page = page_limit
|
|
272
219
|
return self.wrap_models(pager.get_all_at_once(), wrapper_type), pager.next_page_criteria
|
|
273
220
|
|
|
274
|
-
def query_models_by_id_and_map(self, wrapper_type: type[WrappedType]
|
|
221
|
+
def query_models_by_id_and_map(self, wrapper_type: type[WrappedType], ids: Iterable[int],
|
|
275
222
|
page_limit: int | None = None, page_size: int | None = None) \
|
|
276
|
-
-> dict[int, WrappedType
|
|
223
|
+
-> dict[int, WrappedType]:
|
|
277
224
|
"""
|
|
278
225
|
Shorthand for using the data record manager to query for a list of data records by record ID
|
|
279
226
|
and then converting the results into a dictionary of record ID to the record model for that ID.
|
|
280
227
|
|
|
281
|
-
:param wrapper_type: The record model wrapper to use
|
|
228
|
+
:param wrapper_type: The record model wrapper to use.
|
|
282
229
|
:param ids: The list of record IDs to query.
|
|
283
230
|
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages. This parameter
|
|
284
231
|
only functions if you set a page size or the platform enforces a page size.
|
|
285
232
|
:param page_size: The size of the pages to query. If None, the page size may be limited by the platform.
|
|
286
233
|
:return: The record models for the queried records mapped in a dictionary by their record ID.
|
|
287
|
-
If a data type name was used instead of a model wrapper, then the returned records will be PyRecordModels
|
|
288
|
-
instead of WrappedRecordModels.
|
|
289
234
|
"""
|
|
290
|
-
return {
|
|
235
|
+
return {x.record_id: x for x in self.query_models_by_id(wrapper_type, ids, page_limit, page_size)}
|
|
291
236
|
|
|
292
|
-
def query_all_models(self, wrapper_type: type[WrappedType]
|
|
293
|
-
page_size: int | None = None) -> list[WrappedType]
|
|
237
|
+
def query_all_models(self, wrapper_type: type[WrappedType], page_limit: int | None = None,
|
|
238
|
+
page_size: int | None = None) -> list[WrappedType]:
|
|
294
239
|
"""
|
|
295
240
|
Shorthand for using the data record manager to query for all data records of a given type
|
|
296
241
|
and then converting the results into a list of record models.
|
|
297
242
|
|
|
298
|
-
:param wrapper_type: The record model wrapper to use
|
|
243
|
+
:param wrapper_type: The record model wrapper to use.
|
|
299
244
|
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages. This parameter
|
|
300
245
|
only functions if you set a page size or the platform enforces a page size.
|
|
301
246
|
:param page_size: The size of the pages to query. If None, the page size may be limited by the platform.
|
|
302
|
-
:return: The record models for the queried records.
|
|
303
|
-
then the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
247
|
+
:return: The record models for the queried records.
|
|
304
248
|
"""
|
|
305
249
|
criteria: DataRecordPojoPageCriteria | None = None
|
|
306
250
|
if page_size is not None:
|
|
307
251
|
criteria = DataRecordPojoPageCriteria(page_size=page_size)
|
|
308
252
|
return self.query_all_models_with_criteria(wrapper_type, criteria, page_limit)[0]
|
|
309
253
|
|
|
310
|
-
def query_all_models_with_criteria(self, wrapper_type: type[WrappedType]
|
|
254
|
+
def query_all_models_with_criteria(self, wrapper_type: type[WrappedType],
|
|
311
255
|
paging_criteria: DataRecordPojoPageCriteria | None = None,
|
|
312
256
|
page_limit: int | None = None) \
|
|
313
|
-
-> tuple[list[WrappedType]
|
|
257
|
+
-> tuple[list[WrappedType], DataRecordPojoPageCriteria]:
|
|
314
258
|
"""
|
|
315
259
|
Shorthand for using the data record manager to query for all data records of a given type
|
|
316
260
|
and then converting the results into a list of record models.
|
|
317
261
|
|
|
318
|
-
:param wrapper_type: The record model wrapper to use
|
|
262
|
+
:param wrapper_type: The record model wrapper to use.
|
|
319
263
|
:param paging_criteria: The paging criteria to start the query with.
|
|
320
264
|
:param page_limit: The maximum number of pages to query from the starting criteria. If None, exhausts all
|
|
321
265
|
possible pages. This parameter only functions if you set a page size in the paging criteria or the platform
|
|
322
266
|
enforces a page size.
|
|
323
|
-
:return: The record models for the queried records and the final paging criteria.
|
|
324
|
-
instead of a model wrapper, then the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
267
|
+
:return: The record models for the queried records and the final paging criteria.
|
|
325
268
|
"""
|
|
326
|
-
dt: str =
|
|
327
|
-
if isinstance(wrapper_type, str):
|
|
328
|
-
wrapper_type = None
|
|
269
|
+
dt: str = wrapper_type.get_wrapper_data_type_name()
|
|
329
270
|
pager = QueryAllRecordsOfTypeAutoPager(dt, self.user, paging_criteria)
|
|
330
271
|
pager.max_page = page_limit
|
|
331
272
|
return self.wrap_models(pager.get_all_at_once(), wrapper_type), pager.next_page_criteria
|
|
332
273
|
|
|
333
|
-
def query_models_by_report(self, wrapper_type: type[WrappedType]
|
|
274
|
+
def query_models_by_report(self, wrapper_type: type[WrappedType],
|
|
334
275
|
report_name: str | RawReportTerm | CustomReportCriteria,
|
|
335
276
|
filters: dict[FieldIdentifierKey, Iterable[FieldValue]] | None = None,
|
|
336
277
|
page_limit: int | None = None,
|
|
337
278
|
page_size: int | None = None,
|
|
338
|
-
page_number: int | None = None) -> list[WrappedType]
|
|
279
|
+
page_number: int | None = None) -> list[WrappedType]:
|
|
339
280
|
"""
|
|
340
281
|
Run a report and use the results of that report to query for and return the records in the report results.
|
|
341
282
|
First runs the report, then runs a data record manager query on the results of the custom report.
|
|
@@ -345,7 +286,7 @@ class RecordHandler:
|
|
|
345
286
|
|
|
346
287
|
Any given custom report criteria should only have columns from a single data type.
|
|
347
288
|
|
|
348
|
-
:param wrapper_type: The record model wrapper to use
|
|
289
|
+
:param wrapper_type: The record model wrapper to use.
|
|
349
290
|
:param report_name: The name of a system report, or a raw report term for a quick report, or custom report
|
|
350
291
|
criteria for a custom report.
|
|
351
292
|
:param filters: If provided, filter the results of the report using the given mapping of headers to values to
|
|
@@ -357,10 +298,8 @@ class RecordHandler:
|
|
|
357
298
|
:param page_number: The page number to start the search from, If None, starts on the first page.
|
|
358
299
|
If the input report is a custom report criteria, uses the value from the criteria, unless this value is
|
|
359
300
|
not None, in which case it overwrites the given report's value. Note that the number of the first page is 0.
|
|
360
|
-
:return: The record models for the queried records that matched the given report.
|
|
361
|
-
instead of a model wrapper, then the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
301
|
+
:return: The record models for the queried records that matched the given report.
|
|
362
302
|
"""
|
|
363
|
-
warnings.warn("Deprecated in favor of the [System/Custom/Quick]ReportRecordAutoPager classes.", DeprecationWarning)
|
|
364
303
|
if isinstance(report_name, str):
|
|
365
304
|
results: list[dict[str, FieldValue]] = CustomReportUtil.run_system_report(self.user, report_name, filters,
|
|
366
305
|
page_limit, page_size, page_number)
|
|
@@ -368,7 +307,7 @@ class RecordHandler:
|
|
|
368
307
|
results: list[dict[str, FieldValue]] = CustomReportUtil.run_quick_report(self.user, report_name, filters,
|
|
369
308
|
page_limit, page_size, page_number)
|
|
370
309
|
elif isinstance(report_name, CustomReportCriteria):
|
|
371
|
-
dt: str =
|
|
310
|
+
dt: str = wrapper_type.get_wrapper_data_type_name()
|
|
372
311
|
# Ensure that the root data type is the one we're looking for.
|
|
373
312
|
report_name.root_data_type = dt
|
|
374
313
|
# Raise an exception if any column in the report doesn't match the given data type.
|
|
@@ -383,45 +322,40 @@ class RecordHandler:
|
|
|
383
322
|
raise SapioException("Unrecognized report object.")
|
|
384
323
|
|
|
385
324
|
# Using the bracket accessor because we want to throw an exception if RecordId doesn't exist in the report.
|
|
386
|
-
# This should only possibly be the case with system reports, as quick reports will include the record ID
|
|
325
|
+
# This should only possibly be the case with system reports, as quick reports will include the record ID and
|
|
387
326
|
# we forced any given custom report to have a record ID column.
|
|
388
327
|
ids: list[int] = [row["RecordId"] for row in results]
|
|
389
328
|
return self.query_models_by_id(wrapper_type, ids)
|
|
390
329
|
|
|
391
|
-
def add_model(self, wrapper_type: type[WrappedType]
|
|
330
|
+
def add_model(self, wrapper_type: type[WrappedType]) -> WrappedType:
|
|
392
331
|
"""
|
|
393
332
|
Shorthand for using the instance manager to add a new record model of the given type.
|
|
394
333
|
|
|
395
|
-
:param wrapper_type: The record model wrapper to use
|
|
396
|
-
:return: The newly added record model.
|
|
397
|
-
returned record will be a PyRecordModel instead of a WrappedRecordModel.
|
|
334
|
+
:param wrapper_type: The record model wrapper to use.
|
|
335
|
+
:return: The newly added record model.
|
|
398
336
|
"""
|
|
399
|
-
return self.
|
|
337
|
+
return self.inst_man.add_new_record_of_type(wrapper_type)
|
|
400
338
|
|
|
401
|
-
def add_models(self, wrapper_type: type[WrappedType]
|
|
339
|
+
def add_models(self, wrapper_type: type[WrappedType], num: int) -> list[WrappedType]:
|
|
402
340
|
"""
|
|
403
341
|
Shorthand for using the instance manager to add new record models of the given type.
|
|
404
342
|
|
|
405
|
-
:param wrapper_type: The record model wrapper to use
|
|
343
|
+
:param wrapper_type: The record model wrapper to use.
|
|
406
344
|
:param num: The number of models to create.
|
|
407
|
-
:return: The newly added record models.
|
|
408
|
-
returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
345
|
+
:return: The newly added record models.
|
|
409
346
|
"""
|
|
410
|
-
if isinstance(wrapper_type, str):
|
|
411
|
-
return self.inst_man.add_new_records(wrapper_type, num)
|
|
412
347
|
return self.inst_man.add_new_records_of_type(num, wrapper_type)
|
|
413
348
|
|
|
414
|
-
def add_models_with_data(self, wrapper_type: type[WrappedType]
|
|
415
|
-
-> list[WrappedType]
|
|
349
|
+
def add_models_with_data(self, wrapper_type: type[WrappedType], fields: list[FieldIdentifierMap]) \
|
|
350
|
+
-> list[WrappedType]:
|
|
416
351
|
"""
|
|
417
352
|
Shorthand for using the instance manager to add new models of the given type, and then initializing all those
|
|
418
353
|
models with the given fields.
|
|
419
354
|
|
|
420
|
-
:param wrapper_type: The record model wrapper to use
|
|
355
|
+
:param wrapper_type: The record model wrapper to use.
|
|
421
356
|
:param fields: A list of field maps to initialize the record models with.
|
|
422
357
|
:return: The newly added record models with the provided fields set. The records will be in the same order as
|
|
423
|
-
the fields in the fields list.
|
|
424
|
-
records will be PyRecordModels instead of WrappedRecordModels.
|
|
358
|
+
the fields in the fields list.
|
|
425
359
|
"""
|
|
426
360
|
fields: list[FieldMap] = AliasUtil.to_data_field_names_list_dict(fields)
|
|
427
361
|
models: list[WrappedType] = self.add_models(wrapper_type, len(fields))
|
|
@@ -429,9 +363,8 @@ class RecordHandler:
|
|
|
429
363
|
model.set_field_values(field_list)
|
|
430
364
|
return models
|
|
431
365
|
|
|
432
|
-
def find_or_add_model(self, wrapper_type: type[WrappedType]
|
|
433
|
-
id_value: FieldValue, secondary_identifiers: FieldIdentifierMap | None = None)
|
|
434
|
-
-> WrappedType | PyRecordModel:
|
|
366
|
+
def find_or_add_model(self, wrapper_type: type[WrappedType], primary_identifier: FieldIdentifier,
|
|
367
|
+
id_value: FieldValue, secondary_identifiers: FieldIdentifierMap | None = None) -> WrappedType:
|
|
435
368
|
"""
|
|
436
369
|
Find a unique record that matches the given field values. If no such records exist, add a record model to the
|
|
437
370
|
cache with the identifying fields set to the desired values. This record will be created in the system when
|
|
@@ -442,14 +375,12 @@ class RecordHandler:
|
|
|
442
375
|
|
|
443
376
|
Makes a webservice call to query for the existing record.
|
|
444
377
|
|
|
445
|
-
:param wrapper_type: The record model wrapper to use
|
|
378
|
+
:param wrapper_type: The record model wrapper to use.
|
|
446
379
|
:param primary_identifier: The data field name of the field to search on.
|
|
447
380
|
:param id_value: The value of the identifying field to search for.
|
|
448
381
|
:param secondary_identifiers: Optional fields used to filter the records that are returned after searching on
|
|
449
382
|
the primary identifier.
|
|
450
383
|
:return: The record model with the identifying field value, either pulled from the system or newly created.
|
|
451
|
-
If a data type name was used instead of a model wrapper, then the returned record will be a PyRecordModel
|
|
452
|
-
instead of a WrappedRecordModel.
|
|
453
384
|
"""
|
|
454
385
|
# PR-46335: Initialize the secondary identifiers parameter if None is provided to avoid an exception.
|
|
455
386
|
# If no secondary identifiers were provided, use an empty dictionary.
|
|
@@ -470,25 +401,22 @@ class RecordHandler:
|
|
|
470
401
|
secondary_identifiers.update({primary_identifier: id_value})
|
|
471
402
|
return self.add_models_with_data(wrapper_type, [secondary_identifiers])[0]
|
|
472
403
|
|
|
473
|
-
def create_models(self, wrapper_type: type[WrappedType]
|
|
404
|
+
def create_models(self, wrapper_type: type[WrappedType], num: int) -> list[WrappedType]:
|
|
474
405
|
"""
|
|
475
406
|
Shorthand for creating new records via the data record manager and then returning them as wrapped
|
|
476
407
|
record models. Useful in cases where your record model needs to have a valid record ID.
|
|
477
408
|
|
|
478
409
|
Makes a webservice call to create the data records.
|
|
479
410
|
|
|
480
|
-
:param wrapper_type: The record model wrapper to use
|
|
411
|
+
:param wrapper_type: The record model wrapper to use.
|
|
481
412
|
:param num: The number of new records to create.
|
|
482
|
-
:return: The newly created record models.
|
|
483
|
-
returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
413
|
+
:return: The newly created record models.
|
|
484
414
|
"""
|
|
485
|
-
dt: str =
|
|
486
|
-
if isinstance(wrapper_type, str):
|
|
487
|
-
wrapper_type = None
|
|
415
|
+
dt: str = wrapper_type.get_wrapper_data_type_name()
|
|
488
416
|
return self.wrap_models(self.dr_man.add_data_records(dt, num), wrapper_type)
|
|
489
417
|
|
|
490
|
-
def create_models_with_data(self, wrapper_type: type[WrappedType]
|
|
491
|
-
-> list[WrappedType]
|
|
418
|
+
def create_models_with_data(self, wrapper_type: type[WrappedType], fields: list[FieldIdentifierMap]) \
|
|
419
|
+
-> list[WrappedType]:
|
|
492
420
|
"""
|
|
493
421
|
Shorthand for creating new records via the data record manager with field data to initialize the records with
|
|
494
422
|
and then returning them as wrapped record models. Useful in cases where your record model needs to have a valid
|
|
@@ -496,20 +424,17 @@ class RecordHandler:
|
|
|
496
424
|
|
|
497
425
|
Makes a webservice call to create the data records.
|
|
498
426
|
|
|
499
|
-
:param wrapper_type: The record model wrapper to use
|
|
427
|
+
:param wrapper_type: The record model wrapper to use.
|
|
500
428
|
:param fields: The field map list to initialize the new data records with.
|
|
501
|
-
:return: The newly created record models.
|
|
502
|
-
returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
429
|
+
:return: The newly created record models.
|
|
503
430
|
"""
|
|
504
|
-
dt: str =
|
|
505
|
-
if isinstance(wrapper_type, str):
|
|
506
|
-
wrapper_type = None
|
|
431
|
+
dt: str = wrapper_type.get_wrapper_data_type_name()
|
|
507
432
|
fields: list[FieldMap] = AliasUtil.to_data_field_names_list_dict(fields)
|
|
508
433
|
return self.wrap_models(self.dr_man.add_data_records_with_data(dt, fields), wrapper_type)
|
|
509
434
|
|
|
510
|
-
def find_or_create_model(self, wrapper_type: type[WrappedType]
|
|
435
|
+
def find_or_create_model(self, wrapper_type: type[WrappedType], primary_identifier: FieldIdentifier,
|
|
511
436
|
id_value: FieldValue, secondary_identifiers: FieldIdentifierMap | None = None) \
|
|
512
|
-
-> WrappedType
|
|
437
|
+
-> WrappedType:
|
|
513
438
|
"""
|
|
514
439
|
Find a unique record that matches the given field values. If no such records exist, create one with the
|
|
515
440
|
identifying fields set to the desired values. If more than one record with the identifying values exists,
|
|
@@ -521,14 +446,12 @@ class RecordHandler:
|
|
|
521
446
|
Makes a webservice call to query for the existing record. Makes an additional webservice call if the record
|
|
522
447
|
needs to be created.
|
|
523
448
|
|
|
524
|
-
:param wrapper_type: The record model wrapper to use
|
|
449
|
+
:param wrapper_type: The record model wrapper to use.
|
|
525
450
|
:param primary_identifier: The data field name of the field to search on.
|
|
526
451
|
:param id_value: The value of the identifying field to search for.
|
|
527
452
|
:param secondary_identifiers: Optional fields used to filter the records that are returned after searching on
|
|
528
453
|
the primary identifier.
|
|
529
454
|
:return: The record model with the identifying field value, either pulled from the system or newly created.
|
|
530
|
-
If a data type name was used instead of a model wrapper, then the returned record will be a PyRecordModel
|
|
531
|
-
instead of a WrappedRecordModel.
|
|
532
455
|
"""
|
|
533
456
|
# PR-46335: Initialize the secondary identifiers parameter if None is provided to avoid an exception.
|
|
534
457
|
# If no secondary identifiers were provided, use an empty dictionary.
|
|
@@ -549,281 +472,8 @@ class RecordHandler:
|
|
|
549
472
|
secondary_identifiers.update({primary_identifier: id_value})
|
|
550
473
|
return self.create_models_with_data(wrapper_type, [secondary_identifiers])[0]
|
|
551
474
|
|
|
552
|
-
# FR-47525: Add functions for getting and setting record image bytes.
|
|
553
|
-
def get_record_image(self, record: SapioRecord) -> bytes:
|
|
554
|
-
"""
|
|
555
|
-
Retrieve the record image for a given record.
|
|
556
|
-
|
|
557
|
-
:param record: The record model to retrieve the image of.
|
|
558
|
-
:return: The file bytes of the given record's image.
|
|
559
|
-
"""
|
|
560
|
-
record: DataRecord = AliasUtil.to_data_record(record)
|
|
561
|
-
with io.BytesIO() as data_sink:
|
|
562
|
-
def consume_data(chunk: bytes):
|
|
563
|
-
data_sink.write(chunk)
|
|
564
|
-
|
|
565
|
-
self.dr_man.get_record_image(record, consume_data)
|
|
566
|
-
data_sink.flush()
|
|
567
|
-
data_sink.seek(0)
|
|
568
|
-
file_bytes = data_sink.read()
|
|
569
|
-
return file_bytes
|
|
570
|
-
|
|
571
|
-
def set_record_image(self, record: SapioRecord, file_data: str | bytes) -> None:
|
|
572
|
-
"""
|
|
573
|
-
Set the record image for a given record.
|
|
574
|
-
|
|
575
|
-
:param record: The record model to set the image of.
|
|
576
|
-
:param file_data: The file data of the image to set on the record.
|
|
577
|
-
"""
|
|
578
|
-
record: DataRecord = AliasUtil.to_data_record(record)
|
|
579
|
-
with io.BytesIO(file_data.encode() if isinstance(file_data, str) else file_data) as stream:
|
|
580
|
-
self.dr_man.set_record_image(record, stream)
|
|
581
|
-
|
|
582
|
-
# FR-47522: Add RecordHandler functions that copy from the RecordModelUtil class in our Java utilities.
|
|
583
|
-
@staticmethod
|
|
584
|
-
def get_values_list(records: list[RecordModel], field: FieldIdentifier) -> list[FieldValue]:
|
|
585
|
-
"""
|
|
586
|
-
Get a list of field values from a list of record models.
|
|
587
|
-
|
|
588
|
-
:param records: The list of record models to get the field values from.
|
|
589
|
-
:param field: The field to get the values of.
|
|
590
|
-
:return: A list of field values from the input record models. The values are in the same order as the input
|
|
591
|
-
record models.
|
|
592
|
-
"""
|
|
593
|
-
field: str = AliasUtil.to_data_field_name(field)
|
|
594
|
-
return [x.get_field_value(field) for x in records]
|
|
595
|
-
|
|
596
|
-
@staticmethod
|
|
597
|
-
def get_values_set(records: list[RecordModel], field: FieldIdentifier) -> set[FieldValue]:
|
|
598
|
-
"""
|
|
599
|
-
Get a set of field values from a list of record models.
|
|
600
|
-
|
|
601
|
-
:param records: The list of record models to get the field values from.
|
|
602
|
-
:param field: The field to get the values of.
|
|
603
|
-
:return: A set of field values from the input record models.
|
|
604
|
-
"""
|
|
605
|
-
field: str = AliasUtil.to_data_field_name(field)
|
|
606
|
-
return {x.get_field_value(field) for x in records}
|
|
607
|
-
|
|
608
|
-
@staticmethod
|
|
609
|
-
def set_values(records: list[RecordModel], field: FieldIdentifier, value: FieldValue) -> None:
|
|
610
|
-
"""
|
|
611
|
-
Set the value of a field on a list of record models.
|
|
612
|
-
|
|
613
|
-
:param records: The list of record models to set the field value on.
|
|
614
|
-
:param field: The field to set the value of.
|
|
615
|
-
:param value: The value to set the field to for all input records.
|
|
616
|
-
"""
|
|
617
|
-
field: str = AliasUtil.to_data_field_name(field)
|
|
618
|
-
for record in records:
|
|
619
|
-
record.set_field_value(field, value)
|
|
620
|
-
|
|
621
|
-
@staticmethod
|
|
622
|
-
def get_min_record(records: list[RecordModel], field: FieldIdentifier) -> RecordModel:
|
|
623
|
-
"""
|
|
624
|
-
Get the record model with the minimum value of a given field from a list of record models.
|
|
625
|
-
|
|
626
|
-
:param records: The list of record models to search through.
|
|
627
|
-
:param field: The field to find the minimum value of.
|
|
628
|
-
:return: The record model with the minimum value of the given field.
|
|
629
|
-
"""
|
|
630
|
-
field: str = AliasUtil.to_data_field_name(field)
|
|
631
|
-
return min(records, key=lambda x: x.get_field_value(field))
|
|
632
|
-
|
|
633
|
-
@staticmethod
|
|
634
|
-
def get_max_record(records: list[RecordModel], field: FieldIdentifier) -> RecordModel:
|
|
635
|
-
"""
|
|
636
|
-
Get the record model with the maximum value of a given field from a list of record models.
|
|
637
|
-
|
|
638
|
-
:param records: The list of record models to search through.
|
|
639
|
-
:param field: The field to find the maximum value of.
|
|
640
|
-
:return: The record model with the maximum value of the given field.
|
|
641
|
-
"""
|
|
642
|
-
field: str = AliasUtil.to_data_field_name(field)
|
|
643
|
-
return max(records, key=lambda x: x.get_field_value(field))
|
|
644
|
-
|
|
645
|
-
@staticmethod
|
|
646
|
-
def get_from_all(records: Iterable[RecordModel], getter: _PropertyGetter[_PropertyType]) -> list[_PropertyType]:
|
|
647
|
-
"""
|
|
648
|
-
Use a getter property on all records in a list of record models. For example, you can iterate over a list of
|
|
649
|
-
record models using a getter of Ancestors.of_type(SampleModel) to get all the SampleModel ancestors from each
|
|
650
|
-
record.
|
|
651
|
-
|
|
652
|
-
:param records: The list of record models to get the property from.
|
|
653
|
-
:param getter: The getter to use to get the property from each record.
|
|
654
|
-
:return: A list of the property values from the input record models. The value at the matching index of the
|
|
655
|
-
input records is the results of using the getter on that record.
|
|
656
|
-
"""
|
|
657
|
-
return [x.get(getter) for x in records]
|
|
658
|
-
|
|
659
|
-
@staticmethod
|
|
660
|
-
def set_on_all(records: Iterable[RecordModel], setter: _PropertySetter[_PropertyType]) -> list[_PropertyType]:
|
|
661
|
-
"""
|
|
662
|
-
Use a setter property on all records in a list of record models. For example, you can iterate over a list of
|
|
663
|
-
record models user a setter of ForwardSideLink.ref(field_name, record) to set a forward side link on each
|
|
664
|
-
record.
|
|
665
|
-
|
|
666
|
-
:param records: The list of record models to set the property on.
|
|
667
|
-
:param setter: The setter to use to set the property on each record.
|
|
668
|
-
:return: A list of the property values that were set on the input record models. The value at the matching index
|
|
669
|
-
of the input records is the results of using the setter on that record.
|
|
670
|
-
"""
|
|
671
|
-
return [x.set(setter) for x in records]
|
|
672
|
-
|
|
673
|
-
@staticmethod
|
|
674
|
-
def add_to_all(records: Iterable[RecordModel], adder: _PropertyAdder[_PropertyType]) -> list[_PropertyType]:
|
|
675
|
-
"""
|
|
676
|
-
Use an adder property on all records in a list of record models. For example, you can iterate over a list of
|
|
677
|
-
record models using an adder of Child.create(SampleModel) to create a new SampleModel child on each record.
|
|
678
|
-
|
|
679
|
-
:param records: The list of record models to add the property to.
|
|
680
|
-
:param adder: The adder to use to add the property to each record.
|
|
681
|
-
:return: A list of the property values that were added to the input record models. The value at the matching
|
|
682
|
-
index of the input records is the results of using the adder on that record.
|
|
683
|
-
"""
|
|
684
|
-
return [x.add(adder) for x in records]
|
|
685
|
-
|
|
686
|
-
@staticmethod
|
|
687
|
-
def remove_from_all(records: Iterable[RecordModel], remover: _PropertyRemover[_PropertyType]) -> list[_PropertyType]:
|
|
688
|
-
"""
|
|
689
|
-
Use a remover property on all records in a list of record models. For example, you can iterate over a list of
|
|
690
|
-
record models using a remover of Parents.ref(records) to remove a list of parents from each record.
|
|
691
|
-
|
|
692
|
-
:param records: The list of record models to remove the property from.
|
|
693
|
-
:param remover: The remover to use to remove the property from each record.
|
|
694
|
-
:return: A list of the property values that were removed from the input record models. The value at the matching
|
|
695
|
-
index of the input records is the results of using the remover on that record.
|
|
696
|
-
"""
|
|
697
|
-
return [x.remove(remover) for x in records]
|
|
698
|
-
|
|
699
|
-
# FR-47527: Created functions for manipulating relationships between records,
|
|
700
|
-
def get_extension(self, model: RecordModel, wrapper_type: type[WrappedType] | str) \
|
|
701
|
-
-> WrappedType | PyRecordModel | None:
|
|
702
|
-
"""
|
|
703
|
-
Given a record with an extension record related to it, return the extension record as a record model.
|
|
704
|
-
This will retrieve an extension record without doing a webservice request to the server. The input record and
|
|
705
|
-
extension record will be considered related to one another if you later use load_child or load_parent on the
|
|
706
|
-
input record or extension record respectively.
|
|
707
|
-
|
|
708
|
-
:param model: The record model to get the extension for.
|
|
709
|
-
:param wrapper_type: The record model wrapper to use, or the data type name of the extension record. If a data
|
|
710
|
-
type name is provided, the returned record will be a PyRecordModel instead of a WrappedRecordModel.
|
|
711
|
-
:return: The extension record model for the input record model, or None if no extension record exists.
|
|
712
|
-
"""
|
|
713
|
-
ext_dt: str = AliasUtil.to_data_type_name(wrapper_type)
|
|
714
|
-
ext_fields: FieldMap = {}
|
|
715
|
-
for field, value in AliasUtil.to_field_map(model).items():
|
|
716
|
-
if field.startswith(ext_dt + "."):
|
|
717
|
-
ext_fields[field.removeprefix(ext_dt + ".")] = value
|
|
718
|
-
if not ext_fields or ext_fields.get("RecordId") is None:
|
|
719
|
-
return None
|
|
720
|
-
ext_rec: DataRecord = DataRecord(ext_dt, ext_fields.get("RecordId"), ext_fields)
|
|
721
|
-
ext_model: WrappedType | PyRecordModel = self.wrap_model(ext_rec, wrapper_type)
|
|
722
|
-
self._spoof_child_load(model, ext_model)
|
|
723
|
-
self._spoof_parent_load(ext_model, model)
|
|
724
|
-
return ext_model
|
|
725
|
-
|
|
726
|
-
def get_or_add_parent(self, record: RecordModel, parent_type: type[WrappedType] | str) \
|
|
727
|
-
-> WrappedType | PyRecordModel:
|
|
728
|
-
"""
|
|
729
|
-
Given a record model, retrieve the singular parent record model of a given type. If a parent of the given type
|
|
730
|
-
does not exist, a new one will be created. The parents of the given data type must already be loaded.
|
|
731
|
-
|
|
732
|
-
:param record: The record model to get the parent of.
|
|
733
|
-
:param parent_type: The record model wrapper of the parent, or the data type name of the parent. If a data type
|
|
734
|
-
name is provided, the returned record will be a PyRecordModel instead of a WrappedRecordModel.
|
|
735
|
-
:return: The parent record model of the given type.
|
|
736
|
-
"""
|
|
737
|
-
parent_dt: str = AliasUtil.to_data_type_name(parent_type)
|
|
738
|
-
wrapper: type[WrappedType] | None = parent_type if isinstance(parent_type, type) else None
|
|
739
|
-
record: PyRecordModel = RecordModelInstanceManager.unwrap(record)
|
|
740
|
-
parent: PyRecordModel | None = record.get_parent_of_type(parent_dt)
|
|
741
|
-
if parent is not None:
|
|
742
|
-
return self.wrap_model(record, wrapper) if wrapper else parent
|
|
743
|
-
return record.add(Parent.create(wrapper)) if wrapper else record.add(Parent.create_by_name(parent_dt))
|
|
744
|
-
|
|
745
|
-
def get_or_add_child(self, record: RecordModel, child_type: type[WrappedType] | str) -> WrappedType | PyRecordModel:
|
|
746
|
-
"""
|
|
747
|
-
Given a record model, retrieve the singular child record model of a given type. If a child of the given type
|
|
748
|
-
does not exist, a new one will be created. The children of the given data type must already be loaded.
|
|
749
|
-
|
|
750
|
-
:param record: The record model to get the child of.
|
|
751
|
-
:param child_type: The record model wrapper of the child, or the data type name of the child. If a data type
|
|
752
|
-
name is provided, the returned record will be a PyRecordModel instead of a WrappedRecordModel.
|
|
753
|
-
:return: The child record model of the given type.
|
|
754
|
-
"""
|
|
755
|
-
child_dt: str = AliasUtil.to_data_type_name(child_type)
|
|
756
|
-
wrapper: type[WrappedType] | None = child_type if isinstance(child_type, type) else None
|
|
757
|
-
record: PyRecordModel = RecordModelInstanceManager.unwrap(record)
|
|
758
|
-
child: PyRecordModel | None = record.get_child_of_type(child_dt)
|
|
759
|
-
if child is not None:
|
|
760
|
-
return self.wrap_model(record, wrapper) if wrapper else child
|
|
761
|
-
return record.add(Child.create(wrapper)) if wrapper else record.add(Child.create_by_name(child_dt))
|
|
762
|
-
|
|
763
|
-
def get_or_add_side_link(self, record: RecordModel, side_link_field: FieldIdentifier,
|
|
764
|
-
side_link_type: type[WrappedType] | str) -> WrappedType | PyRecordModel:
|
|
765
|
-
"""
|
|
766
|
-
Given a record model, retrieve the singular side link record model of a given type. If a side link of the given
|
|
767
|
-
type does not exist, a new one will be created. The side links of the given data type must already be loaded.
|
|
768
|
-
|
|
769
|
-
:param record: The record model to get the side link of.
|
|
770
|
-
:param side_link_field: The field name of the side link to get.
|
|
771
|
-
:param side_link_type: The record model wrapper of the side link, or the data type name of the side link. If a
|
|
772
|
-
data type name is provided, the returned record will be a PyRecordModel instead of a WrappedRecordModel.
|
|
773
|
-
:return: The side link record model of the given type.
|
|
774
|
-
"""
|
|
775
|
-
side_link_field: str = AliasUtil.to_data_field_name(side_link_field)
|
|
776
|
-
wrapper: type[WrappedType] | None = side_link_type if isinstance(side_link_type, type) else None
|
|
777
|
-
record: PyRecordModel = RecordModelInstanceManager.unwrap(record)
|
|
778
|
-
side_link: PyRecordModel | None = record.get_forward_side_link(side_link_field)
|
|
779
|
-
if side_link is not None:
|
|
780
|
-
return self.wrap_model(record, wrapper) if wrapper else side_link
|
|
781
|
-
side_link: WrappedType | PyRecordModel = self.add_model(side_link_type)
|
|
782
|
-
return record.add(ForwardSideLink.ref(side_link_field, side_link))
|
|
783
|
-
|
|
784
|
-
@staticmethod
|
|
785
|
-
def set_parents(record: RecordModel, parents: Iterable[RecordModel], parent_type: DataTypeIdentifier) -> None:
|
|
786
|
-
"""
|
|
787
|
-
Set the parents of a record model to a list of parent record models of a given type. The parents of the given
|
|
788
|
-
data type must already be loaded. This method will add the parents to the record model if they are not already
|
|
789
|
-
parents, and remove any existing parents that are not in the input list.
|
|
790
|
-
|
|
791
|
-
:param record: The record model to set the parents of.
|
|
792
|
-
:param parents: The list of parent record models to set as the parents of the input record model.
|
|
793
|
-
:param parent_type: The data type identifier of the parent record models.
|
|
794
|
-
"""
|
|
795
|
-
parent_dt: str = AliasUtil.to_data_type_name(parent_type)
|
|
796
|
-
existing_parents: list[PyRecordModel] = record.get(Parents.of_type_name(parent_dt))
|
|
797
|
-
for parent in parents:
|
|
798
|
-
if parent not in existing_parents:
|
|
799
|
-
record.add(Parent.ref(parent))
|
|
800
|
-
for parent in existing_parents:
|
|
801
|
-
if parent not in parents:
|
|
802
|
-
record.remove(Parent.ref(parent))
|
|
803
|
-
|
|
804
|
-
@staticmethod
|
|
805
|
-
def set_children(record: RecordModel, children: Iterable[RecordModel], child_type: DataTypeIdentifier) -> None:
|
|
806
|
-
"""
|
|
807
|
-
Set the children of a record model to a list of child record models of a given type. The children of the given
|
|
808
|
-
data type must already be loaded. This method will add the children to the record model if they are not already
|
|
809
|
-
children, and remove any existing children that are not in the input list.
|
|
810
|
-
|
|
811
|
-
:param record: The record model to set the children of.
|
|
812
|
-
:param children: The list of child record models to set as the children of the input record model.
|
|
813
|
-
:param child_type: The data type identifier of the child record models.
|
|
814
|
-
"""
|
|
815
|
-
child_dt: str = AliasUtil.to_data_type_name(child_type)
|
|
816
|
-
existing_children: list[PyRecordModel] = record.get(Children.of_type_name(child_dt))
|
|
817
|
-
for child in children:
|
|
818
|
-
if child not in existing_children:
|
|
819
|
-
record.add(Child.ref(child))
|
|
820
|
-
for child in existing_children:
|
|
821
|
-
if child not in children:
|
|
822
|
-
record.remove(Child.ref(child))
|
|
823
|
-
|
|
824
475
|
@staticmethod
|
|
825
|
-
def map_to_parent(models: Iterable[
|
|
826
|
-
-> dict[WrappedRecordModel, WrappedType]:
|
|
476
|
+
def map_to_parent(models: Iterable[RecordModel], parent_type: type[WrappedType]) -> dict[RecordModel, WrappedType]:
|
|
827
477
|
"""
|
|
828
478
|
Map a list of record models to a single parent of a given type. The parents must already be loaded.
|
|
829
479
|
|
|
@@ -832,14 +482,14 @@ class RecordHandler:
|
|
|
832
482
|
:return: A dict[ModelType, ParentType]. If an input model doesn't have a parent of the given parent type, then
|
|
833
483
|
it will map to None.
|
|
834
484
|
"""
|
|
835
|
-
return_dict: dict[
|
|
485
|
+
return_dict: dict[RecordModel, WrappedType] = {}
|
|
836
486
|
for model in models:
|
|
837
487
|
return_dict[model] = model.get_parent_of_type(parent_type)
|
|
838
488
|
return return_dict
|
|
839
489
|
|
|
840
490
|
@staticmethod
|
|
841
|
-
def map_to_parents(models: Iterable[
|
|
842
|
-
-> dict[
|
|
491
|
+
def map_to_parents(models: Iterable[RecordModel], parent_type: type[WrappedType]) \
|
|
492
|
+
-> dict[RecordModel, list[WrappedType]]:
|
|
843
493
|
"""
|
|
844
494
|
Map a list of record models to a list parents of a given type. The parents must already be loaded.
|
|
845
495
|
|
|
@@ -848,14 +498,14 @@ class RecordHandler:
|
|
|
848
498
|
:return: A dict[ModelType, list[ParentType]]. If an input model doesn't have a parent of the given parent type,
|
|
849
499
|
then it will map to an empty list.
|
|
850
500
|
"""
|
|
851
|
-
return_dict: dict[
|
|
501
|
+
return_dict: dict[RecordModel, list[WrappedType]] = {}
|
|
852
502
|
for model in models:
|
|
853
503
|
return_dict[model] = model.get_parents_of_type(parent_type)
|
|
854
504
|
return return_dict
|
|
855
505
|
|
|
856
506
|
@staticmethod
|
|
857
|
-
def map_by_parent(models: Iterable[
|
|
858
|
-
-> dict[WrappedType,
|
|
507
|
+
def map_by_parent(models: Iterable[RecordModel], parent_type: type[WrappedType]) \
|
|
508
|
+
-> dict[WrappedType, RecordModel]:
|
|
859
509
|
"""
|
|
860
510
|
Take a list of record models and map them by their parent. Essentially an inversion of map_to_parent.
|
|
861
511
|
If two records share the same parent, an exception will be thrown. The parents must already be loaded.
|
|
@@ -865,8 +515,8 @@ class RecordHandler:
|
|
|
865
515
|
:return: A dict[ParentType, ModelType]. If an input model doesn't have a parent of the given parent type,
|
|
866
516
|
then it will not be in the resulting dictionary.
|
|
867
517
|
"""
|
|
868
|
-
to_parent: dict[
|
|
869
|
-
by_parent: dict[WrappedType,
|
|
518
|
+
to_parent: dict[RecordModel, WrappedType] = RecordHandler.map_to_parent(models, parent_type)
|
|
519
|
+
by_parent: dict[WrappedType, RecordModel] = {}
|
|
870
520
|
for record, parent in to_parent.items():
|
|
871
521
|
if parent is None:
|
|
872
522
|
continue
|
|
@@ -877,8 +527,8 @@ class RecordHandler:
|
|
|
877
527
|
return by_parent
|
|
878
528
|
|
|
879
529
|
@staticmethod
|
|
880
|
-
def map_by_parents(models: Iterable[
|
|
881
|
-
-> dict[WrappedType, list[
|
|
530
|
+
def map_by_parents(models: Iterable[RecordModel], parent_type: type[WrappedType]) \
|
|
531
|
+
-> dict[WrappedType, list[RecordModel]]:
|
|
882
532
|
"""
|
|
883
533
|
Take a list of record models and map them by their parents. Essentially an inversion of map_to_parents. Input
|
|
884
534
|
models that share a parent will end up in the same list. The parents must already be loaded.
|
|
@@ -888,16 +538,15 @@ class RecordHandler:
|
|
|
888
538
|
:return: A dict[ParentType, list[ModelType]]. If an input model doesn't have a parent of the given parent type,
|
|
889
539
|
then it will not be in the resulting dictionary.
|
|
890
540
|
"""
|
|
891
|
-
to_parents: dict[
|
|
892
|
-
by_parents: dict[WrappedType, list[
|
|
541
|
+
to_parents: dict[RecordModel, list[WrappedType]] = RecordHandler.map_to_parents(models, parent_type)
|
|
542
|
+
by_parents: dict[WrappedType, list[RecordModel]] = {}
|
|
893
543
|
for record, parents in to_parents.items():
|
|
894
544
|
for parent in parents:
|
|
895
545
|
by_parents.setdefault(parent, []).append(record)
|
|
896
546
|
return by_parents
|
|
897
547
|
|
|
898
548
|
@staticmethod
|
|
899
|
-
def map_to_child(models: Iterable[
|
|
900
|
-
-> dict[WrappedRecordModel, WrappedType]:
|
|
549
|
+
def map_to_child(models: Iterable[RecordModel], child_type: type[WrappedType]) -> dict[RecordModel, WrappedType]:
|
|
901
550
|
"""
|
|
902
551
|
Map a list of record models to a single child of a given type. The children must already be loaded.
|
|
903
552
|
|
|
@@ -906,14 +555,14 @@ class RecordHandler:
|
|
|
906
555
|
:return: A dict[ModelType, ChildType]. If an input model doesn't have a child of the given child type, then
|
|
907
556
|
it will map to None.
|
|
908
557
|
"""
|
|
909
|
-
return_dict: dict[
|
|
558
|
+
return_dict: dict[RecordModel, WrappedType] = {}
|
|
910
559
|
for model in models:
|
|
911
560
|
return_dict[model] = model.get_child_of_type(child_type)
|
|
912
561
|
return return_dict
|
|
913
562
|
|
|
914
563
|
@staticmethod
|
|
915
|
-
def map_to_children(models: Iterable[
|
|
916
|
-
-> dict[
|
|
564
|
+
def map_to_children(models: Iterable[RecordModel], child_type: type[WrappedType]) \
|
|
565
|
+
-> dict[RecordModel, list[WrappedType]]:
|
|
917
566
|
"""
|
|
918
567
|
Map a list of record models to a list children of a given type. The children must already be loaded.
|
|
919
568
|
|
|
@@ -922,14 +571,14 @@ class RecordHandler:
|
|
|
922
571
|
:return: A dict[ModelType, list[ChildType]]. If an input model doesn't have children of the given child type,
|
|
923
572
|
then it will map to an empty list.
|
|
924
573
|
"""
|
|
925
|
-
return_dict: dict[
|
|
574
|
+
return_dict: dict[RecordModel, list[WrappedType]] = {}
|
|
926
575
|
for model in models:
|
|
927
576
|
return_dict[model] = model.get_children_of_type(child_type)
|
|
928
577
|
return return_dict
|
|
929
578
|
|
|
930
579
|
@staticmethod
|
|
931
|
-
def map_by_child(models: Iterable[
|
|
932
|
-
-> dict[WrappedType,
|
|
580
|
+
def map_by_child(models: Iterable[RecordModel], child_type: type[WrappedType]) \
|
|
581
|
+
-> dict[WrappedType, RecordModel]:
|
|
933
582
|
"""
|
|
934
583
|
Take a list of record models and map them by their children. Essentially an inversion of map_to_child.
|
|
935
584
|
If two records share the same child, an exception will be thrown. The children must already be loaded.
|
|
@@ -939,8 +588,8 @@ class RecordHandler:
|
|
|
939
588
|
:return: A dict[ChildType, ModelType]. If an input model doesn't have a child of the given child type,
|
|
940
589
|
then it will not be in the resulting dictionary.
|
|
941
590
|
"""
|
|
942
|
-
to_child: dict[
|
|
943
|
-
by_child: dict[WrappedType,
|
|
591
|
+
to_child: dict[RecordModel, WrappedType] = RecordHandler.map_to_child(models, child_type)
|
|
592
|
+
by_child: dict[WrappedType, RecordModel] = {}
|
|
944
593
|
for record, child in to_child.items():
|
|
945
594
|
if child is None:
|
|
946
595
|
continue
|
|
@@ -951,8 +600,8 @@ class RecordHandler:
|
|
|
951
600
|
return by_child
|
|
952
601
|
|
|
953
602
|
@staticmethod
|
|
954
|
-
def map_by_children(models: Iterable[
|
|
955
|
-
-> dict[WrappedType, list[
|
|
603
|
+
def map_by_children(models: Iterable[RecordModel], child_type: type[WrappedType]) \
|
|
604
|
+
-> dict[WrappedType, list[RecordModel]]:
|
|
956
605
|
"""
|
|
957
606
|
Take a list of record models and map them by their children. Essentially an inversion of map_to_children. Input
|
|
958
607
|
models that share a child will end up in the same list. The children must already be loaded.
|
|
@@ -962,8 +611,8 @@ class RecordHandler:
|
|
|
962
611
|
:return: A dict[ChildType, list[ModelType]]. If an input model doesn't have children of the given child type,
|
|
963
612
|
then it will not be in the resulting dictionary.
|
|
964
613
|
"""
|
|
965
|
-
to_children: dict[
|
|
966
|
-
by_children: dict[WrappedType, list[
|
|
614
|
+
to_children: dict[RecordModel, list[WrappedType]] = RecordHandler.map_to_children(models, child_type)
|
|
615
|
+
by_children: dict[WrappedType, list[RecordModel]] = {}
|
|
967
616
|
for record, children in to_children.items():
|
|
968
617
|
for child in children:
|
|
969
618
|
by_children.setdefault(child, []).append(record)
|
|
@@ -1202,7 +851,7 @@ class RecordHandler:
|
|
|
1202
851
|
return field_sum
|
|
1203
852
|
|
|
1204
853
|
@staticmethod
|
|
1205
|
-
def mean_of_field(models:
|
|
854
|
+
def mean_of_field(models: Iterable[SapioRecord], field_name: FieldIdentifier) -> float:
|
|
1206
855
|
"""
|
|
1207
856
|
Calculate the mean of the numeric value of a given field across all input models. Excepts that all given models
|
|
1208
857
|
have a value. If the field is an integer field, the value will be converted to a float.
|
|
@@ -1211,7 +860,7 @@ class RecordHandler:
|
|
|
1211
860
|
:param field_name: The name of the numeric field to mean.
|
|
1212
861
|
:return: The mean of the field values for the collection of models.
|
|
1213
862
|
"""
|
|
1214
|
-
return RecordHandler.sum_of_field(models, field_name) / len(models)
|
|
863
|
+
return RecordHandler.sum_of_field(models, field_name) / len(list(models))
|
|
1215
864
|
|
|
1216
865
|
@staticmethod
|
|
1217
866
|
def get_newest_record(records: Iterable[SapioRecord]) -> SapioRecord:
|
|
@@ -1221,7 +870,11 @@ class RecordHandler:
|
|
|
1221
870
|
:param records: The list of records.
|
|
1222
871
|
:return: The input record with the highest record ID. None if the input list is empty.
|
|
1223
872
|
"""
|
|
1224
|
-
|
|
873
|
+
newest: SapioRecord | None = None
|
|
874
|
+
for record in records:
|
|
875
|
+
if newest is None or record.record_id > newest.record_id:
|
|
876
|
+
newest = record
|
|
877
|
+
return newest
|
|
1225
878
|
|
|
1226
879
|
# FR-46696: Add a function for getting the oldest record in a list, just like we have one for the newest record.
|
|
1227
880
|
@staticmethod
|
|
@@ -1232,18 +885,22 @@ class RecordHandler:
|
|
|
1232
885
|
:param records: The list of records.
|
|
1233
886
|
:return: The input record with the lowest record ID. None if the input list is empty.
|
|
1234
887
|
"""
|
|
1235
|
-
|
|
888
|
+
oldest: SapioRecord | None = None
|
|
889
|
+
for record in records:
|
|
890
|
+
if oldest is None or record.record_id < oldest.record_id:
|
|
891
|
+
oldest = record
|
|
892
|
+
return oldest
|
|
1236
893
|
|
|
1237
894
|
@staticmethod
|
|
1238
895
|
def values_to_field_maps(field_name: FieldIdentifier, values: Iterable[FieldValue],
|
|
1239
|
-
existing_fields: list[
|
|
896
|
+
existing_fields: list[FieldIdentifier] | None = None) -> list[FieldMap]:
|
|
1240
897
|
"""
|
|
1241
898
|
Add a list of values for a specific field to a list of dictionaries pairing each value to that field name.
|
|
1242
899
|
|
|
1243
900
|
:param field_name: The name of the field that the values are from.
|
|
1244
901
|
:param values: A list of field values.
|
|
1245
902
|
:param existing_fields: An optional existing fields map list to add the new values to. Values are added in the
|
|
1246
|
-
|
|
903
|
+
list in the same order that they appear. If no existing fields are provided, returns a new fields map list.
|
|
1247
904
|
:return: A fields map list that contains the given values mapped by the given field name.
|
|
1248
905
|
"""
|
|
1249
906
|
# Update the existing fields map list if one is given.
|
|
@@ -1262,9 +919,8 @@ class RecordHandler:
|
|
|
1262
919
|
|
|
1263
920
|
# FR-46155: Update relationship path traversing functions to be non-static and take in a wrapper type so that the
|
|
1264
921
|
# output can be wrapped instead of requiring the user to wrap the output.
|
|
1265
|
-
def get_linear_path(self, models: Iterable[RecordModel], path: RelationshipPath,
|
|
1266
|
-
|
|
1267
|
-
-> dict[RecordModel, WrappedType | PyRecordModel | None]:
|
|
922
|
+
def get_linear_path(self, models: Iterable[RecordModel], path: RelationshipPath, wrapper_type: type[WrappedType]) \
|
|
923
|
+
-> dict[RecordModel, WrappedType | None]:
|
|
1268
924
|
"""
|
|
1269
925
|
Given a relationship path, travel the path starting from the input models. Returns the record at the end of the
|
|
1270
926
|
path, if any. The hierarchy must be linear (1:1 relationship between data types at every step) and the
|
|
@@ -1272,8 +928,7 @@ class RecordHandler:
|
|
|
1272
928
|
|
|
1273
929
|
:param models: A list of record models.
|
|
1274
930
|
:param path: The relationship path to follow.
|
|
1275
|
-
:param wrapper_type: The record model wrapper to use
|
|
1276
|
-
the record will be a PyRecordModel instead of a WrappedRecordModel.
|
|
931
|
+
:param wrapper_type: The record model wrapper to use.
|
|
1277
932
|
:return: Each record model mapped to the record at the end of the path starting from itself. If the end of the
|
|
1278
933
|
path couldn't be reached, the record will map to None.
|
|
1279
934
|
"""
|
|
@@ -1321,12 +976,11 @@ class RecordHandler:
|
|
|
1321
976
|
current = reverse_links[0]
|
|
1322
977
|
else:
|
|
1323
978
|
raise SapioException("Unsupported path direction.")
|
|
1324
|
-
ret_dict.update({model: self.
|
|
979
|
+
ret_dict.update({model: self.inst_man.wrap(current, wrapper_type) if current else None})
|
|
1325
980
|
return ret_dict
|
|
1326
981
|
|
|
1327
982
|
def get_branching_path(self, models: Iterable[RecordModel], path: RelationshipPath,
|
|
1328
|
-
wrapper_type: type[WrappedType]
|
|
1329
|
-
-> dict[RecordModel, list[WrappedType] | list[PyRecordModel]]:
|
|
983
|
+
wrapper_type: type[WrappedType]) -> dict[RecordModel, list[WrappedType]]:
|
|
1330
984
|
"""
|
|
1331
985
|
Given a relationship path, travel the path starting from the input models. Returns the record at the end of the
|
|
1332
986
|
path, if any. The hierarchy may be non-linear (1:Many relationships between data types are allowed) and the
|
|
@@ -1334,8 +988,7 @@ class RecordHandler:
|
|
|
1334
988
|
|
|
1335
989
|
:param models: A list of record models.
|
|
1336
990
|
:param path: The relationship path to follow.
|
|
1337
|
-
:param wrapper_type: The record model wrapper to use
|
|
1338
|
-
the records will be PyRecordModels instead of WrappedRecordModels.
|
|
991
|
+
:param wrapper_type: The record model wrapper to use.
|
|
1339
992
|
:return: Each record model mapped to the records at the end of the path starting from itself. If the end of the
|
|
1340
993
|
path couldn't be reached, the record will map to an empty list.
|
|
1341
994
|
"""
|
|
@@ -1368,14 +1021,13 @@ class RecordHandler:
|
|
|
1368
1021
|
raise SapioException("Unsupported path direction.")
|
|
1369
1022
|
current_search = next_search
|
|
1370
1023
|
next_search = set()
|
|
1371
|
-
ret_dict.update({model: self.
|
|
1024
|
+
ret_dict.update({model: self.inst_man.wrap_list(list(current_search), wrapper_type)})
|
|
1372
1025
|
return ret_dict
|
|
1373
1026
|
|
|
1374
1027
|
# FR-46155: Create a relationship traversing function that returns a single function at the end of the path like
|
|
1375
1028
|
# get_linear_path but can handle branching paths in the middle of the search like get_branching_path.
|
|
1376
|
-
def get_flat_path(self, models: Iterable[RecordModel], path: RelationshipPath,
|
|
1377
|
-
|
|
1378
|
-
-> dict[RecordModel, WrappedType | PyRecordModel | None]:
|
|
1029
|
+
def get_flat_path(self, models: Iterable[RecordModel], path: RelationshipPath, wrapper_type: type[WrappedType]) \
|
|
1030
|
+
-> dict[RecordModel, WrappedType | None]:
|
|
1379
1031
|
"""
|
|
1380
1032
|
Given a relationship path, travel the path starting from the input models. Returns the record at the end of the
|
|
1381
1033
|
path, if any. The hierarchy may be non-linear (1:Many relationships between data types are allowed) and the
|
|
@@ -1387,8 +1039,7 @@ class RecordHandler:
|
|
|
1387
1039
|
|
|
1388
1040
|
:param models: A list of record models.
|
|
1389
1041
|
:param path: The relationship path to follow.
|
|
1390
|
-
:param wrapper_type: The record model wrapper to use
|
|
1391
|
-
the record will be a PyRecordModel instead of a WrappedRecordModel.
|
|
1042
|
+
:param wrapper_type: The record model wrapper to use.
|
|
1392
1043
|
:return: Each record model mapped to the record at the end of the path starting from itself. If the end of the
|
|
1393
1044
|
path couldn't be reached, the record will map to None.
|
|
1394
1045
|
"""
|
|
@@ -1416,22 +1067,21 @@ class RecordHandler:
|
|
|
1416
1067
|
current = current[0].get_reverse_side_link(data_type, node.data_field_name)
|
|
1417
1068
|
else:
|
|
1418
1069
|
raise SapioException("Unsupported path direction.")
|
|
1419
|
-
ret_dict.update({model: self.
|
|
1070
|
+
ret_dict.update({model: self.inst_man.wrap(current[0], wrapper_type) if current else None})
|
|
1420
1071
|
return ret_dict
|
|
1421
1072
|
|
|
1422
|
-
def __find_model(self, wrapper_type: type[WrappedType]
|
|
1423
|
-
secondary_identifiers: FieldIdentifierMap | None = None) -> WrappedType |
|
|
1073
|
+
def __find_model(self, wrapper_type: type[WrappedType], primary_identifier: str, id_value: FieldValue,
|
|
1074
|
+
secondary_identifiers: FieldIdentifierMap | None = None) -> WrappedType | None:
|
|
1424
1075
|
"""
|
|
1425
1076
|
Find a record from the system that matches the given field values. The primary identifier and value is used
|
|
1426
1077
|
to query for the record, then the secondary identifiers may be optionally provided to further filter the
|
|
1427
1078
|
returned results. If no record is found with these filters, returns None.
|
|
1428
1079
|
"""
|
|
1429
1080
|
# Query for all records that match the primary identifier.
|
|
1430
|
-
results: list[WrappedType]
|
|
1431
|
-
[id_value])
|
|
1081
|
+
results: list[WrappedType] = self.query_models(wrapper_type, primary_identifier, [id_value])
|
|
1432
1082
|
|
|
1433
1083
|
# Find the one record, if any, that matches the secondary identifiers.
|
|
1434
|
-
unique_record: WrappedType |
|
|
1084
|
+
unique_record: WrappedType | None = None
|
|
1435
1085
|
for result in results:
|
|
1436
1086
|
matches_all: bool = True
|
|
1437
1087
|
for field, value in secondary_identifiers.items():
|
|
@@ -1441,60 +1091,22 @@ class RecordHandler:
|
|
|
1441
1091
|
if matches_all:
|
|
1442
1092
|
# If a previous record in the results already matched all identifiers, then throw an exception.
|
|
1443
1093
|
if unique_record is not None:
|
|
1444
|
-
raise SapioException(f"More than one record of type {
|
|
1094
|
+
raise SapioException(f"More than one record of type {wrapper_type.get_wrapper_data_type_name()} "
|
|
1445
1095
|
f"encountered in system that matches all provided identifiers.")
|
|
1446
1096
|
unique_record = result
|
|
1447
1097
|
return unique_record
|
|
1448
1098
|
|
|
1449
1099
|
@staticmethod
|
|
1450
|
-
def __verify_data_type(
|
|
1100
|
+
def __verify_data_type(records: Iterable[DataRecord], wrapper_type: type[WrappedType]) -> None:
|
|
1451
1101
|
"""
|
|
1452
|
-
Throw an exception if the data type of the given
|
|
1102
|
+
Throw an exception if the data type of the given records and wrapper don't match.
|
|
1453
1103
|
"""
|
|
1454
1104
|
model_type: str = wrapper_type.get_wrapper_data_type_name()
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
@staticmethod
|
|
1464
|
-
def _spoof_child_load(model: RecordModel, child: RecordModel) -> None:
|
|
1465
|
-
"""
|
|
1466
|
-
Spoof the loading of a child record on a record model. This is useful for when you have records that you know
|
|
1467
|
-
are related but didn't use the relationship manager to load the relationship, which would make a webservice
|
|
1468
|
-
call.
|
|
1469
|
-
"""
|
|
1470
|
-
RecordHandler._spoof_children_load(model, [child])
|
|
1471
|
-
|
|
1472
|
-
@staticmethod
|
|
1473
|
-
def _spoof_children_load(model: RecordModel, children: list[RecordModel]) -> None:
|
|
1474
|
-
"""
|
|
1475
|
-
Spoof the loading of child records on a record model. This is useful for when you have records that you know
|
|
1476
|
-
are related but didn't use the relationship manager to load the relationship, which would make a webservice
|
|
1477
|
-
"""
|
|
1478
|
-
model: PyRecordModel = RecordModelInstanceManager.unwrap(model)
|
|
1479
|
-
child_dt: str = AliasUtil.to_singular_data_type_name(children)
|
|
1480
|
-
# noinspection PyProtectedMember
|
|
1481
|
-
model._mark_children_loaded(child_dt, RecordModelInstanceManager.unwrap_list(children))
|
|
1482
|
-
|
|
1483
|
-
@staticmethod
|
|
1484
|
-
def _spoof_parent_load(model: RecordModel, parent: RecordModel) -> None:
|
|
1485
|
-
"""
|
|
1486
|
-
Spoof the loading of a parent record on a record model. This is useful for when you have records that you know
|
|
1487
|
-
are related but didn't use the relationship manager to load the relationship, which would make a webservice
|
|
1488
|
-
"""
|
|
1489
|
-
RecordHandler._spoof_parents_load(model, [parent])
|
|
1490
|
-
|
|
1491
|
-
@staticmethod
|
|
1492
|
-
def _spoof_parents_load(model: RecordModel, parents: list[RecordModel]) -> None:
|
|
1493
|
-
"""
|
|
1494
|
-
Spoof the loading of parent records on a record model. This is useful for when you have records that you know
|
|
1495
|
-
are related but didn't use the relationship manager to load the relationship, which would make a webservice
|
|
1496
|
-
"""
|
|
1497
|
-
model: PyRecordModel = RecordModelInstanceManager.unwrap(model)
|
|
1498
|
-
parent_dt: str = AliasUtil.to_singular_data_type_name(parents)
|
|
1499
|
-
# noinspection PyProtectedMember
|
|
1500
|
-
model._mark_children_loaded(parent_dt, RecordModelInstanceManager.unwrap_list(parents))
|
|
1105
|
+
for record in records:
|
|
1106
|
+
record_type: str = record.data_type_name
|
|
1107
|
+
# Account for ELN data type records.
|
|
1108
|
+
if ElnBaseDataType.is_eln_type(record_type):
|
|
1109
|
+
record_type = ElnBaseDataType.get_base_type(record_type).data_type_name
|
|
1110
|
+
if record_type != model_type:
|
|
1111
|
+
raise SapioException(f"Data record of type {record_type} cannot be wrapped by the record model wrapper "
|
|
1112
|
+
f"of type {model_type}")
|