sapiopycommons 2024.8.19a305__py3-none-any.whl → 2024.8.20a306__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 +11 -12
- sapiopycommons/customreport/term_builder.py +5 -2
- sapiopycommons/datatype/attachment_util.py +6 -14
- sapiopycommons/eln/experiment_handler.py +23 -12
- 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 +7 -6
- 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 +71 -46
- 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.20a306.dist-info}/METADATA +1 -1
- {sapiopycommons-2024.8.19a305.dist-info → sapiopycommons-2024.8.20a306.dist-info}/RECORD +23 -23
- {sapiopycommons-2024.8.19a305.dist-info → sapiopycommons-2024.8.20a306.dist-info}/WHEEL +0 -0
- {sapiopycommons-2024.8.19a305.dist-info → sapiopycommons-2024.8.20a306.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from sapiopylib.rest.User import SapioUser
|
|
2
|
-
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
3
2
|
|
|
4
|
-
from sapiopycommons.general.aliases import RecordIdentifier, AliasUtil, ExperimentIdentifier
|
|
3
|
+
from sapiopycommons.general.aliases import RecordIdentifier, AliasUtil, ExperimentIdentifier, DataTypeIdentifier, \
|
|
4
|
+
UserIdentifier
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class ProcessTracking:
|
|
8
8
|
@staticmethod
|
|
9
|
-
def assign_to_process(context:
|
|
9
|
+
def assign_to_process(context: UserIdentifier, data_type: DataTypeIdentifier, records: list[RecordIdentifier],
|
|
10
10
|
process_name: str, step_number: int | None = None, branch_id: int | None = None,
|
|
11
11
|
request: RecordIdentifier | None = None) -> None:
|
|
12
12
|
"""
|
|
@@ -27,19 +27,19 @@ class ProcessTracking:
|
|
|
27
27
|
"""
|
|
28
28
|
sub_path = '/ext/process-tracking/assign-to-process'
|
|
29
29
|
payload = {
|
|
30
|
-
"data-type-name": data_type,
|
|
30
|
+
"data-type-name": AliasUtil.to_data_type_name(data_type),
|
|
31
31
|
"record-ids": AliasUtil.to_record_ids(records),
|
|
32
32
|
"process-name": process_name,
|
|
33
33
|
"step-number": step_number,
|
|
34
34
|
"branch-id": branch_id,
|
|
35
35
|
"request-record-id": AliasUtil.to_record_ids([request])[0] if request is not None else None
|
|
36
36
|
}
|
|
37
|
-
user: SapioUser =
|
|
37
|
+
user: SapioUser = AliasUtil.to_sapio_user(context)
|
|
38
38
|
response = user.post(sub_path, payload=payload)
|
|
39
39
|
user.raise_for_status(response)
|
|
40
40
|
|
|
41
41
|
@staticmethod
|
|
42
|
-
def begin_protocol(context:
|
|
42
|
+
def begin_protocol(context: UserIdentifier, data_type: DataTypeIdentifier, records: list[RecordIdentifier],
|
|
43
43
|
experiment: ExperimentIdentifier) -> None:
|
|
44
44
|
"""
|
|
45
45
|
Begin the assigned processes of the given tracked records as the given experiment. This sets the status of the
|
|
@@ -54,16 +54,16 @@ class ProcessTracking:
|
|
|
54
54
|
"""
|
|
55
55
|
sub_path = '/ext/process-tracking/begin-protocol'
|
|
56
56
|
payload = {
|
|
57
|
-
"data-type-name": data_type,
|
|
57
|
+
"data-type-name": AliasUtil.to_data_type_name(data_type),
|
|
58
58
|
"record-ids": AliasUtil.to_record_ids(records),
|
|
59
59
|
"experiment-id": AliasUtil.to_notebook_id(experiment),
|
|
60
60
|
}
|
|
61
|
-
user: SapioUser =
|
|
61
|
+
user: SapioUser = AliasUtil.to_sapio_user(context)
|
|
62
62
|
response = user.post(sub_path, payload=payload)
|
|
63
63
|
user.raise_for_status(response)
|
|
64
64
|
|
|
65
65
|
@staticmethod
|
|
66
|
-
def complete_protocol(context:
|
|
66
|
+
def complete_protocol(context: UserIdentifier, data_type: DataTypeIdentifier, records: list[RecordIdentifier],
|
|
67
67
|
experiment: ExperimentIdentifier) -> None:
|
|
68
68
|
"""
|
|
69
69
|
Complete the current step that the given tracked records are at given the experiment.
|
|
@@ -80,16 +80,16 @@ class ProcessTracking:
|
|
|
80
80
|
"""
|
|
81
81
|
sub_path = '/ext/process-tracking/complete-protocol'
|
|
82
82
|
payload = {
|
|
83
|
-
"data-type-name": data_type,
|
|
83
|
+
"data-type-name": AliasUtil.to_data_type_name(data_type),
|
|
84
84
|
"record-ids": AliasUtil.to_record_ids(records),
|
|
85
85
|
"experiment-id": AliasUtil.to_notebook_id(experiment),
|
|
86
86
|
}
|
|
87
|
-
user: SapioUser =
|
|
87
|
+
user: SapioUser = AliasUtil.to_sapio_user(context)
|
|
88
88
|
response = user.post(sub_path, payload=payload)
|
|
89
89
|
user.raise_for_status(response)
|
|
90
90
|
|
|
91
91
|
@staticmethod
|
|
92
|
-
def fail(context:
|
|
92
|
+
def fail(context: UserIdentifier, data_type: DataTypeIdentifier, records: list[RecordIdentifier],
|
|
93
93
|
experiment: ExperimentIdentifier) -> None:
|
|
94
94
|
"""
|
|
95
95
|
Fail the assigned processes of the given tracked records, changing their statuses to "Failed -". The tracked
|
|
@@ -103,16 +103,16 @@ class ProcessTracking:
|
|
|
103
103
|
"""
|
|
104
104
|
sub_path = '/ext/process-tracking/fail'
|
|
105
105
|
payload = {
|
|
106
|
-
"data-type-name": data_type,
|
|
106
|
+
"data-type-name": AliasUtil.to_data_type_name(data_type),
|
|
107
107
|
"record-ids": AliasUtil.to_record_ids(records),
|
|
108
108
|
"experiment-id": AliasUtil.to_notebook_id(experiment),
|
|
109
109
|
}
|
|
110
|
-
user: SapioUser =
|
|
110
|
+
user: SapioUser = AliasUtil.to_sapio_user(context)
|
|
111
111
|
response = user.post(sub_path, payload=payload)
|
|
112
112
|
user.raise_for_status(response)
|
|
113
113
|
|
|
114
114
|
@staticmethod
|
|
115
|
-
def promote_to_next_by_experiment(context:
|
|
115
|
+
def promote_to_next_by_experiment(context: UserIdentifier, data_type: DataTypeIdentifier,
|
|
116
116
|
records: list[RecordIdentifier], experiment: ExperimentIdentifier) -> None:
|
|
117
117
|
"""
|
|
118
118
|
Promote the status of the given tracked records to the next status in their process using an experiment.
|
|
@@ -129,16 +129,16 @@ class ProcessTracking:
|
|
|
129
129
|
"""
|
|
130
130
|
sub_path = '/ext/process-tracking/promote-status-to-next'
|
|
131
131
|
payload = {
|
|
132
|
-
"data-type-name": data_type,
|
|
132
|
+
"data-type-name": AliasUtil.to_data_type_name(data_type),
|
|
133
133
|
"record-ids": AliasUtil.to_record_ids(records),
|
|
134
134
|
"experiment-id": AliasUtil.to_notebook_id(experiment),
|
|
135
135
|
}
|
|
136
|
-
user: SapioUser =
|
|
136
|
+
user: SapioUser = AliasUtil.to_sapio_user(context)
|
|
137
137
|
response = user.post(sub_path, payload=payload)
|
|
138
138
|
user.raise_for_status(response)
|
|
139
139
|
|
|
140
140
|
@staticmethod
|
|
141
|
-
def promote_to_next_by_step(context:
|
|
141
|
+
def promote_to_next_by_step(context: UserIdentifier, data_type: DataTypeIdentifier,
|
|
142
142
|
records: list[RecordIdentifier], process_name: str, step_number: int,
|
|
143
143
|
branch_id: int | None = None) -> None:
|
|
144
144
|
"""
|
|
@@ -159,7 +159,7 @@ class ProcessTracking:
|
|
|
159
159
|
"""
|
|
160
160
|
sub_path = '/ext/process-tracking/promote-status-to-next'
|
|
161
161
|
payload = {
|
|
162
|
-
"data-type-name": data_type,
|
|
162
|
+
"data-type-name": AliasUtil.to_data_type_name(data_type),
|
|
163
163
|
"record-ids": AliasUtil.to_record_ids(records),
|
|
164
164
|
"current-process-status": {
|
|
165
165
|
"process-name": process_name,
|
|
@@ -167,12 +167,12 @@ class ProcessTracking:
|
|
|
167
167
|
"branch-id": branch_id
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
|
-
user: SapioUser =
|
|
170
|
+
user: SapioUser = AliasUtil.to_sapio_user(context)
|
|
171
171
|
response = user.post(sub_path, payload=payload)
|
|
172
172
|
user.raise_for_status(response)
|
|
173
173
|
|
|
174
174
|
@staticmethod
|
|
175
|
-
def reprocess(context:
|
|
175
|
+
def reprocess(context: UserIdentifier, records: list[RecordIdentifier]) -> None:
|
|
176
176
|
"""
|
|
177
177
|
Reprocess tracked records to a previous step in their process. Reprocessing is controlled by ReturnPoint records
|
|
178
178
|
which are children of the AssignedProcess on the tracked records. Creates a new AssignedProcess record for the
|
|
@@ -187,6 +187,6 @@ class ProcessTracking:
|
|
|
187
187
|
payload = {
|
|
188
188
|
"record-ids": AliasUtil.to_record_ids(records)
|
|
189
189
|
}
|
|
190
|
-
user: SapioUser =
|
|
190
|
+
user: SapioUser = AliasUtil.to_sapio_user(context)
|
|
191
191
|
response = user.post(sub_path, payload=payload)
|
|
192
192
|
user.raise_for_status(response)
|
|
@@ -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,7 +91,7 @@ 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:
|
|
94
|
+
def query_models(self, wrapper_type: type[WrappedType], field: FieldIdentifier, value_list: Iterable[FieldValue],
|
|
95
95
|
page_limit: 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
|
|
@@ -105,9 +105,9 @@ class RecordHandler:
|
|
|
105
105
|
"""
|
|
106
106
|
return self.query_models_with_criteria(wrapper_type, field, value_list, None, page_limit)[0]
|
|
107
107
|
|
|
108
|
-
def query_and_map_models(self, wrapper_type: type[WrappedType], field:
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
def query_and_map_models(self, wrapper_type: type[WrappedType], field: FieldIdentifier,
|
|
109
|
+
value_list: Iterable[FieldValue], page_limit: int | None = None,
|
|
110
|
+
*, mapping_field: FieldIdentifier | None = None) -> dict[FieldValue, list[WrappedType]]:
|
|
111
111
|
"""
|
|
112
112
|
Shorthand for using query_models to search for records given values on a specific field and then using
|
|
113
113
|
map_by_field to turn the returned list into a dictionary mapping field values to records.
|
|
@@ -123,9 +123,10 @@ class RecordHandler:
|
|
|
123
123
|
mapping_field = field
|
|
124
124
|
return self.map_by_field(self.query_models(wrapper_type, field, value_list, page_limit), mapping_field)
|
|
125
125
|
|
|
126
|
-
def query_and_unique_map_models(self, wrapper_type: type[WrappedType], field:
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
def query_and_unique_map_models(self, wrapper_type: type[WrappedType], field: FieldIdentifier,
|
|
127
|
+
value_list: Iterable[FieldValue], page_limit: int | None = None,
|
|
128
|
+
*, mapping_field: FieldIdentifier | None = None) \
|
|
129
|
+
-> dict[FieldValue, WrappedType]:
|
|
129
130
|
"""
|
|
130
131
|
Shorthand for using query_models to search for records given values on a specific field and then using
|
|
131
132
|
map_by_unique_field to turn the returned list into a dictionary mapping field values to records.
|
|
@@ -142,7 +143,8 @@ class RecordHandler:
|
|
|
142
143
|
mapping_field = field
|
|
143
144
|
return self.map_by_unique_field(self.query_models(wrapper_type, field, value_list, page_limit), mapping_field)
|
|
144
145
|
|
|
145
|
-
def query_models_with_criteria(self, wrapper_type: type[WrappedType], field:
|
|
146
|
+
def query_models_with_criteria(self, wrapper_type: type[WrappedType], field: FieldIdentifier,
|
|
147
|
+
value_list: Iterable[FieldValue],
|
|
146
148
|
paging_criteria: DataRecordPojoPageCriteria | None = None,
|
|
147
149
|
page_limit: int | None = None) \
|
|
148
150
|
-> tuple[list[WrappedType], DataRecordPojoPageCriteria]:
|
|
@@ -159,6 +161,7 @@ class RecordHandler:
|
|
|
159
161
|
:return: The record models for the queried records and the final paging criteria.
|
|
160
162
|
"""
|
|
161
163
|
dt: str = wrapper_type.get_wrapper_data_type_name()
|
|
164
|
+
field: str = AliasUtil.to_data_field_name(field)
|
|
162
165
|
pager = QueryDataRecordsAutoPager(dt, field, list(value_list), self.user, paging_criteria)
|
|
163
166
|
pager.max_page = page_limit
|
|
164
167
|
return self.wrap_models(pager.get_all_at_once(), wrapper_type), pager.next_page_criteria
|
|
@@ -241,7 +244,7 @@ class RecordHandler:
|
|
|
241
244
|
|
|
242
245
|
def query_models_by_report(self, wrapper_type: type[WrappedType],
|
|
243
246
|
report_name: str | RawReportTerm | CustomReportCriteria,
|
|
244
|
-
filters: dict[
|
|
247
|
+
filters: dict[FieldIdentifierKey, Iterable[FieldValue]] | None = None,
|
|
245
248
|
page_limit: int | None = None,
|
|
246
249
|
page_size: int | None = None,
|
|
247
250
|
page_number: int | None = None) -> list[WrappedType]:
|
|
@@ -269,11 +272,11 @@ class RecordHandler:
|
|
|
269
272
|
:return: The record models for the queried records that matched the given report.
|
|
270
273
|
"""
|
|
271
274
|
if isinstance(report_name, str):
|
|
272
|
-
results: list[dict[str,
|
|
273
|
-
|
|
275
|
+
results: list[dict[str, FieldValue]] = CustomReportUtil.run_system_report(self.user, report_name, filters,
|
|
276
|
+
page_limit, page_size, page_number)
|
|
274
277
|
elif isinstance(report_name, RawReportTerm):
|
|
275
|
-
results: list[dict[str,
|
|
276
|
-
|
|
278
|
+
results: list[dict[str, FieldValue]] = CustomReportUtil.run_quick_report(self.user, report_name, filters,
|
|
279
|
+
page_limit, page_size, page_number)
|
|
277
280
|
elif isinstance(report_name, CustomReportCriteria):
|
|
278
281
|
dt: str = wrapper_type.get_wrapper_data_type_name()
|
|
279
282
|
# Ensure that the root data type is the one we're looking for.
|
|
@@ -284,8 +287,8 @@ class RecordHandler:
|
|
|
284
287
|
# Enforce that the given custom report has a record ID column.
|
|
285
288
|
if not any([x.data_type_name == dt and x.data_field_name == "RecordId" for x in report_name.column_list]):
|
|
286
289
|
report_name.column_list.append(ReportColumn(dt, "RecordId", FieldType.LONG))
|
|
287
|
-
results: list[dict[str,
|
|
288
|
-
|
|
290
|
+
results: list[dict[str, FieldValue]] = CustomReportUtil.run_custom_report(self.user, report_name, filters,
|
|
291
|
+
page_limit, page_size, page_number)
|
|
289
292
|
else:
|
|
290
293
|
raise SapioException("Unrecognized report object.")
|
|
291
294
|
|
|
@@ -314,7 +317,8 @@ class RecordHandler:
|
|
|
314
317
|
"""
|
|
315
318
|
return self.inst_man.add_new_records_of_type(num, wrapper_type)
|
|
316
319
|
|
|
317
|
-
def add_models_with_data(self, wrapper_type: type[WrappedType], fields: list[
|
|
320
|
+
def add_models_with_data(self, wrapper_type: type[WrappedType], fields: list[FieldIdentifierMap]) \
|
|
321
|
+
-> list[WrappedType]:
|
|
318
322
|
"""
|
|
319
323
|
Shorthand for using the instance manager to add new models of the given type, and then initializing all those
|
|
320
324
|
models with the given fields.
|
|
@@ -324,13 +328,14 @@ class RecordHandler:
|
|
|
324
328
|
:return: The newly added record models with the provided fields set. The records will be in the same order as
|
|
325
329
|
the fields in the fields list.
|
|
326
330
|
"""
|
|
331
|
+
fields: list[FieldMap] = AliasUtil.to_data_field_names_list_dict(fields)
|
|
327
332
|
models: list[WrappedType] = self.add_models(wrapper_type, len(fields))
|
|
328
333
|
for model, field_list in zip(models, fields):
|
|
329
334
|
model.set_field_values(field_list)
|
|
330
335
|
return models
|
|
331
336
|
|
|
332
|
-
def find_or_add_model(self, wrapper_type: type[WrappedType], primary_identifier:
|
|
333
|
-
secondary_identifiers:
|
|
337
|
+
def find_or_add_model(self, wrapper_type: type[WrappedType], primary_identifier: FieldIdentifier,
|
|
338
|
+
id_value: FieldValue, secondary_identifiers: FieldIdentifierMap | None = None) -> WrappedType:
|
|
334
339
|
"""
|
|
335
340
|
Find a unique record that matches the given field values. If no such records exist, add a record model to the
|
|
336
341
|
cache with the identifying fields set to the desired values. This record will be created in the system when
|
|
@@ -353,6 +358,8 @@ class RecordHandler:
|
|
|
353
358
|
if secondary_identifiers is None:
|
|
354
359
|
secondary_identifiers = {}
|
|
355
360
|
|
|
361
|
+
primary_identifier: str = AliasUtil.to_data_field_name(primary_identifier)
|
|
362
|
+
secondary_identifiers: FieldMap = AliasUtil.to_data_field_names_dict(secondary_identifiers)
|
|
356
363
|
unique_record: WrappedType | None = self.__find_model(wrapper_type, primary_identifier, id_value,
|
|
357
364
|
secondary_identifiers)
|
|
358
365
|
# If a unique record matched the identifiers, return it.
|
|
@@ -379,7 +386,7 @@ class RecordHandler:
|
|
|
379
386
|
dt: str = wrapper_type.get_wrapper_data_type_name()
|
|
380
387
|
return self.wrap_models(self.dr_man.add_data_records(dt, num), wrapper_type)
|
|
381
388
|
|
|
382
|
-
def create_models_with_data(self, wrapper_type: type[WrappedType], fields: list[
|
|
389
|
+
def create_models_with_data(self, wrapper_type: type[WrappedType], fields: list[FieldIdentifierMap]) \
|
|
383
390
|
-> list[WrappedType]:
|
|
384
391
|
"""
|
|
385
392
|
Shorthand for creating new records via the data record manager with field data to initialize the records with
|
|
@@ -393,10 +400,12 @@ class RecordHandler:
|
|
|
393
400
|
:return: The newly created record models.
|
|
394
401
|
"""
|
|
395
402
|
dt: str = wrapper_type.get_wrapper_data_type_name()
|
|
403
|
+
fields: list[FieldMap] = AliasUtil.to_data_field_names_list_dict(fields)
|
|
396
404
|
return self.wrap_models(self.dr_man.add_data_records_with_data(dt, fields), wrapper_type)
|
|
397
405
|
|
|
398
|
-
def find_or_create_model(self, wrapper_type: type[WrappedType], primary_identifier:
|
|
399
|
-
secondary_identifiers:
|
|
406
|
+
def find_or_create_model(self, wrapper_type: type[WrappedType], primary_identifier: FieldIdentifier,
|
|
407
|
+
id_value: FieldValue, secondary_identifiers: FieldIdentifierMap | None = None) \
|
|
408
|
+
-> WrappedType:
|
|
400
409
|
"""
|
|
401
410
|
Find a unique record that matches the given field values. If no such records exist, create one with the
|
|
402
411
|
identifying fields set to the desired values. If more than one record with the identifying values exists,
|
|
@@ -420,6 +429,8 @@ class RecordHandler:
|
|
|
420
429
|
if secondary_identifiers is None:
|
|
421
430
|
secondary_identifiers = {}
|
|
422
431
|
|
|
432
|
+
primary_identifier: str = AliasUtil.to_data_field_name(primary_identifier)
|
|
433
|
+
secondary_identifiers: FieldMap = AliasUtil.to_data_field_names_dict(secondary_identifiers)
|
|
423
434
|
unique_record: WrappedType | None = self.__find_model(wrapper_type, primary_identifier, id_value,
|
|
424
435
|
secondary_identifiers)
|
|
425
436
|
# If a unique record matched the identifiers, return it.
|
|
@@ -579,7 +590,7 @@ class RecordHandler:
|
|
|
579
590
|
return by_children
|
|
580
591
|
|
|
581
592
|
@staticmethod
|
|
582
|
-
def map_to_forward_side_link(models: Iterable[WrappedRecordModel], field_name:
|
|
593
|
+
def map_to_forward_side_link(models: Iterable[WrappedRecordModel], field_name: FieldIdentifier,
|
|
583
594
|
side_link_type: type[WrappedType]) -> dict[WrappedRecordModel, WrappedType]:
|
|
584
595
|
"""
|
|
585
596
|
Map a list of record models to their forward side link. The forward side link must already be loaded.
|
|
@@ -590,13 +601,14 @@ class RecordHandler:
|
|
|
590
601
|
:return: A dict[ModelType, SlideLink]. If an input model doesn't have a forward side link of the given type,
|
|
591
602
|
then it will map to None.
|
|
592
603
|
"""
|
|
604
|
+
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
593
605
|
return_dict: dict[WrappedRecordModel, WrappedType] = {}
|
|
594
606
|
for model in models:
|
|
595
607
|
return_dict[model] = model.get_forward_side_link(field_name, side_link_type)
|
|
596
608
|
return return_dict
|
|
597
609
|
|
|
598
610
|
@staticmethod
|
|
599
|
-
def map_by_forward_side_links(models: Iterable[WrappedRecordModel], field_name:
|
|
611
|
+
def map_by_forward_side_links(models: Iterable[WrappedRecordModel], field_name: FieldIdentifier,
|
|
600
612
|
side_link_type: type[WrappedType]) -> dict[WrappedType, list[WrappedRecordModel]]:
|
|
601
613
|
"""
|
|
602
614
|
Take a list of record models and map them by their forward side link. Essentially an inversion of
|
|
@@ -609,6 +621,7 @@ class RecordHandler:
|
|
|
609
621
|
:return: A dict[SideLink, list[ModelType]]. If an input model doesn't have a forward side link of the given type
|
|
610
622
|
pointing to it, then it will not be in the resulting dictionary.
|
|
611
623
|
"""
|
|
624
|
+
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
612
625
|
to_side_link: dict[WrappedRecordModel, WrappedType] = RecordHandler\
|
|
613
626
|
.map_to_forward_side_link(models, field_name, side_link_type)
|
|
614
627
|
by_side_link: dict[WrappedType, list[WrappedRecordModel]] = {}
|
|
@@ -619,7 +632,7 @@ class RecordHandler:
|
|
|
619
632
|
return by_side_link
|
|
620
633
|
|
|
621
634
|
@staticmethod
|
|
622
|
-
def map_by_forward_side_link(models: Iterable[WrappedRecordModel], field_name:
|
|
635
|
+
def map_by_forward_side_link(models: Iterable[WrappedRecordModel], field_name: FieldIdentifier,
|
|
623
636
|
side_link_type: type[WrappedType]) -> dict[WrappedType, WrappedRecordModel]:
|
|
624
637
|
"""
|
|
625
638
|
Take a list of record models and map them by their forward side link. Essentially an inversion of
|
|
@@ -632,6 +645,7 @@ class RecordHandler:
|
|
|
632
645
|
:return: A dict[SideLink, ModelType]. If an input model doesn't have a forward side link of the given type
|
|
633
646
|
pointing to it, then it will not be in the resulting dictionary.
|
|
634
647
|
"""
|
|
648
|
+
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
635
649
|
to_side_link: dict[WrappedRecordModel, WrappedType] = RecordHandler\
|
|
636
650
|
.map_to_forward_side_link(models, field_name, side_link_type)
|
|
637
651
|
by_side_link: dict[WrappedType, WrappedRecordModel] = {}
|
|
@@ -645,7 +659,7 @@ class RecordHandler:
|
|
|
645
659
|
return by_side_link
|
|
646
660
|
|
|
647
661
|
@staticmethod
|
|
648
|
-
def map_to_reverse_side_links(models: Iterable[WrappedRecordModel], field_name:
|
|
662
|
+
def map_to_reverse_side_links(models: Iterable[WrappedRecordModel], field_name: FieldIdentifier,
|
|
649
663
|
side_link_type: type[WrappedType]) -> dict[WrappedRecordModel, list[WrappedType]]:
|
|
650
664
|
"""
|
|
651
665
|
Map a list of record models to a list reverse side links of a given type. The reverse side links must already
|
|
@@ -658,13 +672,14 @@ class RecordHandler:
|
|
|
658
672
|
:return: A dict[ModelType, list[SideLink]]. If an input model doesn't have reverse side links of the given type,
|
|
659
673
|
then it will map to an empty list.
|
|
660
674
|
"""
|
|
675
|
+
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
661
676
|
return_dict: dict[WrappedRecordModel, list[WrappedType]] = {}
|
|
662
677
|
for model in models:
|
|
663
678
|
return_dict[model] = model.get_reverse_side_link(field_name, side_link_type)
|
|
664
679
|
return return_dict
|
|
665
680
|
|
|
666
681
|
@staticmethod
|
|
667
|
-
def map_to_reverse_side_link(models: Iterable[WrappedRecordModel], field_name:
|
|
682
|
+
def map_to_reverse_side_link(models: Iterable[WrappedRecordModel], field_name: FieldIdentifier,
|
|
668
683
|
side_link_type: type[WrappedType]) -> dict[WrappedRecordModel, WrappedType]:
|
|
669
684
|
"""
|
|
670
685
|
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 +692,7 @@ class RecordHandler:
|
|
|
677
692
|
:return: A dict[ModelType, SideLink]. If an input model doesn't have reverse side links of the given type,
|
|
678
693
|
then it will map to None.
|
|
679
694
|
"""
|
|
695
|
+
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
680
696
|
return_dict: dict[WrappedRecordModel, WrappedType] = {}
|
|
681
697
|
for model in models:
|
|
682
698
|
links: list[WrappedType] = model.get_reverse_side_link(field_name, side_link_type)
|
|
@@ -687,7 +703,7 @@ class RecordHandler:
|
|
|
687
703
|
return return_dict
|
|
688
704
|
|
|
689
705
|
@staticmethod
|
|
690
|
-
def map_by_reverse_side_links(models: Iterable[WrappedRecordModel], field_name:
|
|
706
|
+
def map_by_reverse_side_links(models: Iterable[WrappedRecordModel], field_name: FieldIdentifier,
|
|
691
707
|
side_link_type: type[WrappedType]) -> dict[WrappedType, list[WrappedRecordModel]]:
|
|
692
708
|
"""
|
|
693
709
|
Take a list of record models and map them by their reverse side links. Essentially an inversion of
|
|
@@ -701,6 +717,7 @@ class RecordHandler:
|
|
|
701
717
|
:return: A dict[SideLink, list[ModelType]]. If an input model doesn't have reverse side links of the given type
|
|
702
718
|
pointing to it, then it will not be in the resulting dictionary.
|
|
703
719
|
"""
|
|
720
|
+
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
704
721
|
to_side_links: dict[WrappedRecordModel, list[WrappedType]] = RecordHandler\
|
|
705
722
|
.map_to_reverse_side_links(models, field_name, side_link_type)
|
|
706
723
|
by_side_links: dict[WrappedType, list[WrappedRecordModel]] = {}
|
|
@@ -710,7 +727,7 @@ class RecordHandler:
|
|
|
710
727
|
return by_side_links
|
|
711
728
|
|
|
712
729
|
@staticmethod
|
|
713
|
-
def map_by_reverse_side_link(models: Iterable[WrappedRecordModel], field_name:
|
|
730
|
+
def map_by_reverse_side_link(models: Iterable[WrappedRecordModel], field_name: FieldIdentifier,
|
|
714
731
|
side_link_type: type[WrappedType]) -> dict[WrappedType, WrappedRecordModel]:
|
|
715
732
|
"""
|
|
716
733
|
Take a list of record models and map them by their reverse side link. Essentially an inversion of
|
|
@@ -724,6 +741,7 @@ class RecordHandler:
|
|
|
724
741
|
:return: A dict[SideLink, ModelType]. If an input model doesn't have a reverse side link of the given type
|
|
725
742
|
pointing to it, then it will not be in the resulting dictionary.
|
|
726
743
|
"""
|
|
744
|
+
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
727
745
|
to_side_link: dict[WrappedRecordModel, WrappedType] = RecordHandler\
|
|
728
746
|
.map_to_reverse_side_link(models, field_name, side_link_type)
|
|
729
747
|
by_side_link: dict[WrappedType, WrappedRecordModel] = {}
|
|
@@ -750,7 +768,8 @@ class RecordHandler:
|
|
|
750
768
|
return ret_dict
|
|
751
769
|
|
|
752
770
|
@staticmethod
|
|
753
|
-
def map_by_field(models: Iterable[SapioRecord], field_name:
|
|
771
|
+
def map_by_field(models: Iterable[SapioRecord], field_name: FieldIdentifier) \
|
|
772
|
+
-> dict[FieldValue, list[SapioRecord]]:
|
|
754
773
|
"""
|
|
755
774
|
Map the given records by one of their fields. If any two records share the same field value, they'll appear in
|
|
756
775
|
the same value list.
|
|
@@ -759,14 +778,16 @@ class RecordHandler:
|
|
|
759
778
|
:param field_name: The field name to map against.
|
|
760
779
|
:return: A dict mapping field values to the records with that value.
|
|
761
780
|
"""
|
|
762
|
-
|
|
781
|
+
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
782
|
+
ret_dict: dict[FieldValue, list[SapioRecord]] = {}
|
|
763
783
|
for model in models:
|
|
764
|
-
val:
|
|
784
|
+
val: FieldValue = model.get_field_value(field_name)
|
|
765
785
|
ret_dict.setdefault(val, []).append(model)
|
|
766
786
|
return ret_dict
|
|
767
787
|
|
|
768
788
|
@staticmethod
|
|
769
|
-
def map_by_unique_field(models: Iterable[SapioRecord], field_name:
|
|
789
|
+
def map_by_unique_field(models: Iterable[SapioRecord], field_name: FieldIdentifier) \
|
|
790
|
+
-> dict[FieldValue, SapioRecord]:
|
|
770
791
|
"""
|
|
771
792
|
Uniquely map the given records by one of their fields. If any two records share the same field value, throws
|
|
772
793
|
an exception.
|
|
@@ -775,16 +796,17 @@ class RecordHandler:
|
|
|
775
796
|
:param field_name: The field name to map against.
|
|
776
797
|
:return: A dict mapping field values to the record with that value.
|
|
777
798
|
"""
|
|
778
|
-
|
|
799
|
+
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
800
|
+
ret_dict: dict[FieldValue, SapioRecord] = {}
|
|
779
801
|
for model in models:
|
|
780
|
-
val:
|
|
802
|
+
val: FieldValue = model.get_field_value(field_name)
|
|
781
803
|
if val in ret_dict:
|
|
782
804
|
raise SapioException(f"Value {val} encountered more than once in models list.")
|
|
783
805
|
ret_dict.update({val: model})
|
|
784
806
|
return ret_dict
|
|
785
807
|
|
|
786
808
|
@staticmethod
|
|
787
|
-
def sum_of_field(models: Iterable[SapioRecord], field_name:
|
|
809
|
+
def sum_of_field(models: Iterable[SapioRecord], field_name: FieldIdentifier) -> float:
|
|
788
810
|
"""
|
|
789
811
|
Sum up the numeric value of a given field across all input models. Excepts that all given models have a value.
|
|
790
812
|
If the field is an integer field, the value will be converted to a float.
|
|
@@ -793,13 +815,14 @@ class RecordHandler:
|
|
|
793
815
|
:param field_name: The name of the numeric field to sum.
|
|
794
816
|
:return: The sum of the field values for the collection of models.
|
|
795
817
|
"""
|
|
818
|
+
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
796
819
|
field_sum: float = 0
|
|
797
820
|
for model in models:
|
|
798
821
|
field_sum += float(model.get_field_value(field_name))
|
|
799
822
|
return field_sum
|
|
800
823
|
|
|
801
824
|
@staticmethod
|
|
802
|
-
def mean_of_field(models: Iterable[SapioRecord], field_name:
|
|
825
|
+
def mean_of_field(models: Iterable[SapioRecord], field_name: FieldIdentifier) -> float:
|
|
803
826
|
"""
|
|
804
827
|
Calculate the mean of the numeric value of a given field across all input models. Excepts that all given models
|
|
805
828
|
have a value. If the field is an integer field, the value will be converted to a float.
|
|
@@ -840,8 +863,8 @@ class RecordHandler:
|
|
|
840
863
|
return oldest
|
|
841
864
|
|
|
842
865
|
@staticmethod
|
|
843
|
-
def values_to_field_maps(field_name:
|
|
844
|
-
|
|
866
|
+
def values_to_field_maps(field_name: FieldIdentifier, values: Iterable[FieldValue],
|
|
867
|
+
existing_fields: list[FieldIdentifier] | None = None) -> list[FieldMap]:
|
|
845
868
|
"""
|
|
846
869
|
Add a list of values for a specific field to a list of dictionaries pairing each value to that field name.
|
|
847
870
|
|
|
@@ -852,6 +875,8 @@ class RecordHandler:
|
|
|
852
875
|
:return: A fields map list that contains the given values mapped by the given field name.
|
|
853
876
|
"""
|
|
854
877
|
# Update the existing fields map list if one is given.
|
|
878
|
+
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
879
|
+
existing_fields: list[FieldMap] = AliasUtil.to_data_field_names_list_dict(existing_fields)
|
|
855
880
|
if existing_fields:
|
|
856
881
|
values = list(values)
|
|
857
882
|
# The number of new values must match the length of the existing fields list.
|
|
@@ -1016,8 +1041,8 @@ class RecordHandler:
|
|
|
1016
1041
|
ret_dict.update({model: self.inst_man.wrap(current[0], wrapper_type) if current else None})
|
|
1017
1042
|
return ret_dict
|
|
1018
1043
|
|
|
1019
|
-
def __find_model(self, wrapper_type: type[WrappedType], primary_identifier: str, id_value:
|
|
1020
|
-
secondary_identifiers:
|
|
1044
|
+
def __find_model(self, wrapper_type: type[WrappedType], primary_identifier: str, id_value: FieldValue,
|
|
1045
|
+
secondary_identifiers: FieldIdentifierMap | None = None) -> WrappedType | None:
|
|
1021
1046
|
"""
|
|
1022
1047
|
Find a record from the system that matches the given field values. The primary identifier and value is used
|
|
1023
1048
|
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
|
|