sapiopycommons 2024.8.28a315__py3-none-any.whl → 2024.8.29a317__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.
- sapiopycommons/callbacks/callback_util.py +37 -133
- sapiopycommons/datatype/attachment_util.py +10 -11
- sapiopycommons/eln/experiment_handler.py +48 -209
- sapiopycommons/eln/experiment_report_util.py +129 -33
- sapiopycommons/files/complex_data_loader.py +4 -5
- sapiopycommons/files/file_bridge.py +14 -15
- sapiopycommons/files/file_bridge_handler.py +5 -27
- sapiopycommons/files/file_data_handler.py +5 -2
- sapiopycommons/files/file_util.py +5 -38
- sapiopycommons/files/file_validator.py +11 -26
- sapiopycommons/files/file_writer.py +15 -44
- sapiopycommons/general/aliases.py +3 -147
- sapiopycommons/general/custom_report_util.py +32 -34
- sapiopycommons/general/popup_util.py +0 -17
- sapiopycommons/general/time_util.py +0 -40
- sapiopycommons/multimodal/multimodal_data.py +1 -0
- sapiopycommons/processtracking/endpoints.py +22 -22
- sapiopycommons/recordmodel/record_handler.py +77 -228
- sapiopycommons/rules/eln_rule_handler.py +25 -34
- sapiopycommons/rules/on_save_rule_handler.py +31 -34
- sapiopycommons/webhook/webhook_handlers.py +26 -90
- {sapiopycommons-2024.8.28a315.dist-info → sapiopycommons-2024.8.29a317.dist-info}/METADATA +1 -1
- sapiopycommons-2024.8.29a317.dist-info/RECORD +43 -0
- sapiopycommons/customreport/__init__.py +0 -0
- sapiopycommons/customreport/column_builder.py +0 -60
- sapiopycommons/customreport/custom_report_builder.py +0 -125
- sapiopycommons/customreport/term_builder.py +0 -299
- sapiopycommons/general/audit_log.py +0 -196
- sapiopycommons/general/sapio_links.py +0 -50
- sapiopycommons/webhook/webservice_handlers.py +0 -67
- sapiopycommons-2024.8.28a315.dist-info/RECORD +0 -50
- {sapiopycommons-2024.8.28a315.dist-info → sapiopycommons-2024.8.29a317.dist-info}/WHEEL +0 -0
- {sapiopycommons-2024.8.28a315.dist-info → sapiopycommons-2024.8.29a317.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
from collections.abc import Iterable
|
|
4
|
-
from
|
|
2
|
+
from typing import Any
|
|
5
3
|
|
|
6
4
|
from sapiopylib.rest.DataRecordManagerService import DataRecordManager
|
|
7
5
|
from sapiopylib.rest.User import SapioUser
|
|
@@ -9,7 +7,7 @@ from sapiopylib.rest.pojo.CustomReport import CustomReportCriteria, RawReportTer
|
|
|
9
7
|
from sapiopylib.rest.pojo.DataRecord import DataRecord
|
|
10
8
|
from sapiopylib.rest.pojo.DataRecordPaging import DataRecordPojoPageCriteria
|
|
11
9
|
from sapiopylib.rest.pojo.datatype.FieldDefinition import FieldType
|
|
12
|
-
from sapiopylib.rest.pojo.
|
|
10
|
+
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
13
11
|
from sapiopylib.rest.utils.autopaging import QueryDataRecordsAutoPager, QueryDataRecordByIdListAutoPager, \
|
|
14
12
|
QueryAllRecordsOfTypeAutoPager
|
|
15
13
|
from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
|
|
@@ -18,10 +16,8 @@ from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelMana
|
|
|
18
16
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType, WrappedRecordModel
|
|
19
17
|
from sapiopylib.rest.utils.recordmodel.RelationshipPath import RelationshipPath, RelationshipNode, \
|
|
20
18
|
RelationshipNodeType
|
|
21
|
-
from sapiopylib.rest.utils.recordmodel.ancestry import RecordModelAncestorManager
|
|
22
19
|
|
|
23
|
-
from sapiopycommons.general.aliases import RecordModel, SapioRecord, FieldMap
|
|
24
|
-
FieldIdentifierMap, FieldValue, UserIdentifier, FieldIdentifierKey
|
|
20
|
+
from sapiopycommons.general.aliases import RecordModel, SapioRecord, FieldMap
|
|
25
21
|
from sapiopycommons.general.custom_report_util import CustomReportUtil
|
|
26
22
|
from sapiopycommons.general.exceptions import SapioException
|
|
27
23
|
|
|
@@ -36,38 +32,16 @@ class RecordHandler:
|
|
|
36
32
|
rec_man: RecordModelManager
|
|
37
33
|
inst_man: RecordModelInstanceManager
|
|
38
34
|
rel_man: RecordModelRelationshipManager
|
|
39
|
-
an_man: RecordModelAncestorManager
|
|
40
|
-
|
|
41
|
-
__instances: WeakValueDictionary[SapioUser, RecordHandler] = WeakValueDictionary()
|
|
42
|
-
__initialized: bool
|
|
43
35
|
|
|
44
|
-
def
|
|
36
|
+
def __init__(self, context: SapioWebhookContext | SapioUser):
|
|
45
37
|
"""
|
|
46
38
|
:param context: The current webhook context or a user object to send requests from.
|
|
47
39
|
"""
|
|
48
|
-
user = AliasUtil.to_sapio_user(context)
|
|
49
|
-
obj = cls.__instances.get(user)
|
|
50
|
-
if not obj:
|
|
51
|
-
obj = object.__new__(cls)
|
|
52
|
-
obj.__initialized = False
|
|
53
|
-
cls.__instances[user] = obj
|
|
54
|
-
return obj
|
|
55
|
-
|
|
56
|
-
def __init__(self, context: UserIdentifier):
|
|
57
|
-
"""
|
|
58
|
-
:param context: The current webhook context or a user object to send requests from.
|
|
59
|
-
"""
|
|
60
|
-
self.user = AliasUtil.to_sapio_user(context)
|
|
61
|
-
if self.__initialized:
|
|
62
|
-
return
|
|
63
|
-
self.__initialized = True
|
|
64
|
-
|
|
65
40
|
self.user = context if isinstance(context, SapioUser) else context.user
|
|
66
41
|
self.dr_man = DataRecordManager(self.user)
|
|
67
42
|
self.rec_man = RecordModelManager(self.user)
|
|
68
43
|
self.inst_man = self.rec_man.instance_manager
|
|
69
44
|
self.rel_man = self.rec_man.relationship_manager
|
|
70
|
-
self.an_man = RecordModelAncestorManager(self.rec_man)
|
|
71
45
|
|
|
72
46
|
def wrap_model(self, record: DataRecord, wrapper_type: type[WrappedType]) -> WrappedType:
|
|
73
47
|
"""
|
|
@@ -77,7 +51,6 @@ class RecordHandler:
|
|
|
77
51
|
:param wrapper_type: The record model wrapper to use.
|
|
78
52
|
:return: The record model for the input.
|
|
79
53
|
"""
|
|
80
|
-
self.__verify_data_type([record], wrapper_type)
|
|
81
54
|
return self.inst_man.add_existing_record_of_type(record, wrapper_type)
|
|
82
55
|
|
|
83
56
|
def wrap_models(self, records: Iterable[DataRecord], wrapper_type: type[WrappedType]) -> list[WrappedType]:
|
|
@@ -88,11 +61,10 @@ class RecordHandler:
|
|
|
88
61
|
:param wrapper_type: The record model wrapper to use.
|
|
89
62
|
:return: The record models for the input.
|
|
90
63
|
"""
|
|
91
|
-
self.__verify_data_type(records, wrapper_type)
|
|
92
64
|
return self.inst_man.add_existing_records_of_type(list(records), wrapper_type)
|
|
93
65
|
|
|
94
|
-
def query_models(self, wrapper_type: type[WrappedType], field:
|
|
95
|
-
page_limit: int | None = None
|
|
66
|
+
def query_models(self, wrapper_type: type[WrappedType], field: str, value_list: Iterable[Any],
|
|
67
|
+
page_limit: int | None = None) -> list[WrappedType]:
|
|
96
68
|
"""
|
|
97
69
|
Shorthand for using the data record manager to query for a list of data records by field value
|
|
98
70
|
and then converting the results into a list of record models.
|
|
@@ -100,20 +72,14 @@ class RecordHandler:
|
|
|
100
72
|
:param wrapper_type: The record model wrapper to use.
|
|
101
73
|
:param field: The field to query on.
|
|
102
74
|
:param value_list: The values of the field to query on.
|
|
103
|
-
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages.
|
|
104
|
-
only functions if you set a page size or the platform enforces a page size.
|
|
105
|
-
:param page_size: The size of the pages to query. If None, the page size may be limited by the platform.
|
|
75
|
+
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages.
|
|
106
76
|
:return: The record models for the queried records.
|
|
107
77
|
"""
|
|
108
|
-
|
|
109
|
-
if page_size is not None:
|
|
110
|
-
criteria = DataRecordPojoPageCriteria(page_size=page_size)
|
|
111
|
-
return self.query_models_with_criteria(wrapper_type, field, value_list, criteria, page_limit)[0]
|
|
78
|
+
return self.query_models_with_criteria(wrapper_type, field, value_list, None, page_limit)[0]
|
|
112
79
|
|
|
113
|
-
def query_and_map_models(self, wrapper_type: type[WrappedType], field:
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
-> dict[FieldValue, list[WrappedType]]:
|
|
80
|
+
def query_and_map_models(self, wrapper_type: type[WrappedType], field: str, value_list: Iterable[Any],
|
|
81
|
+
page_limit: int | None = None, *, mapping_field: str | None = None) \
|
|
82
|
+
-> dict[Any, list[WrappedType]]:
|
|
117
83
|
"""
|
|
118
84
|
Shorthand for using query_models to search for records given values on a specific field and then using
|
|
119
85
|
map_by_field to turn the returned list into a dictionary mapping field values to records.
|
|
@@ -121,21 +87,17 @@ class RecordHandler:
|
|
|
121
87
|
:param wrapper_type: The record model wrapper to use.
|
|
122
88
|
:param field: The field to query and map on.
|
|
123
89
|
:param value_list: The values of the field to query on.
|
|
124
|
-
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages.
|
|
125
|
-
only functions if you set a page size or the platform enforces a page size.
|
|
126
|
-
:param page_size: The size of the pages to query. If None, the page size may be limited by the platform.
|
|
90
|
+
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages.
|
|
127
91
|
:param mapping_field: If provided, use this field to map against instead of the field that was queried on.
|
|
128
92
|
:return: The record models for the queried records mapped by field values to the records with that value.
|
|
129
93
|
"""
|
|
130
94
|
if mapping_field is None:
|
|
131
95
|
mapping_field = field
|
|
132
|
-
return self.map_by_field(self.query_models(wrapper_type, field, value_list, page_limit,
|
|
133
|
-
mapping_field)
|
|
96
|
+
return self.map_by_field(self.query_models(wrapper_type, field, value_list, page_limit), mapping_field)
|
|
134
97
|
|
|
135
|
-
def query_and_unique_map_models(self, wrapper_type: type[WrappedType], field:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
-> dict[FieldValue, WrappedType]:
|
|
98
|
+
def query_and_unique_map_models(self, wrapper_type: type[WrappedType], field: str, value_list: Iterable[Any],
|
|
99
|
+
page_limit: int | None = None, *, mapping_field: str | None = None) \
|
|
100
|
+
-> dict[Any, WrappedType]:
|
|
139
101
|
"""
|
|
140
102
|
Shorthand for using query_models to search for records given values on a specific field and then using
|
|
141
103
|
map_by_unique_field to turn the returned list into a dictionary mapping field values to records.
|
|
@@ -144,19 +106,15 @@ class RecordHandler:
|
|
|
144
106
|
:param wrapper_type: The record model wrapper to use.
|
|
145
107
|
:param field: The field to query and map on.
|
|
146
108
|
:param value_list: The values of the field to query on.
|
|
147
|
-
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages.
|
|
148
|
-
only functions if you set a page size or the platform enforces a page size.
|
|
149
|
-
:param page_size: The size of the pages to query. If None, the page size may be limited by the platform.
|
|
109
|
+
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages.
|
|
150
110
|
:param mapping_field: If provided, use this field to map against instead of the field that was queried on.
|
|
151
111
|
:return: The record models for the queried records mapped by field values to the record with that value.
|
|
152
112
|
"""
|
|
153
113
|
if mapping_field is None:
|
|
154
114
|
mapping_field = field
|
|
155
|
-
return self.map_by_unique_field(self.query_models(wrapper_type, field, value_list, page_limit,
|
|
156
|
-
mapping_field)
|
|
115
|
+
return self.map_by_unique_field(self.query_models(wrapper_type, field, value_list, page_limit), mapping_field)
|
|
157
116
|
|
|
158
|
-
def query_models_with_criteria(self, wrapper_type: type[WrappedType], field:
|
|
159
|
-
value_list: Iterable[FieldValue],
|
|
117
|
+
def query_models_with_criteria(self, wrapper_type: type[WrappedType], field: str, value_list: Iterable[Any],
|
|
160
118
|
paging_criteria: DataRecordPojoPageCriteria | None = None,
|
|
161
119
|
page_limit: int | None = None) \
|
|
162
120
|
-> tuple[list[WrappedType], DataRecordPojoPageCriteria]:
|
|
@@ -169,33 +127,26 @@ class RecordHandler:
|
|
|
169
127
|
:param value_list: The values of the field to query on.
|
|
170
128
|
:param paging_criteria: The paging criteria to start the query with.
|
|
171
129
|
:param page_limit: The maximum number of pages to query from the starting criteria. If None, exhausts all
|
|
172
|
-
possible pages.
|
|
173
|
-
enforces a page size.
|
|
130
|
+
possible pages.
|
|
174
131
|
:return: The record models for the queried records and the final paging criteria.
|
|
175
132
|
"""
|
|
176
133
|
dt: str = wrapper_type.get_wrapper_data_type_name()
|
|
177
|
-
field: str = AliasUtil.to_data_field_name(field)
|
|
178
134
|
pager = QueryDataRecordsAutoPager(dt, field, list(value_list), self.user, paging_criteria)
|
|
179
135
|
pager.max_page = page_limit
|
|
180
136
|
return self.wrap_models(pager.get_all_at_once(), wrapper_type), pager.next_page_criteria
|
|
181
137
|
|
|
182
138
|
def query_models_by_id(self, wrapper_type: type[WrappedType], ids: Iterable[int],
|
|
183
|
-
page_limit: int | None = None
|
|
139
|
+
page_limit: int | None = None) -> list[WrappedType]:
|
|
184
140
|
"""
|
|
185
141
|
Shorthand for using the data record manager to query for a list of data records by record ID
|
|
186
142
|
and then converting the results into a list of record models.
|
|
187
143
|
|
|
188
144
|
:param wrapper_type: The record model wrapper to use.
|
|
189
145
|
:param ids: The list of record IDs to query.
|
|
190
|
-
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages.
|
|
191
|
-
only functions if you set a page size or the platform enforces a page size.
|
|
192
|
-
:param page_size: The size of the pages to query. If None, the page size may be limited by the platform.
|
|
146
|
+
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages.
|
|
193
147
|
:return: The record models for the queried records.
|
|
194
148
|
"""
|
|
195
|
-
|
|
196
|
-
if page_size is not None:
|
|
197
|
-
criteria = DataRecordPojoPageCriteria(page_size=page_size)
|
|
198
|
-
return self.query_models_by_id_with_criteria(wrapper_type, ids, criteria, page_limit)[0]
|
|
149
|
+
return self.query_models_by_id_with_criteria(wrapper_type, ids, None, page_limit)[0]
|
|
199
150
|
|
|
200
151
|
def query_models_by_id_with_criteria(self, wrapper_type: type[WrappedType], ids: Iterable[int],
|
|
201
152
|
paging_criteria: DataRecordPojoPageCriteria | None = None,
|
|
@@ -209,8 +160,7 @@ class RecordHandler:
|
|
|
209
160
|
:param ids: The list of record IDs to query.
|
|
210
161
|
:param paging_criteria: The paging criteria to start the query with.
|
|
211
162
|
:param page_limit: The maximum number of pages to query from the starting criteria. If None, exhausts all
|
|
212
|
-
possible pages.
|
|
213
|
-
enforces a page size.
|
|
163
|
+
possible pages.
|
|
214
164
|
:return: The record models for the queried records and the final paging criteria.
|
|
215
165
|
"""
|
|
216
166
|
dt: str = wrapper_type.get_wrapper_data_type_name()
|
|
@@ -218,38 +168,16 @@ class RecordHandler:
|
|
|
218
168
|
pager.max_page = page_limit
|
|
219
169
|
return self.wrap_models(pager.get_all_at_once(), wrapper_type), pager.next_page_criteria
|
|
220
170
|
|
|
221
|
-
def
|
|
222
|
-
page_limit: int | None = None, page_size: int | None = None) \
|
|
223
|
-
-> dict[int, WrappedType]:
|
|
224
|
-
"""
|
|
225
|
-
Shorthand for using the data record manager to query for a list of data records by record ID
|
|
226
|
-
and then converting the results into a dictionary of record ID to the record model for that ID.
|
|
227
|
-
|
|
228
|
-
:param wrapper_type: The record model wrapper to use.
|
|
229
|
-
:param ids: The list of record IDs to query.
|
|
230
|
-
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages. This parameter
|
|
231
|
-
only functions if you set a page size or the platform enforces a page size.
|
|
232
|
-
:param page_size: The size of the pages to query. If None, the page size may be limited by the platform.
|
|
233
|
-
:return: The record models for the queried records mapped in a dictionary by their record ID.
|
|
234
|
-
"""
|
|
235
|
-
return {x.record_id: x for x in self.query_models_by_id(wrapper_type, ids, page_limit, page_size)}
|
|
236
|
-
|
|
237
|
-
def query_all_models(self, wrapper_type: type[WrappedType], page_limit: int | None = None,
|
|
238
|
-
page_size: int | None = None) -> list[WrappedType]:
|
|
171
|
+
def query_all_models(self, wrapper_type: type[WrappedType], page_limit: int | None = None) -> list[WrappedType]:
|
|
239
172
|
"""
|
|
240
173
|
Shorthand for using the data record manager to query for all data records of a given type
|
|
241
174
|
and then converting the results into a list of record models.
|
|
242
175
|
|
|
243
176
|
:param wrapper_type: The record model wrapper to use.
|
|
244
|
-
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages.
|
|
245
|
-
only functions if you set a page size or the platform enforces a page size.
|
|
246
|
-
:param page_size: The size of the pages to query. If None, the page size may be limited by the platform.
|
|
177
|
+
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages.
|
|
247
178
|
:return: The record models for the queried records.
|
|
248
179
|
"""
|
|
249
|
-
|
|
250
|
-
if page_size is not None:
|
|
251
|
-
criteria = DataRecordPojoPageCriteria(page_size=page_size)
|
|
252
|
-
return self.query_all_models_with_criteria(wrapper_type, criteria, page_limit)[0]
|
|
180
|
+
return self.query_all_models_with_criteria(wrapper_type, None, page_limit)[0]
|
|
253
181
|
|
|
254
182
|
def query_all_models_with_criteria(self, wrapper_type: type[WrappedType],
|
|
255
183
|
paging_criteria: DataRecordPojoPageCriteria | None = None,
|
|
@@ -262,8 +190,7 @@ class RecordHandler:
|
|
|
262
190
|
:param wrapper_type: The record model wrapper to use.
|
|
263
191
|
:param paging_criteria: The paging criteria to start the query with.
|
|
264
192
|
:param page_limit: The maximum number of pages to query from the starting criteria. If None, exhausts all
|
|
265
|
-
possible pages.
|
|
266
|
-
enforces a page size.
|
|
193
|
+
possible pages.
|
|
267
194
|
:return: The record models for the queried records and the final paging criteria.
|
|
268
195
|
"""
|
|
269
196
|
dt: str = wrapper_type.get_wrapper_data_type_name()
|
|
@@ -273,7 +200,7 @@ class RecordHandler:
|
|
|
273
200
|
|
|
274
201
|
def query_models_by_report(self, wrapper_type: type[WrappedType],
|
|
275
202
|
report_name: str | RawReportTerm | CustomReportCriteria,
|
|
276
|
-
filters: dict[
|
|
203
|
+
filters: dict[str, Iterable[Any]] | None = None,
|
|
277
204
|
page_limit: int | None = None,
|
|
278
205
|
page_size: int | None = None,
|
|
279
206
|
page_number: int | None = None) -> list[WrappedType]:
|
|
@@ -301,11 +228,11 @@ class RecordHandler:
|
|
|
301
228
|
:return: The record models for the queried records that matched the given report.
|
|
302
229
|
"""
|
|
303
230
|
if isinstance(report_name, str):
|
|
304
|
-
results: list[dict[str,
|
|
305
|
-
|
|
231
|
+
results: list[dict[str, Any]] = CustomReportUtil.run_system_report(self.user, report_name, filters,
|
|
232
|
+
page_limit, page_size, page_number)
|
|
306
233
|
elif isinstance(report_name, RawReportTerm):
|
|
307
|
-
results: list[dict[str,
|
|
308
|
-
|
|
234
|
+
results: list[dict[str, Any]] = CustomReportUtil.run_quick_report(self.user, report_name, filters,
|
|
235
|
+
page_limit, page_size, page_number)
|
|
309
236
|
elif isinstance(report_name, CustomReportCriteria):
|
|
310
237
|
dt: str = wrapper_type.get_wrapper_data_type_name()
|
|
311
238
|
# Ensure that the root data type is the one we're looking for.
|
|
@@ -316,8 +243,8 @@ class RecordHandler:
|
|
|
316
243
|
# Enforce that the given custom report has a record ID column.
|
|
317
244
|
if not any([x.data_type_name == dt and x.data_field_name == "RecordId" for x in report_name.column_list]):
|
|
318
245
|
report_name.column_list.append(ReportColumn(dt, "RecordId", FieldType.LONG))
|
|
319
|
-
results: list[dict[str,
|
|
320
|
-
|
|
246
|
+
results: list[dict[str, Any]] = CustomReportUtil.run_custom_report(self.user, report_name, filters,
|
|
247
|
+
page_limit, page_size, page_number)
|
|
321
248
|
else:
|
|
322
249
|
raise SapioException("Unrecognized report object.")
|
|
323
250
|
|
|
@@ -346,8 +273,7 @@ class RecordHandler:
|
|
|
346
273
|
"""
|
|
347
274
|
return self.inst_man.add_new_records_of_type(num, wrapper_type)
|
|
348
275
|
|
|
349
|
-
def add_models_with_data(self, wrapper_type: type[WrappedType], fields: list[
|
|
350
|
-
-> list[WrappedType]:
|
|
276
|
+
def add_models_with_data(self, wrapper_type: type[WrappedType], fields: list[FieldMap]) -> list[WrappedType]:
|
|
351
277
|
"""
|
|
352
278
|
Shorthand for using the instance manager to add new models of the given type, and then initializing all those
|
|
353
279
|
models with the given fields.
|
|
@@ -357,14 +283,13 @@ class RecordHandler:
|
|
|
357
283
|
:return: The newly added record models with the provided fields set. The records will be in the same order as
|
|
358
284
|
the fields in the fields list.
|
|
359
285
|
"""
|
|
360
|
-
fields: list[FieldMap] = AliasUtil.to_data_field_names_list_dict(fields)
|
|
361
286
|
models: list[WrappedType] = self.add_models(wrapper_type, len(fields))
|
|
362
287
|
for model, field_list in zip(models, fields):
|
|
363
288
|
model.set_field_values(field_list)
|
|
364
289
|
return models
|
|
365
290
|
|
|
366
|
-
def find_or_add_model(self, wrapper_type: type[WrappedType], primary_identifier:
|
|
367
|
-
|
|
291
|
+
def find_or_add_model(self, wrapper_type: type[WrappedType], primary_identifier: str, id_value: Any,
|
|
292
|
+
secondary_identifiers: FieldMap | None = None) -> WrappedType:
|
|
368
293
|
"""
|
|
369
294
|
Find a unique record that matches the given field values. If no such records exist, add a record model to the
|
|
370
295
|
cache with the identifying fields set to the desired values. This record will be created in the system when
|
|
@@ -387,8 +312,6 @@ class RecordHandler:
|
|
|
387
312
|
if secondary_identifiers is None:
|
|
388
313
|
secondary_identifiers = {}
|
|
389
314
|
|
|
390
|
-
primary_identifier: str = AliasUtil.to_data_field_name(primary_identifier)
|
|
391
|
-
secondary_identifiers: FieldMap = AliasUtil.to_data_field_names_dict(secondary_identifiers)
|
|
392
315
|
unique_record: WrappedType | None = self.__find_model(wrapper_type, primary_identifier, id_value,
|
|
393
316
|
secondary_identifiers)
|
|
394
317
|
# If a unique record matched the identifiers, return it.
|
|
@@ -415,7 +338,7 @@ class RecordHandler:
|
|
|
415
338
|
dt: str = wrapper_type.get_wrapper_data_type_name()
|
|
416
339
|
return self.wrap_models(self.dr_man.add_data_records(dt, num), wrapper_type)
|
|
417
340
|
|
|
418
|
-
def create_models_with_data(self, wrapper_type: type[WrappedType], fields: list[
|
|
341
|
+
def create_models_with_data(self, wrapper_type: type[WrappedType], fields: list[FieldMap]) \
|
|
419
342
|
-> list[WrappedType]:
|
|
420
343
|
"""
|
|
421
344
|
Shorthand for creating new records via the data record manager with field data to initialize the records with
|
|
@@ -429,12 +352,10 @@ class RecordHandler:
|
|
|
429
352
|
:return: The newly created record models.
|
|
430
353
|
"""
|
|
431
354
|
dt: str = wrapper_type.get_wrapper_data_type_name()
|
|
432
|
-
fields: list[FieldMap] = AliasUtil.to_data_field_names_list_dict(fields)
|
|
433
355
|
return self.wrap_models(self.dr_man.add_data_records_with_data(dt, fields), wrapper_type)
|
|
434
356
|
|
|
435
|
-
def find_or_create_model(self, wrapper_type: type[WrappedType], primary_identifier:
|
|
436
|
-
|
|
437
|
-
-> WrappedType:
|
|
357
|
+
def find_or_create_model(self, wrapper_type: type[WrappedType], primary_identifier: str, id_value: Any,
|
|
358
|
+
secondary_identifiers: FieldMap | None = None) -> WrappedType:
|
|
438
359
|
"""
|
|
439
360
|
Find a unique record that matches the given field values. If no such records exist, create one with the
|
|
440
361
|
identifying fields set to the desired values. If more than one record with the identifying values exists,
|
|
@@ -458,8 +379,6 @@ class RecordHandler:
|
|
|
458
379
|
if secondary_identifiers is None:
|
|
459
380
|
secondary_identifiers = {}
|
|
460
381
|
|
|
461
|
-
primary_identifier: str = AliasUtil.to_data_field_name(primary_identifier)
|
|
462
|
-
secondary_identifiers: FieldMap = AliasUtil.to_data_field_names_dict(secondary_identifiers)
|
|
463
382
|
unique_record: WrappedType | None = self.__find_model(wrapper_type, primary_identifier, id_value,
|
|
464
383
|
secondary_identifiers)
|
|
465
384
|
# If a unique record matched the identifiers, return it.
|
|
@@ -578,7 +497,7 @@ class RecordHandler:
|
|
|
578
497
|
|
|
579
498
|
@staticmethod
|
|
580
499
|
def map_by_child(models: Iterable[RecordModel], child_type: type[WrappedType]) \
|
|
581
|
-
-> dict[WrappedType, RecordModel]:
|
|
500
|
+
-> dict[WrappedType, list[RecordModel]]:
|
|
582
501
|
"""
|
|
583
502
|
Take a list of record models and map them by their children. Essentially an inversion of map_to_child.
|
|
584
503
|
If two records share the same child, an exception will be thrown. The children must already be loaded.
|
|
@@ -619,7 +538,7 @@ class RecordHandler:
|
|
|
619
538
|
return by_children
|
|
620
539
|
|
|
621
540
|
@staticmethod
|
|
622
|
-
def map_to_forward_side_link(models: Iterable[WrappedRecordModel], field_name:
|
|
541
|
+
def map_to_forward_side_link(models: Iterable[WrappedRecordModel], field_name: str,
|
|
623
542
|
side_link_type: type[WrappedType]) -> dict[WrappedRecordModel, WrappedType]:
|
|
624
543
|
"""
|
|
625
544
|
Map a list of record models to their forward side link. The forward side link must already be loaded.
|
|
@@ -630,14 +549,13 @@ class RecordHandler:
|
|
|
630
549
|
:return: A dict[ModelType, SlideLink]. If an input model doesn't have a forward side link of the given type,
|
|
631
550
|
then it will map to None.
|
|
632
551
|
"""
|
|
633
|
-
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
634
552
|
return_dict: dict[WrappedRecordModel, WrappedType] = {}
|
|
635
553
|
for model in models:
|
|
636
554
|
return_dict[model] = model.get_forward_side_link(field_name, side_link_type)
|
|
637
555
|
return return_dict
|
|
638
556
|
|
|
639
557
|
@staticmethod
|
|
640
|
-
def map_by_forward_side_links(models: Iterable[WrappedRecordModel], field_name:
|
|
558
|
+
def map_by_forward_side_links(models: Iterable[WrappedRecordModel], field_name: str,
|
|
641
559
|
side_link_type: type[WrappedType]) -> dict[WrappedType, list[WrappedRecordModel]]:
|
|
642
560
|
"""
|
|
643
561
|
Take a list of record models and map them by their forward side link. Essentially an inversion of
|
|
@@ -650,7 +568,6 @@ class RecordHandler:
|
|
|
650
568
|
:return: A dict[SideLink, list[ModelType]]. If an input model doesn't have a forward side link of the given type
|
|
651
569
|
pointing to it, then it will not be in the resulting dictionary.
|
|
652
570
|
"""
|
|
653
|
-
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
654
571
|
to_side_link: dict[WrappedRecordModel, WrappedType] = RecordHandler\
|
|
655
572
|
.map_to_forward_side_link(models, field_name, side_link_type)
|
|
656
573
|
by_side_link: dict[WrappedType, list[WrappedRecordModel]] = {}
|
|
@@ -661,7 +578,7 @@ class RecordHandler:
|
|
|
661
578
|
return by_side_link
|
|
662
579
|
|
|
663
580
|
@staticmethod
|
|
664
|
-
def map_by_forward_side_link(models: Iterable[WrappedRecordModel], field_name:
|
|
581
|
+
def map_by_forward_side_link(models: Iterable[WrappedRecordModel], field_name: str,
|
|
665
582
|
side_link_type: type[WrappedType]) -> dict[WrappedType, WrappedRecordModel]:
|
|
666
583
|
"""
|
|
667
584
|
Take a list of record models and map them by their forward side link. Essentially an inversion of
|
|
@@ -674,7 +591,6 @@ class RecordHandler:
|
|
|
674
591
|
:return: A dict[SideLink, ModelType]. If an input model doesn't have a forward side link of the given type
|
|
675
592
|
pointing to it, then it will not be in the resulting dictionary.
|
|
676
593
|
"""
|
|
677
|
-
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
678
594
|
to_side_link: dict[WrappedRecordModel, WrappedType] = RecordHandler\
|
|
679
595
|
.map_to_forward_side_link(models, field_name, side_link_type)
|
|
680
596
|
by_side_link: dict[WrappedType, WrappedRecordModel] = {}
|
|
@@ -688,7 +604,7 @@ class RecordHandler:
|
|
|
688
604
|
return by_side_link
|
|
689
605
|
|
|
690
606
|
@staticmethod
|
|
691
|
-
def map_to_reverse_side_links(models: Iterable[WrappedRecordModel], field_name:
|
|
607
|
+
def map_to_reverse_side_links(models: Iterable[WrappedRecordModel], field_name: str,
|
|
692
608
|
side_link_type: type[WrappedType]) -> dict[WrappedRecordModel, list[WrappedType]]:
|
|
693
609
|
"""
|
|
694
610
|
Map a list of record models to a list reverse side links of a given type. The reverse side links must already
|
|
@@ -701,14 +617,13 @@ class RecordHandler:
|
|
|
701
617
|
:return: A dict[ModelType, list[SideLink]]. If an input model doesn't have reverse side links of the given type,
|
|
702
618
|
then it will map to an empty list.
|
|
703
619
|
"""
|
|
704
|
-
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
705
620
|
return_dict: dict[WrappedRecordModel, list[WrappedType]] = {}
|
|
706
621
|
for model in models:
|
|
707
622
|
return_dict[model] = model.get_reverse_side_link(field_name, side_link_type)
|
|
708
623
|
return return_dict
|
|
709
624
|
|
|
710
625
|
@staticmethod
|
|
711
|
-
def map_to_reverse_side_link(models: Iterable[WrappedRecordModel], field_name:
|
|
626
|
+
def map_to_reverse_side_link(models: Iterable[WrappedRecordModel], field_name: str,
|
|
712
627
|
side_link_type: type[WrappedType]) -> dict[WrappedRecordModel, WrappedType]:
|
|
713
628
|
"""
|
|
714
629
|
Map a list of record models to the reverse side link of a given type. If a given record has more than one
|
|
@@ -721,7 +636,6 @@ class RecordHandler:
|
|
|
721
636
|
:return: A dict[ModelType, SideLink]. If an input model doesn't have reverse side links of the given type,
|
|
722
637
|
then it will map to None.
|
|
723
638
|
"""
|
|
724
|
-
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
725
639
|
return_dict: dict[WrappedRecordModel, WrappedType] = {}
|
|
726
640
|
for model in models:
|
|
727
641
|
links: list[WrappedType] = model.get_reverse_side_link(field_name, side_link_type)
|
|
@@ -732,7 +646,7 @@ class RecordHandler:
|
|
|
732
646
|
return return_dict
|
|
733
647
|
|
|
734
648
|
@staticmethod
|
|
735
|
-
def map_by_reverse_side_links(models: Iterable[WrappedRecordModel], field_name:
|
|
649
|
+
def map_by_reverse_side_links(models: Iterable[WrappedRecordModel], field_name: str,
|
|
736
650
|
side_link_type: type[WrappedType]) -> dict[WrappedType, list[WrappedRecordModel]]:
|
|
737
651
|
"""
|
|
738
652
|
Take a list of record models and map them by their reverse side links. Essentially an inversion of
|
|
@@ -746,7 +660,6 @@ class RecordHandler:
|
|
|
746
660
|
:return: A dict[SideLink, list[ModelType]]. If an input model doesn't have reverse side links of the given type
|
|
747
661
|
pointing to it, then it will not be in the resulting dictionary.
|
|
748
662
|
"""
|
|
749
|
-
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
750
663
|
to_side_links: dict[WrappedRecordModel, list[WrappedType]] = RecordHandler\
|
|
751
664
|
.map_to_reverse_side_links(models, field_name, side_link_type)
|
|
752
665
|
by_side_links: dict[WrappedType, list[WrappedRecordModel]] = {}
|
|
@@ -756,7 +669,7 @@ class RecordHandler:
|
|
|
756
669
|
return by_side_links
|
|
757
670
|
|
|
758
671
|
@staticmethod
|
|
759
|
-
def map_by_reverse_side_link(models: Iterable[WrappedRecordModel], field_name:
|
|
672
|
+
def map_by_reverse_side_link(models: Iterable[WrappedRecordModel], field_name: str,
|
|
760
673
|
side_link_type: type[WrappedType]) -> dict[WrappedType, WrappedRecordModel]:
|
|
761
674
|
"""
|
|
762
675
|
Take a list of record models and map them by their reverse side link. Essentially an inversion of
|
|
@@ -770,7 +683,6 @@ class RecordHandler:
|
|
|
770
683
|
:return: A dict[SideLink, ModelType]. If an input model doesn't have a reverse side link of the given type
|
|
771
684
|
pointing to it, then it will not be in the resulting dictionary.
|
|
772
685
|
"""
|
|
773
|
-
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
774
686
|
to_side_link: dict[WrappedRecordModel, WrappedType] = RecordHandler\
|
|
775
687
|
.map_to_reverse_side_link(models, field_name, side_link_type)
|
|
776
688
|
by_side_link: dict[WrappedType, WrappedRecordModel] = {}
|
|
@@ -797,8 +709,7 @@ class RecordHandler:
|
|
|
797
709
|
return ret_dict
|
|
798
710
|
|
|
799
711
|
@staticmethod
|
|
800
|
-
def map_by_field(models: Iterable[SapioRecord], field_name:
|
|
801
|
-
-> dict[FieldValue, list[SapioRecord]]:
|
|
712
|
+
def map_by_field(models: Iterable[SapioRecord], field_name: str) -> dict[Any, list[SapioRecord]]:
|
|
802
713
|
"""
|
|
803
714
|
Map the given records by one of their fields. If any two records share the same field value, they'll appear in
|
|
804
715
|
the same value list.
|
|
@@ -807,16 +718,14 @@ class RecordHandler:
|
|
|
807
718
|
:param field_name: The field name to map against.
|
|
808
719
|
:return: A dict mapping field values to the records with that value.
|
|
809
720
|
"""
|
|
810
|
-
|
|
811
|
-
ret_dict: dict[FieldValue, list[SapioRecord]] = {}
|
|
721
|
+
ret_dict: dict[Any, list[SapioRecord]] = {}
|
|
812
722
|
for model in models:
|
|
813
|
-
val:
|
|
723
|
+
val: Any = model.get_field_value(field_name)
|
|
814
724
|
ret_dict.setdefault(val, []).append(model)
|
|
815
725
|
return ret_dict
|
|
816
726
|
|
|
817
727
|
@staticmethod
|
|
818
|
-
def map_by_unique_field(models: Iterable[SapioRecord], field_name:
|
|
819
|
-
-> dict[FieldValue, SapioRecord]:
|
|
728
|
+
def map_by_unique_field(models: Iterable[SapioRecord], field_name: str) -> dict[Any, SapioRecord]:
|
|
820
729
|
"""
|
|
821
730
|
Uniquely map the given records by one of their fields. If any two records share the same field value, throws
|
|
822
731
|
an exception.
|
|
@@ -825,17 +734,16 @@ class RecordHandler:
|
|
|
825
734
|
:param field_name: The field name to map against.
|
|
826
735
|
:return: A dict mapping field values to the record with that value.
|
|
827
736
|
"""
|
|
828
|
-
|
|
829
|
-
ret_dict: dict[FieldValue, SapioRecord] = {}
|
|
737
|
+
ret_dict: dict[Any, SapioRecord] = {}
|
|
830
738
|
for model in models:
|
|
831
|
-
val:
|
|
739
|
+
val: Any = model.get_field_value(field_name)
|
|
832
740
|
if val in ret_dict:
|
|
833
741
|
raise SapioException(f"Value {val} encountered more than once in models list.")
|
|
834
742
|
ret_dict.update({val: model})
|
|
835
743
|
return ret_dict
|
|
836
744
|
|
|
837
745
|
@staticmethod
|
|
838
|
-
def sum_of_field(models: Iterable[SapioRecord], field_name:
|
|
746
|
+
def sum_of_field(models: Iterable[SapioRecord], field_name: str) -> float:
|
|
839
747
|
"""
|
|
840
748
|
Sum up the numeric value of a given field across all input models. Excepts that all given models have a value.
|
|
841
749
|
If the field is an integer field, the value will be converted to a float.
|
|
@@ -844,14 +752,13 @@ class RecordHandler:
|
|
|
844
752
|
:param field_name: The name of the numeric field to sum.
|
|
845
753
|
:return: The sum of the field values for the collection of models.
|
|
846
754
|
"""
|
|
847
|
-
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
848
755
|
field_sum: float = 0
|
|
849
756
|
for model in models:
|
|
850
757
|
field_sum += float(model.get_field_value(field_name))
|
|
851
758
|
return field_sum
|
|
852
759
|
|
|
853
760
|
@staticmethod
|
|
854
|
-
def mean_of_field(models: Iterable[SapioRecord], field_name:
|
|
761
|
+
def mean_of_field(models: Iterable[SapioRecord], field_name: str) -> float:
|
|
855
762
|
"""
|
|
856
763
|
Calculate the mean of the numeric value of a given field across all input models. Excepts that all given models
|
|
857
764
|
have a value. If the field is an integer field, the value will be converted to a float.
|
|
@@ -892,8 +799,8 @@ class RecordHandler:
|
|
|
892
799
|
return oldest
|
|
893
800
|
|
|
894
801
|
@staticmethod
|
|
895
|
-
def values_to_field_maps(field_name:
|
|
896
|
-
|
|
802
|
+
def values_to_field_maps(field_name: str, values: Iterable[Any], existing_fields: list[FieldMap] | None = None) \
|
|
803
|
+
-> list[FieldMap]:
|
|
897
804
|
"""
|
|
898
805
|
Add a list of values for a specific field to a list of dictionaries pairing each value to that field name.
|
|
899
806
|
|
|
@@ -904,8 +811,6 @@ class RecordHandler:
|
|
|
904
811
|
:return: A fields map list that contains the given values mapped by the given field name.
|
|
905
812
|
"""
|
|
906
813
|
# Update the existing fields map list if one is given.
|
|
907
|
-
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
908
|
-
existing_fields: list[FieldMap] = AliasUtil.to_data_field_names_list_dict(existing_fields)
|
|
909
814
|
if existing_fields:
|
|
910
815
|
values = list(values)
|
|
911
816
|
# The number of new values must match the length of the existing fields list.
|
|
@@ -926,6 +831,8 @@ class RecordHandler:
|
|
|
926
831
|
path, if any. The hierarchy must be linear (1:1 relationship between data types at every step) and the
|
|
927
832
|
relationship path must already be loaded.
|
|
928
833
|
|
|
834
|
+
Currently, the relationship path may only contain parent/child nodes.
|
|
835
|
+
|
|
929
836
|
:param models: A list of record models.
|
|
930
837
|
:param path: The relationship path to follow.
|
|
931
838
|
:param wrapper_type: The record model wrapper to use.
|
|
@@ -936,44 +843,15 @@ class RecordHandler:
|
|
|
936
843
|
# PR-46832: Update path traversal to account for changes to RelationshipPath in Sapiopylib.
|
|
937
844
|
path: list[RelationshipNode] = path.path
|
|
938
845
|
for model in models:
|
|
939
|
-
current: PyRecordModel
|
|
846
|
+
current: PyRecordModel = model if isinstance(model, PyRecordModel) else model.backing_model
|
|
940
847
|
for node in path:
|
|
941
|
-
|
|
942
|
-
direction: RelationshipNodeType = node.direction
|
|
848
|
+
direction = node.direction
|
|
943
849
|
if current is None:
|
|
944
850
|
break
|
|
945
851
|
if direction == RelationshipNodeType.CHILD:
|
|
946
|
-
current = current.get_child_of_type(
|
|
852
|
+
current = current.get_child_of_type(node.data_type_name)
|
|
947
853
|
elif direction == RelationshipNodeType.PARENT:
|
|
948
|
-
current = current.get_parent_of_type(
|
|
949
|
-
elif direction == RelationshipNodeType.ANCESTOR:
|
|
950
|
-
ancestors: list[PyRecordModel] = list(self.an_man.get_ancestors_of_type(current, data_type))
|
|
951
|
-
if not ancestors:
|
|
952
|
-
current = None
|
|
953
|
-
elif len(ancestors) > 1:
|
|
954
|
-
raise SapioException(f"Hierarchy contains multiple ancestors of type {data_type}.")
|
|
955
|
-
else:
|
|
956
|
-
current = ancestors[0]
|
|
957
|
-
elif direction == RelationshipNodeType.DESCENDANT:
|
|
958
|
-
descendants: list[PyRecordModel] = list(self.an_man.get_descendant_of_type(current, data_type))
|
|
959
|
-
if not descendants:
|
|
960
|
-
current = None
|
|
961
|
-
elif len(descendants) > 1:
|
|
962
|
-
raise SapioException(f"Hierarchy contains multiple descendants of type {data_type}.")
|
|
963
|
-
else:
|
|
964
|
-
current = descendants[0]
|
|
965
|
-
elif direction == RelationshipNodeType.FORWARD_SIDE_LINK:
|
|
966
|
-
current = current.get_forward_side_link(node.data_field_name)
|
|
967
|
-
elif direction == RelationshipNodeType.REVERSE_SIDE_LINK:
|
|
968
|
-
field_name: str = node.data_field_name
|
|
969
|
-
reverse_links: list[PyRecordModel] = current.get_reverse_side_link(field_name, data_type)
|
|
970
|
-
if not reverse_links:
|
|
971
|
-
current = None
|
|
972
|
-
elif len(reverse_links) > 1:
|
|
973
|
-
raise SapioException(f"Hierarchy contains multiple reverse links of type {data_type} on field "
|
|
974
|
-
f"{field_name}.")
|
|
975
|
-
else:
|
|
976
|
-
current = reverse_links[0]
|
|
854
|
+
current = current.get_parent_of_type(node.data_type_name)
|
|
977
855
|
else:
|
|
978
856
|
raise SapioException("Unsupported path direction.")
|
|
979
857
|
ret_dict.update({model: self.inst_man.wrap(current, wrapper_type) if current else None})
|
|
@@ -986,6 +864,8 @@ class RecordHandler:
|
|
|
986
864
|
path, if any. The hierarchy may be non-linear (1:Many relationships between data types are allowed) and the
|
|
987
865
|
relationship path must already be loaded.
|
|
988
866
|
|
|
867
|
+
Currently, the relationship path may only contain parent/child nodes.
|
|
868
|
+
|
|
989
869
|
:param models: A list of record models.
|
|
990
870
|
:param path: The relationship path to follow.
|
|
991
871
|
:param wrapper_type: The record model wrapper to use.
|
|
@@ -1000,23 +880,14 @@ class RecordHandler:
|
|
|
1000
880
|
next_search: set[PyRecordModel] = set()
|
|
1001
881
|
# Exhaust the records at each step in the path, then use those records for the next step.
|
|
1002
882
|
for node in path:
|
|
1003
|
-
|
|
1004
|
-
direction: RelationshipNodeType = node.direction
|
|
883
|
+
direction = node.direction
|
|
1005
884
|
if len(current_search) == 0:
|
|
1006
885
|
break
|
|
1007
886
|
for search in current_search:
|
|
1008
887
|
if direction == RelationshipNodeType.CHILD:
|
|
1009
|
-
next_search.update(search.get_children_of_type(
|
|
888
|
+
next_search.update(search.get_children_of_type(node.data_type_name))
|
|
1010
889
|
elif direction == RelationshipNodeType.PARENT:
|
|
1011
|
-
next_search.update(search.get_parents_of_type(
|
|
1012
|
-
elif direction == RelationshipNodeType.ANCESTOR:
|
|
1013
|
-
next_search.update(self.an_man.get_ancestors_of_type(search, data_type))
|
|
1014
|
-
elif direction == RelationshipNodeType.DESCENDANT:
|
|
1015
|
-
next_search.update(self.an_man.get_descendant_of_type(search, data_type))
|
|
1016
|
-
elif direction == RelationshipNodeType.FORWARD_SIDE_LINK:
|
|
1017
|
-
next_search.add(search.get_forward_side_link(node.data_field_name))
|
|
1018
|
-
elif direction == RelationshipNodeType.REVERSE_SIDE_LINK:
|
|
1019
|
-
next_search.update(search.get_reverse_side_link(node.data_field_name, data_type))
|
|
890
|
+
next_search.update(search.get_parents_of_type(node.data_type_name))
|
|
1020
891
|
else:
|
|
1021
892
|
raise SapioException("Unsupported path direction.")
|
|
1022
893
|
current_search = next_search
|
|
@@ -1037,6 +908,8 @@ class RecordHandler:
|
|
|
1037
908
|
relationships (e.g. a sample which is aliquoted to a number of samples, then those aliquots are pooled back
|
|
1038
909
|
together into a single sample).
|
|
1039
910
|
|
|
911
|
+
Currently, the relationship path may only contain parent/child nodes.
|
|
912
|
+
|
|
1040
913
|
:param models: A list of record models.
|
|
1041
914
|
:param path: The relationship path to follow.
|
|
1042
915
|
:param wrapper_type: The record model wrapper to use.
|
|
@@ -1049,29 +922,20 @@ class RecordHandler:
|
|
|
1049
922
|
for model in models:
|
|
1050
923
|
current: list[PyRecordModel] = [model if isinstance(model, PyRecordModel) else model.backing_model]
|
|
1051
924
|
for node in path:
|
|
1052
|
-
|
|
1053
|
-
direction: RelationshipNodeType = node.direction
|
|
925
|
+
direction = node.direction
|
|
1054
926
|
if len(current) == 0:
|
|
1055
927
|
break
|
|
1056
928
|
if direction == RelationshipNodeType.CHILD:
|
|
1057
|
-
current = current[0].get_children_of_type(
|
|
929
|
+
current = current[0].get_children_of_type(node.data_type_name)
|
|
1058
930
|
elif direction == RelationshipNodeType.PARENT:
|
|
1059
|
-
current = current[0].get_parents_of_type(
|
|
1060
|
-
elif direction == RelationshipNodeType.ANCESTOR:
|
|
1061
|
-
current = list(self.an_man.get_ancestors_of_type(current[0], data_type))
|
|
1062
|
-
elif direction == RelationshipNodeType.DESCENDANT:
|
|
1063
|
-
current = list(self.an_man.get_descendant_of_type(current[0], data_type))
|
|
1064
|
-
elif direction == RelationshipNodeType.FORWARD_SIDE_LINK:
|
|
1065
|
-
current = [current[0].get_forward_side_link(node.data_field_name)]
|
|
1066
|
-
elif direction == RelationshipNodeType.REVERSE_SIDE_LINK:
|
|
1067
|
-
current = current[0].get_reverse_side_link(node.data_field_name, data_type)
|
|
931
|
+
current = current[0].get_parents_of_type(node.data_type_name)
|
|
1068
932
|
else:
|
|
1069
933
|
raise SapioException("Unsupported path direction.")
|
|
1070
934
|
ret_dict.update({model: self.inst_man.wrap(current[0], wrapper_type) if current else None})
|
|
1071
935
|
return ret_dict
|
|
1072
936
|
|
|
1073
|
-
def __find_model(self, wrapper_type: type[WrappedType], primary_identifier: str, id_value:
|
|
1074
|
-
secondary_identifiers:
|
|
937
|
+
def __find_model(self, wrapper_type: type[WrappedType], primary_identifier: str, id_value: Any,
|
|
938
|
+
secondary_identifiers: FieldMap | None = None) -> WrappedType | None:
|
|
1075
939
|
"""
|
|
1076
940
|
Find a record from the system that matches the given field values. The primary identifier and value is used
|
|
1077
941
|
to query for the record, then the secondary identifiers may be optionally provided to further filter the
|
|
@@ -1095,18 +959,3 @@ class RecordHandler:
|
|
|
1095
959
|
f"encountered in system that matches all provided identifiers.")
|
|
1096
960
|
unique_record = result
|
|
1097
961
|
return unique_record
|
|
1098
|
-
|
|
1099
|
-
@staticmethod
|
|
1100
|
-
def __verify_data_type(records: Iterable[DataRecord], wrapper_type: type[WrappedType]) -> None:
|
|
1101
|
-
"""
|
|
1102
|
-
Throw an exception if the data type of the given records and wrapper don't match.
|
|
1103
|
-
"""
|
|
1104
|
-
model_type: str = wrapper_type.get_wrapper_data_type_name()
|
|
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}")
|