sapiopycommons 2024.8.19a305__py3-none-any.whl → 2024.8.26a307__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 +14 -15
- sapiopycommons/customreport/term_builder.py +5 -2
- sapiopycommons/datatype/attachment_util.py +6 -14
- sapiopycommons/eln/experiment_handler.py +24 -17
- sapiopycommons/eln/experiment_report_util.py +33 -129
- sapiopycommons/files/complex_data_loader.py +4 -3
- sapiopycommons/files/file_bridge.py +14 -13
- sapiopycommons/files/file_bridge_handler.py +8 -7
- sapiopycommons/files/file_data_handler.py +2 -5
- sapiopycommons/files/file_validator.py +7 -7
- sapiopycommons/general/aliases.py +54 -1
- sapiopycommons/general/audit_log.py +19 -23
- sapiopycommons/general/custom_report_util.py +34 -32
- sapiopycommons/general/sapio_links.py +4 -2
- sapiopycommons/multimodal/multimodal_data.py +0 -1
- sapiopycommons/processtracking/endpoints.py +22 -22
- sapiopycommons/recordmodel/record_handler.py +119 -65
- sapiopycommons/rules/eln_rule_handler.py +5 -3
- sapiopycommons/rules/on_save_rule_handler.py +5 -3
- {sapiopycommons-2024.8.19a305.dist-info → sapiopycommons-2024.8.26a307.dist-info}/METADATA +1 -1
- {sapiopycommons-2024.8.19a305.dist-info → sapiopycommons-2024.8.26a307.dist-info}/RECORD +23 -23
- {sapiopycommons-2024.8.19a305.dist-info → sapiopycommons-2024.8.26a307.dist-info}/WHEEL +0 -0
- {sapiopycommons-2024.8.19a305.dist-info → sapiopycommons-2024.8.26a307.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from collections.abc import Iterable
|
|
4
|
-
from typing import Any
|
|
5
4
|
from weakref import WeakValueDictionary
|
|
6
5
|
|
|
7
6
|
from sapiopylib.rest.DataRecordManagerService import DataRecordManager
|
|
@@ -11,7 +10,6 @@ from sapiopylib.rest.pojo.DataRecord import DataRecord
|
|
|
11
10
|
from sapiopylib.rest.pojo.DataRecordPaging import DataRecordPojoPageCriteria
|
|
12
11
|
from sapiopylib.rest.pojo.datatype.FieldDefinition import FieldType
|
|
13
12
|
from sapiopylib.rest.pojo.eln.SapioELNEnums import ElnBaseDataType
|
|
14
|
-
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
15
13
|
from sapiopylib.rest.utils.autopaging import QueryDataRecordsAutoPager, QueryDataRecordByIdListAutoPager, \
|
|
16
14
|
QueryAllRecordsOfTypeAutoPager
|
|
17
15
|
from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
|
|
@@ -22,7 +20,8 @@ from sapiopylib.rest.utils.recordmodel.RelationshipPath import RelationshipPath,
|
|
|
22
20
|
RelationshipNodeType
|
|
23
21
|
from sapiopylib.rest.utils.recordmodel.ancestry import RecordModelAncestorManager
|
|
24
22
|
|
|
25
|
-
from sapiopycommons.general.aliases import RecordModel, SapioRecord, FieldMap
|
|
23
|
+
from sapiopycommons.general.aliases import RecordModel, SapioRecord, FieldMap, FieldIdentifier, AliasUtil, \
|
|
24
|
+
FieldIdentifierMap, FieldValue, UserIdentifier, FieldIdentifierKey
|
|
26
25
|
from sapiopycommons.general.custom_report_util import CustomReportUtil
|
|
27
26
|
from sapiopycommons.general.exceptions import SapioException
|
|
28
27
|
|
|
@@ -42,11 +41,11 @@ class RecordHandler:
|
|
|
42
41
|
__instances: WeakValueDictionary[SapioUser, RecordHandler] = WeakValueDictionary()
|
|
43
42
|
__initialized: bool
|
|
44
43
|
|
|
45
|
-
def __new__(cls, context:
|
|
44
|
+
def __new__(cls, context: UserIdentifier):
|
|
46
45
|
"""
|
|
47
46
|
:param context: The current webhook context or a user object to send requests from.
|
|
48
47
|
"""
|
|
49
|
-
user =
|
|
48
|
+
user = AliasUtil.to_sapio_user(context)
|
|
50
49
|
obj = cls.__instances.get(user)
|
|
51
50
|
if not obj:
|
|
52
51
|
obj = object.__new__(cls)
|
|
@@ -54,10 +53,11 @@ class RecordHandler:
|
|
|
54
53
|
cls.__instances[user] = obj
|
|
55
54
|
return obj
|
|
56
55
|
|
|
57
|
-
def __init__(self, context:
|
|
56
|
+
def __init__(self, context: UserIdentifier):
|
|
58
57
|
"""
|
|
59
58
|
:param context: The current webhook context or a user object to send requests from.
|
|
60
59
|
"""
|
|
60
|
+
self.user = AliasUtil.to_sapio_user(context)
|
|
61
61
|
if self.__initialized:
|
|
62
62
|
return
|
|
63
63
|
self.__initialized = True
|
|
@@ -91,8 +91,8 @@ class RecordHandler:
|
|
|
91
91
|
self.__verify_data_type(records, wrapper_type)
|
|
92
92
|
return self.inst_man.add_existing_records_of_type(list(records), wrapper_type)
|
|
93
93
|
|
|
94
|
-
def query_models(self, wrapper_type: type[WrappedType], field:
|
|
95
|
-
page_limit: int | None = None) -> list[WrappedType]:
|
|
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]:
|
|
96
96
|
"""
|
|
97
97
|
Shorthand for using the data record manager to query for a list of data records by field value
|
|
98
98
|
and then converting the results into a list of record models.
|
|
@@ -100,14 +100,20 @@ class RecordHandler:
|
|
|
100
100
|
:param wrapper_type: The record model wrapper to use.
|
|
101
101
|
:param field: The field to query on.
|
|
102
102
|
: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.
|
|
103
|
+
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages. This parameter
|
|
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.
|
|
104
106
|
:return: The record models for the queried records.
|
|
105
107
|
"""
|
|
106
|
-
|
|
108
|
+
criteria: DataRecordPojoPageCriteria | None = None
|
|
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]
|
|
107
112
|
|
|
108
|
-
def query_and_map_models(self, wrapper_type: type[WrappedType], field:
|
|
109
|
-
|
|
110
|
-
|
|
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]]:
|
|
111
117
|
"""
|
|
112
118
|
Shorthand for using query_models to search for records given values on a specific field and then using
|
|
113
119
|
map_by_field to turn the returned list into a dictionary mapping field values to records.
|
|
@@ -115,17 +121,21 @@ class RecordHandler:
|
|
|
115
121
|
:param wrapper_type: The record model wrapper to use.
|
|
116
122
|
:param field: The field to query and map on.
|
|
117
123
|
:param value_list: The values of the field to query on.
|
|
118
|
-
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages.
|
|
124
|
+
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages. This parameter
|
|
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.
|
|
119
127
|
:param mapping_field: If provided, use this field to map against instead of the field that was queried on.
|
|
120
128
|
:return: The record models for the queried records mapped by field values to the records with that value.
|
|
121
129
|
"""
|
|
122
130
|
if mapping_field is None:
|
|
123
131
|
mapping_field = field
|
|
124
|
-
return self.map_by_field(self.query_models(wrapper_type, field, value_list, page_limit
|
|
132
|
+
return self.map_by_field(self.query_models(wrapper_type, field, value_list, page_limit, page_size),
|
|
133
|
+
mapping_field)
|
|
125
134
|
|
|
126
|
-
def query_and_unique_map_models(self, wrapper_type: type[WrappedType], field:
|
|
127
|
-
|
|
128
|
-
|
|
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]:
|
|
129
139
|
"""
|
|
130
140
|
Shorthand for using query_models to search for records given values on a specific field and then using
|
|
131
141
|
map_by_unique_field to turn the returned list into a dictionary mapping field values to records.
|
|
@@ -134,15 +144,19 @@ class RecordHandler:
|
|
|
134
144
|
:param wrapper_type: The record model wrapper to use.
|
|
135
145
|
:param field: The field to query and map on.
|
|
136
146
|
:param value_list: The values of the field to query on.
|
|
137
|
-
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages.
|
|
147
|
+
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages. This parameter
|
|
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.
|
|
138
150
|
:param mapping_field: If provided, use this field to map against instead of the field that was queried on.
|
|
139
151
|
:return: The record models for the queried records mapped by field values to the record with that value.
|
|
140
152
|
"""
|
|
141
153
|
if mapping_field is None:
|
|
142
154
|
mapping_field = field
|
|
143
|
-
return self.map_by_unique_field(self.query_models(wrapper_type, field, value_list, page_limit
|
|
155
|
+
return self.map_by_unique_field(self.query_models(wrapper_type, field, value_list, page_limit, page_size),
|
|
156
|
+
mapping_field)
|
|
144
157
|
|
|
145
|
-
def query_models_with_criteria(self, wrapper_type: type[WrappedType], field:
|
|
158
|
+
def query_models_with_criteria(self, wrapper_type: type[WrappedType], field: FieldIdentifier,
|
|
159
|
+
value_list: Iterable[FieldValue],
|
|
146
160
|
paging_criteria: DataRecordPojoPageCriteria | None = None,
|
|
147
161
|
page_limit: int | None = None) \
|
|
148
162
|
-> tuple[list[WrappedType], DataRecordPojoPageCriteria]:
|
|
@@ -155,26 +169,33 @@ class RecordHandler:
|
|
|
155
169
|
:param value_list: The values of the field to query on.
|
|
156
170
|
:param paging_criteria: The paging criteria to start the query with.
|
|
157
171
|
:param page_limit: The maximum number of pages to query from the starting criteria. If None, exhausts all
|
|
158
|
-
possible pages.
|
|
172
|
+
possible pages. This parameter only functions if you set a page size in the paging criteria or the platform
|
|
173
|
+
enforces a page size.
|
|
159
174
|
:return: The record models for the queried records and the final paging criteria.
|
|
160
175
|
"""
|
|
161
176
|
dt: str = wrapper_type.get_wrapper_data_type_name()
|
|
177
|
+
field: str = AliasUtil.to_data_field_name(field)
|
|
162
178
|
pager = QueryDataRecordsAutoPager(dt, field, list(value_list), self.user, paging_criteria)
|
|
163
179
|
pager.max_page = page_limit
|
|
164
180
|
return self.wrap_models(pager.get_all_at_once(), wrapper_type), pager.next_page_criteria
|
|
165
181
|
|
|
166
182
|
def query_models_by_id(self, wrapper_type: type[WrappedType], ids: Iterable[int],
|
|
167
|
-
page_limit: int | None = None) -> list[WrappedType]:
|
|
183
|
+
page_limit: int | None = None, page_size: int | None = None) -> list[WrappedType]:
|
|
168
184
|
"""
|
|
169
185
|
Shorthand for using the data record manager to query for a list of data records by record ID
|
|
170
186
|
and then converting the results into a list of record models.
|
|
171
187
|
|
|
172
188
|
:param wrapper_type: The record model wrapper to use.
|
|
173
189
|
:param ids: The list of record IDs to query.
|
|
174
|
-
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages.
|
|
190
|
+
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages. This parameter
|
|
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.
|
|
175
193
|
:return: The record models for the queried records.
|
|
176
194
|
"""
|
|
177
|
-
|
|
195
|
+
criteria: DataRecordPojoPageCriteria | None = None
|
|
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]
|
|
178
199
|
|
|
179
200
|
def query_models_by_id_with_criteria(self, wrapper_type: type[WrappedType], ids: Iterable[int],
|
|
180
201
|
paging_criteria: DataRecordPojoPageCriteria | None = None,
|
|
@@ -188,7 +209,8 @@ class RecordHandler:
|
|
|
188
209
|
:param ids: The list of record IDs to query.
|
|
189
210
|
:param paging_criteria: The paging criteria to start the query with.
|
|
190
211
|
:param page_limit: The maximum number of pages to query from the starting criteria. If None, exhausts all
|
|
191
|
-
possible pages.
|
|
212
|
+
possible pages. This parameter only functions if you set a page size in the paging criteria or the platform
|
|
213
|
+
enforces a page size.
|
|
192
214
|
:return: The record models for the queried records and the final paging criteria.
|
|
193
215
|
"""
|
|
194
216
|
dt: str = wrapper_type.get_wrapper_data_type_name()
|
|
@@ -197,28 +219,37 @@ class RecordHandler:
|
|
|
197
219
|
return self.wrap_models(pager.get_all_at_once(), wrapper_type), pager.next_page_criteria
|
|
198
220
|
|
|
199
221
|
def query_models_by_id_and_map(self, wrapper_type: type[WrappedType], ids: Iterable[int],
|
|
200
|
-
page_limit: int | None = None
|
|
222
|
+
page_limit: int | None = None, page_size: int | None = None) \
|
|
223
|
+
-> dict[int, WrappedType]:
|
|
201
224
|
"""
|
|
202
225
|
Shorthand for using the data record manager to query for a list of data records by record ID
|
|
203
226
|
and then converting the results into a dictionary of record ID to the record model for that ID.
|
|
204
227
|
|
|
205
228
|
:param wrapper_type: The record model wrapper to use.
|
|
206
229
|
:param ids: The list of record IDs to query.
|
|
207
|
-
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages.
|
|
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.
|
|
208
233
|
:return: The record models for the queried records mapped in a dictionary by their record ID.
|
|
209
234
|
"""
|
|
210
|
-
return {x.record_id: x for x in self.query_models_by_id(wrapper_type, ids, page_limit)}
|
|
235
|
+
return {x.record_id: x for x in self.query_models_by_id(wrapper_type, ids, page_limit, page_size)}
|
|
211
236
|
|
|
212
|
-
def query_all_models(self, wrapper_type: type[WrappedType], page_limit: int | None = None
|
|
237
|
+
def query_all_models(self, wrapper_type: type[WrappedType], page_limit: int | None = None,
|
|
238
|
+
page_size: int | None = None) -> list[WrappedType]:
|
|
213
239
|
"""
|
|
214
240
|
Shorthand for using the data record manager to query for all data records of a given type
|
|
215
241
|
and then converting the results into a list of record models.
|
|
216
242
|
|
|
217
243
|
:param wrapper_type: The record model wrapper to use.
|
|
218
|
-
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages.
|
|
244
|
+
:param page_limit: The maximum number of pages to query. If None, exhausts all possible pages. This parameter
|
|
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.
|
|
219
247
|
:return: The record models for the queried records.
|
|
220
248
|
"""
|
|
221
|
-
|
|
249
|
+
criteria: DataRecordPojoPageCriteria | None = None
|
|
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]
|
|
222
253
|
|
|
223
254
|
def query_all_models_with_criteria(self, wrapper_type: type[WrappedType],
|
|
224
255
|
paging_criteria: DataRecordPojoPageCriteria | None = None,
|
|
@@ -231,7 +262,8 @@ class RecordHandler:
|
|
|
231
262
|
:param wrapper_type: The record model wrapper to use.
|
|
232
263
|
:param paging_criteria: The paging criteria to start the query with.
|
|
233
264
|
:param page_limit: The maximum number of pages to query from the starting criteria. If None, exhausts all
|
|
234
|
-
possible pages.
|
|
265
|
+
possible pages. This parameter only functions if you set a page size in the paging criteria or the platform
|
|
266
|
+
enforces a page size.
|
|
235
267
|
:return: The record models for the queried records and the final paging criteria.
|
|
236
268
|
"""
|
|
237
269
|
dt: str = wrapper_type.get_wrapper_data_type_name()
|
|
@@ -241,7 +273,7 @@ class RecordHandler:
|
|
|
241
273
|
|
|
242
274
|
def query_models_by_report(self, wrapper_type: type[WrappedType],
|
|
243
275
|
report_name: str | RawReportTerm | CustomReportCriteria,
|
|
244
|
-
filters: dict[
|
|
276
|
+
filters: dict[FieldIdentifierKey, Iterable[FieldValue]] | None = None,
|
|
245
277
|
page_limit: int | None = None,
|
|
246
278
|
page_size: int | None = None,
|
|
247
279
|
page_number: int | None = None) -> list[WrappedType]:
|
|
@@ -269,11 +301,11 @@ class RecordHandler:
|
|
|
269
301
|
:return: The record models for the queried records that matched the given report.
|
|
270
302
|
"""
|
|
271
303
|
if isinstance(report_name, str):
|
|
272
|
-
results: list[dict[str,
|
|
273
|
-
|
|
304
|
+
results: list[dict[str, FieldValue]] = CustomReportUtil.run_system_report(self.user, report_name, filters,
|
|
305
|
+
page_limit, page_size, page_number)
|
|
274
306
|
elif isinstance(report_name, RawReportTerm):
|
|
275
|
-
results: list[dict[str,
|
|
276
|
-
|
|
307
|
+
results: list[dict[str, FieldValue]] = CustomReportUtil.run_quick_report(self.user, report_name, filters,
|
|
308
|
+
page_limit, page_size, page_number)
|
|
277
309
|
elif isinstance(report_name, CustomReportCriteria):
|
|
278
310
|
dt: str = wrapper_type.get_wrapper_data_type_name()
|
|
279
311
|
# Ensure that the root data type is the one we're looking for.
|
|
@@ -284,8 +316,8 @@ class RecordHandler:
|
|
|
284
316
|
# Enforce that the given custom report has a record ID column.
|
|
285
317
|
if not any([x.data_type_name == dt and x.data_field_name == "RecordId" for x in report_name.column_list]):
|
|
286
318
|
report_name.column_list.append(ReportColumn(dt, "RecordId", FieldType.LONG))
|
|
287
|
-
results: list[dict[str,
|
|
288
|
-
|
|
319
|
+
results: list[dict[str, FieldValue]] = CustomReportUtil.run_custom_report(self.user, report_name, filters,
|
|
320
|
+
page_limit, page_size, page_number)
|
|
289
321
|
else:
|
|
290
322
|
raise SapioException("Unrecognized report object.")
|
|
291
323
|
|
|
@@ -314,7 +346,8 @@ class RecordHandler:
|
|
|
314
346
|
"""
|
|
315
347
|
return self.inst_man.add_new_records_of_type(num, wrapper_type)
|
|
316
348
|
|
|
317
|
-
def add_models_with_data(self, wrapper_type: type[WrappedType], fields: list[
|
|
349
|
+
def add_models_with_data(self, wrapper_type: type[WrappedType], fields: list[FieldIdentifierMap]) \
|
|
350
|
+
-> list[WrappedType]:
|
|
318
351
|
"""
|
|
319
352
|
Shorthand for using the instance manager to add new models of the given type, and then initializing all those
|
|
320
353
|
models with the given fields.
|
|
@@ -324,13 +357,14 @@ class RecordHandler:
|
|
|
324
357
|
:return: The newly added record models with the provided fields set. The records will be in the same order as
|
|
325
358
|
the fields in the fields list.
|
|
326
359
|
"""
|
|
360
|
+
fields: list[FieldMap] = AliasUtil.to_data_field_names_list_dict(fields)
|
|
327
361
|
models: list[WrappedType] = self.add_models(wrapper_type, len(fields))
|
|
328
362
|
for model, field_list in zip(models, fields):
|
|
329
363
|
model.set_field_values(field_list)
|
|
330
364
|
return models
|
|
331
365
|
|
|
332
|
-
def find_or_add_model(self, wrapper_type: type[WrappedType], primary_identifier:
|
|
333
|
-
secondary_identifiers:
|
|
366
|
+
def find_or_add_model(self, wrapper_type: type[WrappedType], primary_identifier: FieldIdentifier,
|
|
367
|
+
id_value: FieldValue, secondary_identifiers: FieldIdentifierMap | None = None) -> WrappedType:
|
|
334
368
|
"""
|
|
335
369
|
Find a unique record that matches the given field values. If no such records exist, add a record model to the
|
|
336
370
|
cache with the identifying fields set to the desired values. This record will be created in the system when
|
|
@@ -353,6 +387,8 @@ class RecordHandler:
|
|
|
353
387
|
if secondary_identifiers is None:
|
|
354
388
|
secondary_identifiers = {}
|
|
355
389
|
|
|
390
|
+
primary_identifier: str = AliasUtil.to_data_field_name(primary_identifier)
|
|
391
|
+
secondary_identifiers: FieldMap = AliasUtil.to_data_field_names_dict(secondary_identifiers)
|
|
356
392
|
unique_record: WrappedType | None = self.__find_model(wrapper_type, primary_identifier, id_value,
|
|
357
393
|
secondary_identifiers)
|
|
358
394
|
# If a unique record matched the identifiers, return it.
|
|
@@ -379,7 +415,7 @@ class RecordHandler:
|
|
|
379
415
|
dt: str = wrapper_type.get_wrapper_data_type_name()
|
|
380
416
|
return self.wrap_models(self.dr_man.add_data_records(dt, num), wrapper_type)
|
|
381
417
|
|
|
382
|
-
def create_models_with_data(self, wrapper_type: type[WrappedType], fields: list[
|
|
418
|
+
def create_models_with_data(self, wrapper_type: type[WrappedType], fields: list[FieldIdentifierMap]) \
|
|
383
419
|
-> list[WrappedType]:
|
|
384
420
|
"""
|
|
385
421
|
Shorthand for creating new records via the data record manager with field data to initialize the records with
|
|
@@ -393,10 +429,12 @@ class RecordHandler:
|
|
|
393
429
|
:return: The newly created record models.
|
|
394
430
|
"""
|
|
395
431
|
dt: str = wrapper_type.get_wrapper_data_type_name()
|
|
432
|
+
fields: list[FieldMap] = AliasUtil.to_data_field_names_list_dict(fields)
|
|
396
433
|
return self.wrap_models(self.dr_man.add_data_records_with_data(dt, fields), wrapper_type)
|
|
397
434
|
|
|
398
|
-
def find_or_create_model(self, wrapper_type: type[WrappedType], primary_identifier:
|
|
399
|
-
secondary_identifiers:
|
|
435
|
+
def find_or_create_model(self, wrapper_type: type[WrappedType], primary_identifier: FieldIdentifier,
|
|
436
|
+
id_value: FieldValue, secondary_identifiers: FieldIdentifierMap | None = None) \
|
|
437
|
+
-> WrappedType:
|
|
400
438
|
"""
|
|
401
439
|
Find a unique record that matches the given field values. If no such records exist, create one with the
|
|
402
440
|
identifying fields set to the desired values. If more than one record with the identifying values exists,
|
|
@@ -420,6 +458,8 @@ class RecordHandler:
|
|
|
420
458
|
if secondary_identifiers is None:
|
|
421
459
|
secondary_identifiers = {}
|
|
422
460
|
|
|
461
|
+
primary_identifier: str = AliasUtil.to_data_field_name(primary_identifier)
|
|
462
|
+
secondary_identifiers: FieldMap = AliasUtil.to_data_field_names_dict(secondary_identifiers)
|
|
423
463
|
unique_record: WrappedType | None = self.__find_model(wrapper_type, primary_identifier, id_value,
|
|
424
464
|
secondary_identifiers)
|
|
425
465
|
# If a unique record matched the identifiers, return it.
|
|
@@ -579,7 +619,7 @@ class RecordHandler:
|
|
|
579
619
|
return by_children
|
|
580
620
|
|
|
581
621
|
@staticmethod
|
|
582
|
-
def map_to_forward_side_link(models: Iterable[WrappedRecordModel], field_name:
|
|
622
|
+
def map_to_forward_side_link(models: Iterable[WrappedRecordModel], field_name: FieldIdentifier,
|
|
583
623
|
side_link_type: type[WrappedType]) -> dict[WrappedRecordModel, WrappedType]:
|
|
584
624
|
"""
|
|
585
625
|
Map a list of record models to their forward side link. The forward side link must already be loaded.
|
|
@@ -590,13 +630,14 @@ class RecordHandler:
|
|
|
590
630
|
:return: A dict[ModelType, SlideLink]. If an input model doesn't have a forward side link of the given type,
|
|
591
631
|
then it will map to None.
|
|
592
632
|
"""
|
|
633
|
+
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
593
634
|
return_dict: dict[WrappedRecordModel, WrappedType] = {}
|
|
594
635
|
for model in models:
|
|
595
636
|
return_dict[model] = model.get_forward_side_link(field_name, side_link_type)
|
|
596
637
|
return return_dict
|
|
597
638
|
|
|
598
639
|
@staticmethod
|
|
599
|
-
def map_by_forward_side_links(models: Iterable[WrappedRecordModel], field_name:
|
|
640
|
+
def map_by_forward_side_links(models: Iterable[WrappedRecordModel], field_name: FieldIdentifier,
|
|
600
641
|
side_link_type: type[WrappedType]) -> dict[WrappedType, list[WrappedRecordModel]]:
|
|
601
642
|
"""
|
|
602
643
|
Take a list of record models and map them by their forward side link. Essentially an inversion of
|
|
@@ -609,6 +650,7 @@ class RecordHandler:
|
|
|
609
650
|
:return: A dict[SideLink, list[ModelType]]. If an input model doesn't have a forward side link of the given type
|
|
610
651
|
pointing to it, then it will not be in the resulting dictionary.
|
|
611
652
|
"""
|
|
653
|
+
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
612
654
|
to_side_link: dict[WrappedRecordModel, WrappedType] = RecordHandler\
|
|
613
655
|
.map_to_forward_side_link(models, field_name, side_link_type)
|
|
614
656
|
by_side_link: dict[WrappedType, list[WrappedRecordModel]] = {}
|
|
@@ -619,7 +661,7 @@ class RecordHandler:
|
|
|
619
661
|
return by_side_link
|
|
620
662
|
|
|
621
663
|
@staticmethod
|
|
622
|
-
def map_by_forward_side_link(models: Iterable[WrappedRecordModel], field_name:
|
|
664
|
+
def map_by_forward_side_link(models: Iterable[WrappedRecordModel], field_name: FieldIdentifier,
|
|
623
665
|
side_link_type: type[WrappedType]) -> dict[WrappedType, WrappedRecordModel]:
|
|
624
666
|
"""
|
|
625
667
|
Take a list of record models and map them by their forward side link. Essentially an inversion of
|
|
@@ -632,6 +674,7 @@ class RecordHandler:
|
|
|
632
674
|
:return: A dict[SideLink, ModelType]. If an input model doesn't have a forward side link of the given type
|
|
633
675
|
pointing to it, then it will not be in the resulting dictionary.
|
|
634
676
|
"""
|
|
677
|
+
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
635
678
|
to_side_link: dict[WrappedRecordModel, WrappedType] = RecordHandler\
|
|
636
679
|
.map_to_forward_side_link(models, field_name, side_link_type)
|
|
637
680
|
by_side_link: dict[WrappedType, WrappedRecordModel] = {}
|
|
@@ -645,7 +688,7 @@ class RecordHandler:
|
|
|
645
688
|
return by_side_link
|
|
646
689
|
|
|
647
690
|
@staticmethod
|
|
648
|
-
def map_to_reverse_side_links(models: Iterable[WrappedRecordModel], field_name:
|
|
691
|
+
def map_to_reverse_side_links(models: Iterable[WrappedRecordModel], field_name: FieldIdentifier,
|
|
649
692
|
side_link_type: type[WrappedType]) -> dict[WrappedRecordModel, list[WrappedType]]:
|
|
650
693
|
"""
|
|
651
694
|
Map a list of record models to a list reverse side links of a given type. The reverse side links must already
|
|
@@ -658,13 +701,14 @@ class RecordHandler:
|
|
|
658
701
|
:return: A dict[ModelType, list[SideLink]]. If an input model doesn't have reverse side links of the given type,
|
|
659
702
|
then it will map to an empty list.
|
|
660
703
|
"""
|
|
704
|
+
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
661
705
|
return_dict: dict[WrappedRecordModel, list[WrappedType]] = {}
|
|
662
706
|
for model in models:
|
|
663
707
|
return_dict[model] = model.get_reverse_side_link(field_name, side_link_type)
|
|
664
708
|
return return_dict
|
|
665
709
|
|
|
666
710
|
@staticmethod
|
|
667
|
-
def map_to_reverse_side_link(models: Iterable[WrappedRecordModel], field_name:
|
|
711
|
+
def map_to_reverse_side_link(models: Iterable[WrappedRecordModel], field_name: FieldIdentifier,
|
|
668
712
|
side_link_type: type[WrappedType]) -> dict[WrappedRecordModel, WrappedType]:
|
|
669
713
|
"""
|
|
670
714
|
Map a list of record models to the reverse side link of a given type. If a given record has more than one
|
|
@@ -677,6 +721,7 @@ class RecordHandler:
|
|
|
677
721
|
:return: A dict[ModelType, SideLink]. If an input model doesn't have reverse side links of the given type,
|
|
678
722
|
then it will map to None.
|
|
679
723
|
"""
|
|
724
|
+
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
680
725
|
return_dict: dict[WrappedRecordModel, WrappedType] = {}
|
|
681
726
|
for model in models:
|
|
682
727
|
links: list[WrappedType] = model.get_reverse_side_link(field_name, side_link_type)
|
|
@@ -687,7 +732,7 @@ class RecordHandler:
|
|
|
687
732
|
return return_dict
|
|
688
733
|
|
|
689
734
|
@staticmethod
|
|
690
|
-
def map_by_reverse_side_links(models: Iterable[WrappedRecordModel], field_name:
|
|
735
|
+
def map_by_reverse_side_links(models: Iterable[WrappedRecordModel], field_name: FieldIdentifier,
|
|
691
736
|
side_link_type: type[WrappedType]) -> dict[WrappedType, list[WrappedRecordModel]]:
|
|
692
737
|
"""
|
|
693
738
|
Take a list of record models and map them by their reverse side links. Essentially an inversion of
|
|
@@ -701,6 +746,7 @@ class RecordHandler:
|
|
|
701
746
|
:return: A dict[SideLink, list[ModelType]]. If an input model doesn't have reverse side links of the given type
|
|
702
747
|
pointing to it, then it will not be in the resulting dictionary.
|
|
703
748
|
"""
|
|
749
|
+
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
704
750
|
to_side_links: dict[WrappedRecordModel, list[WrappedType]] = RecordHandler\
|
|
705
751
|
.map_to_reverse_side_links(models, field_name, side_link_type)
|
|
706
752
|
by_side_links: dict[WrappedType, list[WrappedRecordModel]] = {}
|
|
@@ -710,7 +756,7 @@ class RecordHandler:
|
|
|
710
756
|
return by_side_links
|
|
711
757
|
|
|
712
758
|
@staticmethod
|
|
713
|
-
def map_by_reverse_side_link(models: Iterable[WrappedRecordModel], field_name:
|
|
759
|
+
def map_by_reverse_side_link(models: Iterable[WrappedRecordModel], field_name: FieldIdentifier,
|
|
714
760
|
side_link_type: type[WrappedType]) -> dict[WrappedType, WrappedRecordModel]:
|
|
715
761
|
"""
|
|
716
762
|
Take a list of record models and map them by their reverse side link. Essentially an inversion of
|
|
@@ -724,6 +770,7 @@ class RecordHandler:
|
|
|
724
770
|
:return: A dict[SideLink, ModelType]. If an input model doesn't have a reverse side link of the given type
|
|
725
771
|
pointing to it, then it will not be in the resulting dictionary.
|
|
726
772
|
"""
|
|
773
|
+
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
727
774
|
to_side_link: dict[WrappedRecordModel, WrappedType] = RecordHandler\
|
|
728
775
|
.map_to_reverse_side_link(models, field_name, side_link_type)
|
|
729
776
|
by_side_link: dict[WrappedType, WrappedRecordModel] = {}
|
|
@@ -750,7 +797,8 @@ class RecordHandler:
|
|
|
750
797
|
return ret_dict
|
|
751
798
|
|
|
752
799
|
@staticmethod
|
|
753
|
-
def map_by_field(models: Iterable[SapioRecord], field_name:
|
|
800
|
+
def map_by_field(models: Iterable[SapioRecord], field_name: FieldIdentifier) \
|
|
801
|
+
-> dict[FieldValue, list[SapioRecord]]:
|
|
754
802
|
"""
|
|
755
803
|
Map the given records by one of their fields. If any two records share the same field value, they'll appear in
|
|
756
804
|
the same value list.
|
|
@@ -759,14 +807,16 @@ class RecordHandler:
|
|
|
759
807
|
:param field_name: The field name to map against.
|
|
760
808
|
:return: A dict mapping field values to the records with that value.
|
|
761
809
|
"""
|
|
762
|
-
|
|
810
|
+
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
811
|
+
ret_dict: dict[FieldValue, list[SapioRecord]] = {}
|
|
763
812
|
for model in models:
|
|
764
|
-
val:
|
|
813
|
+
val: FieldValue = model.get_field_value(field_name)
|
|
765
814
|
ret_dict.setdefault(val, []).append(model)
|
|
766
815
|
return ret_dict
|
|
767
816
|
|
|
768
817
|
@staticmethod
|
|
769
|
-
def map_by_unique_field(models: Iterable[SapioRecord], field_name:
|
|
818
|
+
def map_by_unique_field(models: Iterable[SapioRecord], field_name: FieldIdentifier) \
|
|
819
|
+
-> dict[FieldValue, SapioRecord]:
|
|
770
820
|
"""
|
|
771
821
|
Uniquely map the given records by one of their fields. If any two records share the same field value, throws
|
|
772
822
|
an exception.
|
|
@@ -775,16 +825,17 @@ class RecordHandler:
|
|
|
775
825
|
:param field_name: The field name to map against.
|
|
776
826
|
:return: A dict mapping field values to the record with that value.
|
|
777
827
|
"""
|
|
778
|
-
|
|
828
|
+
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
829
|
+
ret_dict: dict[FieldValue, SapioRecord] = {}
|
|
779
830
|
for model in models:
|
|
780
|
-
val:
|
|
831
|
+
val: FieldValue = model.get_field_value(field_name)
|
|
781
832
|
if val in ret_dict:
|
|
782
833
|
raise SapioException(f"Value {val} encountered more than once in models list.")
|
|
783
834
|
ret_dict.update({val: model})
|
|
784
835
|
return ret_dict
|
|
785
836
|
|
|
786
837
|
@staticmethod
|
|
787
|
-
def sum_of_field(models: Iterable[SapioRecord], field_name:
|
|
838
|
+
def sum_of_field(models: Iterable[SapioRecord], field_name: FieldIdentifier) -> float:
|
|
788
839
|
"""
|
|
789
840
|
Sum up the numeric value of a given field across all input models. Excepts that all given models have a value.
|
|
790
841
|
If the field is an integer field, the value will be converted to a float.
|
|
@@ -793,13 +844,14 @@ class RecordHandler:
|
|
|
793
844
|
:param field_name: The name of the numeric field to sum.
|
|
794
845
|
:return: The sum of the field values for the collection of models.
|
|
795
846
|
"""
|
|
847
|
+
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
796
848
|
field_sum: float = 0
|
|
797
849
|
for model in models:
|
|
798
850
|
field_sum += float(model.get_field_value(field_name))
|
|
799
851
|
return field_sum
|
|
800
852
|
|
|
801
853
|
@staticmethod
|
|
802
|
-
def mean_of_field(models: Iterable[SapioRecord], field_name:
|
|
854
|
+
def mean_of_field(models: Iterable[SapioRecord], field_name: FieldIdentifier) -> float:
|
|
803
855
|
"""
|
|
804
856
|
Calculate the mean of the numeric value of a given field across all input models. Excepts that all given models
|
|
805
857
|
have a value. If the field is an integer field, the value will be converted to a float.
|
|
@@ -840,8 +892,8 @@ class RecordHandler:
|
|
|
840
892
|
return oldest
|
|
841
893
|
|
|
842
894
|
@staticmethod
|
|
843
|
-
def values_to_field_maps(field_name:
|
|
844
|
-
|
|
895
|
+
def values_to_field_maps(field_name: FieldIdentifier, values: Iterable[FieldValue],
|
|
896
|
+
existing_fields: list[FieldIdentifier] | None = None) -> list[FieldMap]:
|
|
845
897
|
"""
|
|
846
898
|
Add a list of values for a specific field to a list of dictionaries pairing each value to that field name.
|
|
847
899
|
|
|
@@ -852,6 +904,8 @@ class RecordHandler:
|
|
|
852
904
|
:return: A fields map list that contains the given values mapped by the given field name.
|
|
853
905
|
"""
|
|
854
906
|
# 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)
|
|
855
909
|
if existing_fields:
|
|
856
910
|
values = list(values)
|
|
857
911
|
# The number of new values must match the length of the existing fields list.
|
|
@@ -1016,8 +1070,8 @@ class RecordHandler:
|
|
|
1016
1070
|
ret_dict.update({model: self.inst_man.wrap(current[0], wrapper_type) if current else None})
|
|
1017
1071
|
return ret_dict
|
|
1018
1072
|
|
|
1019
|
-
def __find_model(self, wrapper_type: type[WrappedType], primary_identifier: str, id_value:
|
|
1020
|
-
secondary_identifiers:
|
|
1073
|
+
def __find_model(self, wrapper_type: type[WrappedType], primary_identifier: str, id_value: FieldValue,
|
|
1074
|
+
secondary_identifiers: FieldIdentifierMap | None = None) -> WrappedType | None:
|
|
1021
1075
|
"""
|
|
1022
1076
|
Find a record from the system that matches the given field values. The primary identifier and value is used
|
|
1023
1077
|
to query for the record, then the secondary identifiers may be optionally provided to further filter the
|
|
@@ -9,7 +9,7 @@ from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
|
9
9
|
from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelManager, RecordModelInstanceManager
|
|
10
10
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
|
|
11
11
|
|
|
12
|
-
from sapiopycommons.general.aliases import FieldMap
|
|
12
|
+
from sapiopycommons.general.aliases import FieldMap, AliasUtil, DataTypeIdentifier
|
|
13
13
|
from sapiopycommons.general.exceptions import SapioException
|
|
14
14
|
|
|
15
15
|
|
|
@@ -125,7 +125,7 @@ class ElnRuleHandler:
|
|
|
125
125
|
"""
|
|
126
126
|
return list(self.__entry_to_field_maps.keys())
|
|
127
127
|
|
|
128
|
-
def get_records(self, data_type:
|
|
128
|
+
def get_records(self, data_type: DataTypeIdentifier, entry: str | None = None) -> list[DataRecord]:
|
|
129
129
|
"""
|
|
130
130
|
Get records from the cached context with the given data type. Capable of being filtered to searching within
|
|
131
131
|
the context of an entry name. If the given data type or entry does not exist in the context,
|
|
@@ -136,11 +136,12 @@ class ElnRuleHandler:
|
|
|
136
136
|
type from every entry. If an entry is provided, but it does not exist in the context, returns an empty list.
|
|
137
137
|
:return: The records from the context that match the input parameters.
|
|
138
138
|
"""
|
|
139
|
+
data_type: str = AliasUtil.to_data_type_name(data_type)
|
|
139
140
|
records: dict[str, set[DataRecord]] = self.__entry_to_records.get(entry, {}) if entry else self.__records
|
|
140
141
|
return list(records.get(data_type, []))
|
|
141
142
|
|
|
142
143
|
# FR-46701: Add functions to the rule handlers for accessing the field maps of inaccessible records in the context.
|
|
143
|
-
def get_field_maps(self, data_type:
|
|
144
|
+
def get_field_maps(self, data_type: DataTypeIdentifier, entry: str | None = None) -> list[FieldMap]:
|
|
144
145
|
"""
|
|
145
146
|
Get field maps from the cached context with the given data type. Capable of being filtered to searching within
|
|
146
147
|
the context of an entry name. If the given data type or entry does not exist in the context,
|
|
@@ -156,6 +157,7 @@ class ElnRuleHandler:
|
|
|
156
157
|
list.
|
|
157
158
|
:return: The field maps from the context that match the input parameters.
|
|
158
159
|
"""
|
|
160
|
+
data_type: str = AliasUtil.to_data_type_name(data_type)
|
|
159
161
|
field_maps: dict[str, dict[int, FieldMap]] = self.__entry_to_field_maps.get(entry, {}) if entry else self.__field_maps
|
|
160
162
|
return list(field_maps.get(data_type, {}).values())
|
|
161
163
|
|
|
@@ -9,7 +9,7 @@ from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
|
9
9
|
from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelManager, RecordModelInstanceManager
|
|
10
10
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
|
|
11
11
|
|
|
12
|
-
from sapiopycommons.general.aliases import FieldMap
|
|
12
|
+
from sapiopycommons.general.aliases import FieldMap, DataTypeIdentifier, AliasUtil
|
|
13
13
|
from sapiopycommons.general.exceptions import SapioException
|
|
14
14
|
|
|
15
15
|
|
|
@@ -121,7 +121,7 @@ class OnSaveRuleHandler:
|
|
|
121
121
|
"""
|
|
122
122
|
return list(self.__base_id_to_field_maps.keys())
|
|
123
123
|
|
|
124
|
-
def get_records(self, data_type:
|
|
124
|
+
def get_records(self, data_type: DataTypeIdentifier, record_id: int | None = None) -> list[DataRecord]:
|
|
125
125
|
"""
|
|
126
126
|
Get records from the cached context with the given data type. Capable of being filtered to searching within
|
|
127
127
|
the context of a record ID. If the given data type or record ID does not exist in the context,
|
|
@@ -132,11 +132,12 @@ class OnSaveRuleHandler:
|
|
|
132
132
|
data type from every ID. If an ID is provided, but it does not exist in the context, returns an empty list.
|
|
133
133
|
:return: The records from the context that match the input parameters.
|
|
134
134
|
"""
|
|
135
|
+
data_type: str = AliasUtil.to_data_type_name(data_type)
|
|
135
136
|
records: dict[str, set[DataRecord]] = self.__base_id_to_records.get(record_id, {}) if record_id else self.__records
|
|
136
137
|
return list(records.get(data_type, []))
|
|
137
138
|
|
|
138
139
|
# FR-46701: Add functions to the rule handlers for accessing the field maps of inaccessible records in the context.
|
|
139
|
-
def get_field_maps(self, data_type:
|
|
140
|
+
def get_field_maps(self, data_type: DataTypeIdentifier, record_id: int | None = None) -> list[FieldMap]:
|
|
140
141
|
"""
|
|
141
142
|
Get field maps from the cached context with the given data type. Capable of being filtered to searching within
|
|
142
143
|
the context of a record ID. If the given data type or record ID does not exist in the context,
|
|
@@ -152,6 +153,7 @@ class OnSaveRuleHandler:
|
|
|
152
153
|
list.
|
|
153
154
|
:return: The field maps from the context that match the input parameters.
|
|
154
155
|
"""
|
|
156
|
+
data_type: str = AliasUtil.to_data_type_name(data_type)
|
|
155
157
|
field_maps: dict[str, dict[int, FieldMap]] = self.__base_id_to_field_maps.get(record_id, {}) if record_id else self.__field_maps
|
|
156
158
|
return list(field_maps.get(data_type, {}).values())
|
|
157
159
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: sapiopycommons
|
|
3
|
-
Version: 2024.8.
|
|
3
|
+
Version: 2024.8.26a307
|
|
4
4
|
Summary: Official Sapio Python API Utilities Package
|
|
5
5
|
Project-URL: Homepage, https://github.com/sapiosciences
|
|
6
6
|
Author-email: Jonathan Steck <jsteck@sapiosciences.com>, Yechen Qiao <yqiao@sapiosciences.com>
|