sapiopycommons 2024.8.2a301__py3-none-any.whl → 2024.8.15a304__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 +22 -59
- sapiopycommons/datatype/attachment_util.py +6 -15
- sapiopycommons/eln/experiment_handler.py +29 -66
- sapiopycommons/files/complex_data_loader.py +1 -1
- sapiopycommons/files/file_bridge.py +1 -1
- sapiopycommons/files/file_util.py +4 -13
- sapiopycommons/files/file_validator.py +2 -1
- sapiopycommons/general/aliases.py +1 -21
- sapiopycommons/recordmodel/record_handler.py +16 -78
- sapiopycommons/rules/eln_rule_handler.py +22 -6
- sapiopycommons/rules/on_save_rule_handler.py +28 -6
- sapiopycommons/webhook/webhook_handlers.py +23 -58
- {sapiopycommons-2024.8.2a301.dist-info → sapiopycommons-2024.8.15a304.dist-info}/METADATA +1 -1
- {sapiopycommons-2024.8.2a301.dist-info → sapiopycommons-2024.8.15a304.dist-info}/RECORD +16 -18
- sapiopycommons/general/audit_log.py +0 -200
- sapiopycommons/general/sapio_links.py +0 -48
- {sapiopycommons-2024.8.2a301.dist-info → sapiopycommons-2024.8.15a304.dist-info}/WHEEL +0 -0
- {sapiopycommons-2024.8.2a301.dist-info → sapiopycommons-2024.8.15a304.dist-info}/licenses/LICENSE +0 -0
|
@@ -7,7 +7,6 @@ from sapiopylib.rest.pojo.CustomReport import CustomReportCriteria, RawReportTer
|
|
|
7
7
|
from sapiopylib.rest.pojo.DataRecord import DataRecord
|
|
8
8
|
from sapiopylib.rest.pojo.DataRecordPaging import DataRecordPojoPageCriteria
|
|
9
9
|
from sapiopylib.rest.pojo.datatype.FieldDefinition import FieldType
|
|
10
|
-
from sapiopylib.rest.pojo.eln.SapioELNEnums import ElnBaseDataType
|
|
11
10
|
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
12
11
|
from sapiopylib.rest.utils.autopaging import QueryDataRecordsAutoPager, QueryDataRecordByIdListAutoPager, \
|
|
13
12
|
QueryAllRecordsOfTypeAutoPager
|
|
@@ -17,7 +16,6 @@ from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelMana
|
|
|
17
16
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType, WrappedRecordModel
|
|
18
17
|
from sapiopylib.rest.utils.recordmodel.RelationshipPath import RelationshipPath, RelationshipNode, \
|
|
19
18
|
RelationshipNodeType
|
|
20
|
-
from sapiopylib.rest.utils.recordmodel.ancestry import RecordModelAncestorManager
|
|
21
19
|
|
|
22
20
|
from sapiopycommons.general.aliases import RecordModel, SapioRecord, FieldMap
|
|
23
21
|
from sapiopycommons.general.custom_report_util import CustomReportUtil
|
|
@@ -34,7 +32,6 @@ class RecordHandler:
|
|
|
34
32
|
rec_man: RecordModelManager
|
|
35
33
|
inst_man: RecordModelInstanceManager
|
|
36
34
|
rel_man: RecordModelRelationshipManager
|
|
37
|
-
an_man: RecordModelAncestorManager
|
|
38
35
|
|
|
39
36
|
def __init__(self, context: SapioWebhookContext | SapioUser):
|
|
40
37
|
"""
|
|
@@ -45,7 +42,6 @@ class RecordHandler:
|
|
|
45
42
|
self.rec_man = RecordModelManager(self.user)
|
|
46
43
|
self.inst_man = self.rec_man.instance_manager
|
|
47
44
|
self.rel_man = self.rec_man.relationship_manager
|
|
48
|
-
self.an_man = RecordModelAncestorManager(self.rec_man)
|
|
49
45
|
|
|
50
46
|
def wrap_model(self, record: DataRecord, wrapper_type: type[WrappedType]) -> WrappedType:
|
|
51
47
|
"""
|
|
@@ -55,7 +51,6 @@ class RecordHandler:
|
|
|
55
51
|
:param wrapper_type: The record model wrapper to use.
|
|
56
52
|
:return: The record model for the input.
|
|
57
53
|
"""
|
|
58
|
-
self.__verify_data_type([record], wrapper_type)
|
|
59
54
|
return self.inst_man.add_existing_record_of_type(record, wrapper_type)
|
|
60
55
|
|
|
61
56
|
def wrap_models(self, records: Iterable[DataRecord], wrapper_type: type[WrappedType]) -> list[WrappedType]:
|
|
@@ -66,7 +61,6 @@ class RecordHandler:
|
|
|
66
61
|
:param wrapper_type: The record model wrapper to use.
|
|
67
62
|
:return: The record models for the input.
|
|
68
63
|
"""
|
|
69
|
-
self.__verify_data_type(records, wrapper_type)
|
|
70
64
|
return self.inst_man.add_existing_records_of_type(list(records), wrapper_type)
|
|
71
65
|
|
|
72
66
|
def query_models(self, wrapper_type: type[WrappedType], field: str, value_list: Iterable[Any],
|
|
@@ -837,6 +831,8 @@ class RecordHandler:
|
|
|
837
831
|
path, if any. The hierarchy must be linear (1:1 relationship between data types at every step) and the
|
|
838
832
|
relationship path must already be loaded.
|
|
839
833
|
|
|
834
|
+
Currently, the relationship path may only contain parent/child nodes.
|
|
835
|
+
|
|
840
836
|
:param models: A list of record models.
|
|
841
837
|
:param path: The relationship path to follow.
|
|
842
838
|
:param wrapper_type: The record model wrapper to use.
|
|
@@ -847,44 +843,15 @@ class RecordHandler:
|
|
|
847
843
|
# PR-46832: Update path traversal to account for changes to RelationshipPath in Sapiopylib.
|
|
848
844
|
path: list[RelationshipNode] = path.path
|
|
849
845
|
for model in models:
|
|
850
|
-
current: PyRecordModel
|
|
846
|
+
current: PyRecordModel = model if isinstance(model, PyRecordModel) else model.backing_model
|
|
851
847
|
for node in path:
|
|
852
|
-
|
|
853
|
-
direction: RelationshipNodeType = node.direction
|
|
848
|
+
direction = node.direction
|
|
854
849
|
if current is None:
|
|
855
850
|
break
|
|
856
851
|
if direction == RelationshipNodeType.CHILD:
|
|
857
|
-
current = current.get_child_of_type(
|
|
852
|
+
current = current.get_child_of_type(node.data_type_name)
|
|
858
853
|
elif direction == RelationshipNodeType.PARENT:
|
|
859
|
-
current = current.get_parent_of_type(
|
|
860
|
-
elif direction == RelationshipNodeType.ANCESTOR:
|
|
861
|
-
ancestors: list[PyRecordModel] = list(self.an_man.get_ancestors_of_type(current, data_type))
|
|
862
|
-
if not ancestors:
|
|
863
|
-
current = None
|
|
864
|
-
elif len(ancestors) > 1:
|
|
865
|
-
raise SapioException(f"Hierarchy contains multiple ancestors of type {data_type}.")
|
|
866
|
-
else:
|
|
867
|
-
current = ancestors[0]
|
|
868
|
-
elif direction == RelationshipNodeType.DESCENDANT:
|
|
869
|
-
descendants: list[PyRecordModel] = list(self.an_man.get_descendant_of_type(current, data_type))
|
|
870
|
-
if not descendants:
|
|
871
|
-
current = None
|
|
872
|
-
elif len(descendants) > 1:
|
|
873
|
-
raise SapioException(f"Hierarchy contains multiple descendants of type {data_type}.")
|
|
874
|
-
else:
|
|
875
|
-
current = descendants[0]
|
|
876
|
-
elif direction == RelationshipNodeType.FORWARD_SIDE_LINK:
|
|
877
|
-
current = current.get_forward_side_link(node.data_field_name)
|
|
878
|
-
elif direction == RelationshipNodeType.REVERSE_SIDE_LINK:
|
|
879
|
-
field_name: str = node.data_field_name
|
|
880
|
-
reverse_links: list[PyRecordModel] = current.get_reverse_side_link(field_name, data_type)
|
|
881
|
-
if not reverse_links:
|
|
882
|
-
current = None
|
|
883
|
-
elif len(reverse_links) > 1:
|
|
884
|
-
raise SapioException(f"Hierarchy contains multiple reverse links of type {data_type} on field "
|
|
885
|
-
f"{field_name}.")
|
|
886
|
-
else:
|
|
887
|
-
current = reverse_links[0]
|
|
854
|
+
current = current.get_parent_of_type(node.data_type_name)
|
|
888
855
|
else:
|
|
889
856
|
raise SapioException("Unsupported path direction.")
|
|
890
857
|
ret_dict.update({model: self.inst_man.wrap(current, wrapper_type) if current else None})
|
|
@@ -897,6 +864,8 @@ class RecordHandler:
|
|
|
897
864
|
path, if any. The hierarchy may be non-linear (1:Many relationships between data types are allowed) and the
|
|
898
865
|
relationship path must already be loaded.
|
|
899
866
|
|
|
867
|
+
Currently, the relationship path may only contain parent/child nodes.
|
|
868
|
+
|
|
900
869
|
:param models: A list of record models.
|
|
901
870
|
:param path: The relationship path to follow.
|
|
902
871
|
:param wrapper_type: The record model wrapper to use.
|
|
@@ -911,23 +880,14 @@ class RecordHandler:
|
|
|
911
880
|
next_search: set[PyRecordModel] = set()
|
|
912
881
|
# Exhaust the records at each step in the path, then use those records for the next step.
|
|
913
882
|
for node in path:
|
|
914
|
-
|
|
915
|
-
direction: RelationshipNodeType = node.direction
|
|
883
|
+
direction = node.direction
|
|
916
884
|
if len(current_search) == 0:
|
|
917
885
|
break
|
|
918
886
|
for search in current_search:
|
|
919
887
|
if direction == RelationshipNodeType.CHILD:
|
|
920
|
-
next_search.update(search.get_children_of_type(
|
|
888
|
+
next_search.update(search.get_children_of_type(node.data_type_name))
|
|
921
889
|
elif direction == RelationshipNodeType.PARENT:
|
|
922
|
-
next_search.update(search.get_parents_of_type(
|
|
923
|
-
elif direction == RelationshipNodeType.ANCESTOR:
|
|
924
|
-
next_search.update(self.an_man.get_ancestors_of_type(search, data_type))
|
|
925
|
-
elif direction == RelationshipNodeType.DESCENDANT:
|
|
926
|
-
next_search.update(self.an_man.get_descendant_of_type(search, data_type))
|
|
927
|
-
elif direction == RelationshipNodeType.FORWARD_SIDE_LINK:
|
|
928
|
-
next_search.add(search.get_forward_side_link(node.data_field_name))
|
|
929
|
-
elif direction == RelationshipNodeType.REVERSE_SIDE_LINK:
|
|
930
|
-
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))
|
|
931
891
|
else:
|
|
932
892
|
raise SapioException("Unsupported path direction.")
|
|
933
893
|
current_search = next_search
|
|
@@ -948,6 +908,8 @@ class RecordHandler:
|
|
|
948
908
|
relationships (e.g. a sample which is aliquoted to a number of samples, then those aliquots are pooled back
|
|
949
909
|
together into a single sample).
|
|
950
910
|
|
|
911
|
+
Currently, the relationship path may only contain parent/child nodes.
|
|
912
|
+
|
|
951
913
|
:param models: A list of record models.
|
|
952
914
|
:param path: The relationship path to follow.
|
|
953
915
|
:param wrapper_type: The record model wrapper to use.
|
|
@@ -960,22 +922,13 @@ class RecordHandler:
|
|
|
960
922
|
for model in models:
|
|
961
923
|
current: list[PyRecordModel] = [model if isinstance(model, PyRecordModel) else model.backing_model]
|
|
962
924
|
for node in path:
|
|
963
|
-
|
|
964
|
-
direction: RelationshipNodeType = node.direction
|
|
925
|
+
direction = node.direction
|
|
965
926
|
if len(current) == 0:
|
|
966
927
|
break
|
|
967
928
|
if direction == RelationshipNodeType.CHILD:
|
|
968
|
-
current = current[0].get_children_of_type(
|
|
929
|
+
current = current[0].get_children_of_type(node.data_type_name)
|
|
969
930
|
elif direction == RelationshipNodeType.PARENT:
|
|
970
|
-
current = current[0].get_parents_of_type(
|
|
971
|
-
elif direction == RelationshipNodeType.ANCESTOR:
|
|
972
|
-
current = list(self.an_man.get_ancestors_of_type(current[0], data_type))
|
|
973
|
-
elif direction == RelationshipNodeType.DESCENDANT:
|
|
974
|
-
current = list(self.an_man.get_descendant_of_type(current[0], data_type))
|
|
975
|
-
elif direction == RelationshipNodeType.FORWARD_SIDE_LINK:
|
|
976
|
-
current = [current[0].get_forward_side_link(node.data_field_name)]
|
|
977
|
-
elif direction == RelationshipNodeType.REVERSE_SIDE_LINK:
|
|
978
|
-
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)
|
|
979
932
|
else:
|
|
980
933
|
raise SapioException("Unsupported path direction.")
|
|
981
934
|
ret_dict.update({model: self.inst_man.wrap(current[0], wrapper_type) if current else None})
|
|
@@ -1006,18 +959,3 @@ class RecordHandler:
|
|
|
1006
959
|
f"encountered in system that matches all provided identifiers.")
|
|
1007
960
|
unique_record = result
|
|
1008
961
|
return unique_record
|
|
1009
|
-
|
|
1010
|
-
@staticmethod
|
|
1011
|
-
def __verify_data_type(records: Iterable[DataRecord], wrapper_type: type[WrappedType]) -> None:
|
|
1012
|
-
"""
|
|
1013
|
-
Throw an exception if the data type of the given records and wrapper don't match.
|
|
1014
|
-
"""
|
|
1015
|
-
model_type: str = wrapper_type.get_wrapper_data_type_name()
|
|
1016
|
-
for record in records:
|
|
1017
|
-
record_type: str = record.data_type_name
|
|
1018
|
-
# Account for ELN data type records.
|
|
1019
|
-
if ElnBaseDataType.is_eln_type(record_type):
|
|
1020
|
-
record_type = ElnBaseDataType.get_base_type(record_type).data_type_name
|
|
1021
|
-
if record_type != model_type:
|
|
1022
|
-
raise SapioException(f"Data record of type {record_type} cannot be wrapped by the record model wrapper "
|
|
1023
|
-
f"of type {model_type}")
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from sapiopylib.rest.pojo.DataRecord import DataRecord
|
|
2
|
-
from sapiopylib.rest.pojo.
|
|
2
|
+
from sapiopylib.rest.pojo.webhook.VeloxRules import VeloxRuleType, VeloxRuleParser
|
|
3
3
|
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
4
4
|
from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelManager, RecordModelInstanceManager
|
|
5
5
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
|
|
@@ -12,6 +12,7 @@ from sapiopycommons.general.exceptions import SapioException
|
|
|
12
12
|
class ElnRuleHandler:
|
|
13
13
|
"""
|
|
14
14
|
A class which helps with the parsing and navigation of the ELN rule result map of a webhook context.
|
|
15
|
+
TODO: Add functionality around the VeloxRuleType of the rule results.
|
|
15
16
|
"""
|
|
16
17
|
__context: SapioWebhookContext
|
|
17
18
|
"""The context that this handler is working from."""
|
|
@@ -63,8 +64,13 @@ class ElnRuleHandler:
|
|
|
63
64
|
# Get the data type of this record. If this is an ELN type, ignore the digits.
|
|
64
65
|
data_type: str = record.data_type_name
|
|
65
66
|
# PR-46331: Ensure that all ELN types are converted to their base data type name.
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
# TODO: Use ElnBaseDataType.is_eln_type when it is no longer bugged in sapiopylib.
|
|
68
|
+
if data_type.startswith("ELNExperiment_"):
|
|
69
|
+
data_type = "ELNExperiment"
|
|
70
|
+
elif data_type.startswith("ELNExperimentDetail_"):
|
|
71
|
+
data_type = "ELNExperimentDetail"
|
|
72
|
+
elif data_type.startswith("ELNSampleDetail_"):
|
|
73
|
+
data_type = "ELNSampleDetail"
|
|
68
74
|
# Update the list of records of this type that exist so far globally.
|
|
69
75
|
self.__records.setdefault(data_type, set()).add(record)
|
|
70
76
|
# Do the same for the list of records of this type for this specific entry.
|
|
@@ -79,9 +85,19 @@ class ElnRuleHandler:
|
|
|
79
85
|
entry_dict: dict[str, dict[int, FieldMap]] = {}
|
|
80
86
|
for record_result in entry_results:
|
|
81
87
|
for result in record_result.velox_type_rule_field_map_result_list:
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
88
|
+
# TODO: sapiopylib currently has a bug where this velox_type_pojo variable is stored as a dict instead
|
|
89
|
+
# of as the intended VeloxRuleType object. Parse that dict as a VeloxRuleType before use.
|
|
90
|
+
velox_type: VeloxRuleType | dict = result.velox_type_pojo
|
|
91
|
+
if isinstance(velox_type, dict):
|
|
92
|
+
velox_type: VeloxRuleType = VeloxRuleParser.parse_velox_rule_type(velox_type)
|
|
93
|
+
data_type: str = velox_type.data_type_name
|
|
94
|
+
# TODO: Use ElnBaseDataType.is_eln_type when it is no longer bugged in sapiopylib.
|
|
95
|
+
if data_type.startswith("ELNExperiment_"):
|
|
96
|
+
data_type = "ELNExperiment"
|
|
97
|
+
elif data_type.startswith("ELNExperimentDetail_"):
|
|
98
|
+
data_type = "ELNExperimentDetail"
|
|
99
|
+
elif data_type.startswith("ELNSampleDetail_"):
|
|
100
|
+
data_type = "ELNSampleDetail"
|
|
85
101
|
for field_map in result.field_map_list:
|
|
86
102
|
rec_id: int = field_map.get("RecordId")
|
|
87
103
|
self.__field_maps.setdefault(data_type, {}).update({rec_id: field_map})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from sapiopylib.rest.pojo.DataRecord import DataRecord
|
|
2
|
-
from sapiopylib.rest.pojo.
|
|
2
|
+
from sapiopylib.rest.pojo.webhook.VeloxRules import VeloxRuleType, VeloxRuleParser
|
|
3
3
|
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
4
4
|
from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelManager, RecordModelInstanceManager
|
|
5
5
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
|
|
@@ -12,6 +12,7 @@ from sapiopycommons.general.exceptions import SapioException
|
|
|
12
12
|
class OnSaveRuleHandler:
|
|
13
13
|
"""
|
|
14
14
|
A class which helps with the parsing and navigation of the on save rule result map of a webhook context.
|
|
15
|
+
TODO: Add functionality around the VeloxRuleType of the rule results.
|
|
15
16
|
"""
|
|
16
17
|
__context: SapioWebhookContext
|
|
17
18
|
"""The context that this handler is working from."""
|
|
@@ -50,6 +51,9 @@ class OnSaveRuleHandler:
|
|
|
50
51
|
self.__base_id_to_records = {}
|
|
51
52
|
# Each record ID in the context has a list of results for that record.
|
|
52
53
|
for record_id, rule_results in self.__context.velox_on_save_result_map.items():
|
|
54
|
+
# TODO: Record IDs are currently being stored in the map as strings instead of ints. This can be removed
|
|
55
|
+
# once sapiopylib is fixed.
|
|
56
|
+
record_id = int(record_id)
|
|
53
57
|
# Keep track of the records for this specific record ID.
|
|
54
58
|
id_dict: dict[str, set[DataRecord]] = {}
|
|
55
59
|
# The list of results for a record consist of a list of data records and a VeloxType that specifies
|
|
@@ -60,8 +64,13 @@ class OnSaveRuleHandler:
|
|
|
60
64
|
# Get the data type of this record. If this is an ELN type, ignore the digits.
|
|
61
65
|
data_type: str = record.data_type_name
|
|
62
66
|
# PR-46331: Ensure that all ELN types are converted to their base data type name.
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
# TODO: Use ElnBaseDataType.is_eln_type when it is no longer bugged in sapiopylib.
|
|
68
|
+
if data_type.startswith("ELNExperiment_"):
|
|
69
|
+
data_type = "ELNExperiment"
|
|
70
|
+
elif data_type.startswith("ELNExperimentDetail_"):
|
|
71
|
+
data_type = "ELNExperimentDetail"
|
|
72
|
+
elif data_type.startswith("ELNSampleDetail_"):
|
|
73
|
+
data_type = "ELNSampleDetail"
|
|
65
74
|
# Update the list of records of this type that exist so far globally.
|
|
66
75
|
self.__records.setdefault(data_type, set()).add(record)
|
|
67
76
|
# Do the same for the list of records of this type that relate to this record ID.
|
|
@@ -73,11 +82,24 @@ class OnSaveRuleHandler:
|
|
|
73
82
|
self.__base_id_to_field_maps = {}
|
|
74
83
|
# Repeat the same thing for the field map results.
|
|
75
84
|
for record_id, rule_results in self.__context.velox_on_save_field_map_result_map.items():
|
|
85
|
+
# TODO: Record IDs are currently being stored in the map as strings instead of ints. This can be removed
|
|
86
|
+
# once sapiopylib is fixed.
|
|
87
|
+
record_id = int(record_id)
|
|
76
88
|
id_dict: dict[str, dict[int, FieldMap]] = {}
|
|
77
89
|
for record_result in rule_results:
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
90
|
+
# TODO: sapiopylib currently has a bug where this velox_type_pojo variable is stored as a dict instead
|
|
91
|
+
# of as the intended VeloxRuleType object. Parse that dict as a VeloxRuleType before use.
|
|
92
|
+
velox_type: VeloxRuleType | dict = record_result.velox_type_pojo
|
|
93
|
+
if isinstance(velox_type, dict):
|
|
94
|
+
velox_type: VeloxRuleType = VeloxRuleParser.parse_velox_rule_type(velox_type)
|
|
95
|
+
data_type: str = velox_type.data_type_name
|
|
96
|
+
# TODO: Use ElnBaseDataType.is_eln_type when it is no longer bugged in sapiopylib.
|
|
97
|
+
if data_type.startswith("ELNExperiment_"):
|
|
98
|
+
data_type = "ELNExperiment"
|
|
99
|
+
elif data_type.startswith("ELNExperimentDetail_"):
|
|
100
|
+
data_type = "ELNExperimentDetail"
|
|
101
|
+
elif data_type.startswith("ELNSampleDetail_"):
|
|
102
|
+
data_type = "ELNSampleDetail"
|
|
81
103
|
for field_map in record_result.field_map_list:
|
|
82
104
|
rec_id: int = field_map.get("RecordId")
|
|
83
105
|
self.__field_maps.setdefault(data_type, {}).update({rec_id: field_map})
|
|
@@ -4,9 +4,7 @@ from logging import Logger
|
|
|
4
4
|
|
|
5
5
|
from sapiopylib.rest.DataMgmtService import DataMgmtServer
|
|
6
6
|
from sapiopylib.rest.DataRecordManagerService import DataRecordManager
|
|
7
|
-
from sapiopylib.rest.User import SapioUser
|
|
8
7
|
from sapiopylib.rest.WebhookService import AbstractWebhookHandler
|
|
9
|
-
from sapiopylib.rest.pojo.Message import VeloxLogMessage, VeloxLogLevel
|
|
10
8
|
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
11
9
|
from sapiopylib.rest.pojo.webhook.WebhookEnums import WebhookEndpointType
|
|
12
10
|
from sapiopylib.rest.pojo.webhook.WebhookResult import SapioWebhookResult
|
|
@@ -16,7 +14,6 @@ from sapiopylib.rest.utils.recordmodel.ancestry import RecordModelAncestorManage
|
|
|
16
14
|
|
|
17
15
|
from sapiopycommons.general.exceptions import SapioUserErrorException, SapioCriticalErrorException, \
|
|
18
16
|
SapioUserCancelledException
|
|
19
|
-
from sapiopycommons.general.sapio_links import SapioNavigationLinker
|
|
20
17
|
|
|
21
18
|
|
|
22
19
|
# FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
|
|
@@ -28,7 +25,6 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
|
|
|
28
25
|
"""
|
|
29
26
|
logger: Logger
|
|
30
27
|
|
|
31
|
-
user: SapioUser
|
|
32
28
|
context: SapioWebhookContext
|
|
33
29
|
|
|
34
30
|
dr_man: DataRecordManager
|
|
@@ -39,13 +35,11 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
|
|
|
39
35
|
an_man: RecordModelAncestorManager
|
|
40
36
|
|
|
41
37
|
def run(self, context: SapioWebhookContext) -> SapioWebhookResult:
|
|
42
|
-
self.user = context.user
|
|
43
38
|
self.context = context
|
|
44
|
-
|
|
45
|
-
self.logger = self.user.logger
|
|
39
|
+
self.logger = context.user.logger
|
|
46
40
|
|
|
47
41
|
self.dr_man = context.data_record_manager
|
|
48
|
-
self.rec_man = RecordModelManager(
|
|
42
|
+
self.rec_man = RecordModelManager(context.user)
|
|
49
43
|
self.inst_man = self.rec_man.instance_manager
|
|
50
44
|
self.rel_man = self.rec_man.relationship_manager
|
|
51
45
|
self.an_man = RecordModelAncestorManager(self.rec_man)
|
|
@@ -106,7 +100,7 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
|
|
|
106
100
|
return result
|
|
107
101
|
self.log_error(traceback.format_exc())
|
|
108
102
|
if self.can_send_client_callback():
|
|
109
|
-
DataMgmtServer.get_client_callback(self.user).display_error(e.args[0])
|
|
103
|
+
DataMgmtServer.get_client_callback(self.context.user).display_error(e.args[0])
|
|
110
104
|
return SapioWebhookResult(False)
|
|
111
105
|
|
|
112
106
|
def handle_unexpected_exception(self, e: Exception) -> SapioWebhookResult:
|
|
@@ -120,10 +114,7 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
|
|
|
120
114
|
result: SapioWebhookResult | None = self.handle_any_exception(e)
|
|
121
115
|
if result is not None:
|
|
122
116
|
return result
|
|
123
|
-
|
|
124
|
-
self.log_error(msg)
|
|
125
|
-
# FR-47079: Also log all unexpected exception messages to the webhook execution log within the platform.
|
|
126
|
-
self.log_error_to_webhook_execution_log(msg)
|
|
117
|
+
self.log_error(traceback.format_exc())
|
|
127
118
|
return SapioWebhookResult(False, display_text="Unexpected error occurred during webhook execution. "
|
|
128
119
|
"Please contact Sapio support.")
|
|
129
120
|
|
|
@@ -154,58 +145,32 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
|
|
|
154
145
|
|
|
155
146
|
def log_info(self, msg: str) -> None:
|
|
156
147
|
"""
|
|
157
|
-
Write an info message to the
|
|
158
|
-
|
|
148
|
+
Write an info message to the log. Log destination is stdout. This message will be prepended with the user's
|
|
149
|
+
username and the experiment ID of the experiment they are in, if any.
|
|
159
150
|
"""
|
|
160
|
-
|
|
151
|
+
exp_id = None
|
|
152
|
+
if self.context.eln_experiment is not None:
|
|
153
|
+
exp_id = self.context.eln_experiment.notebook_experiment_id
|
|
154
|
+
# CR-46333: Add the user's group to the logging message.
|
|
155
|
+
user = self.context.user
|
|
156
|
+
username = user.username
|
|
157
|
+
group_name = user.session_additional_data.current_group_name
|
|
158
|
+
self.logger.info(f"(User: {username}, Group: {group_name}, Experiment: {exp_id}):\n{msg}")
|
|
161
159
|
|
|
162
160
|
def log_error(self, msg: str) -> None:
|
|
163
161
|
"""
|
|
164
|
-
Write an error message to the
|
|
165
|
-
|
|
166
|
-
"""
|
|
167
|
-
# PR-46209: Use logger.error instead of logger.info when logging errors.
|
|
168
|
-
self.logger.error(self._format_log(msg, "log_error call"))
|
|
169
|
-
|
|
170
|
-
def log_error_to_webhook_execution_log(self, msg: str) -> None:
|
|
171
|
-
"""
|
|
172
|
-
Write an error message to the platform's webhook execution log. This can be reviewed by navigating to the
|
|
173
|
-
webhook configuration where the webhook that called this function is defined and clicking the "View Log"
|
|
174
|
-
button. From there, select one of the rows for the webhook executions and click "Download Log" from the right
|
|
175
|
-
side table.
|
|
176
|
-
"""
|
|
177
|
-
messenger = DataMgmtServer.get_messenger(self.user)
|
|
178
|
-
messenger.log_message(VeloxLogMessage(message=self._format_log(msg, "Error occurred during webhook execution."),
|
|
179
|
-
log_level=VeloxLogLevel.ERROR,
|
|
180
|
-
originating_class=self.__class__.__name__))
|
|
181
|
-
|
|
182
|
-
def _format_log(self, msg: str, prefix: str | None = None) -> str:
|
|
183
|
-
"""
|
|
184
|
-
Given a message to log, populate it with some metadata about this particular webhook execution, including
|
|
185
|
-
the group of the user and the invocation type of the webhook call.
|
|
162
|
+
Write an error message to the log. Log destination is stderr. This message will be prepended with the user's
|
|
163
|
+
username and the experiment ID of the experiment they are in, if any.
|
|
186
164
|
"""
|
|
187
|
-
|
|
188
|
-
navigator = SapioNavigationLinker(self.context)
|
|
165
|
+
exp_id = None
|
|
189
166
|
if self.context.eln_experiment is not None:
|
|
190
|
-
|
|
191
|
-
elif self.context.data_record and not self.context.data_record_list:
|
|
192
|
-
link = navigator.data_record(self.context.data_record)
|
|
193
|
-
elif self.context.base_data_record:
|
|
194
|
-
link = navigator.data_record(self.context.base_data_record)
|
|
195
|
-
else:
|
|
196
|
-
link = None
|
|
197
|
-
|
|
198
|
-
message: str = ""
|
|
199
|
-
if prefix:
|
|
200
|
-
message += prefix + "\n"
|
|
201
|
-
message += f"Webhook invocation type: {self.context.end_point_type.display_name}\n"
|
|
202
|
-
message += f"Username: {self.user.username}\n"
|
|
167
|
+
exp_id = self.context.eln_experiment.notebook_experiment_id
|
|
203
168
|
# CR-46333: Add the user's group to the logging message.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
169
|
+
user = self.context.user
|
|
170
|
+
username = user.username
|
|
171
|
+
group_name = user.session_additional_data.current_group_name
|
|
172
|
+
# PR-46209: Use logger.error instead of logger.info when logging errors.
|
|
173
|
+
self.logger.error(f"(User: {username}, Group: {group_name}, Experiment: {exp_id}):\n{msg}")
|
|
209
174
|
|
|
210
175
|
def is_main_toolbar(self) -> bool:
|
|
211
176
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: sapiopycommons
|
|
3
|
-
Version: 2024.8.
|
|
3
|
+
Version: 2024.8.15a304
|
|
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>
|
|
@@ -1,31 +1,29 @@
|
|
|
1
1
|
sapiopycommons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
sapiopycommons/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
sapiopycommons/callbacks/callback_util.py,sha256=
|
|
3
|
+
sapiopycommons/callbacks/callback_util.py,sha256=caeIWCHvK33jDs3TRskpJv0kDe7W8NPK4MyJPjgztwo,58012
|
|
4
4
|
sapiopycommons/chem/IndigoMolecules.py,sha256=QqFDi9CKERj6sn_ZwVcS2xZq4imlkaTeCrpq1iNcEJA,1992
|
|
5
5
|
sapiopycommons/chem/Molecules.py,sha256=t80IsQBPJ9mwE8ZxnWomAGrZDhdsOuPvLaTPb_N6jGU,8639
|
|
6
6
|
sapiopycommons/chem/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
7
|
sapiopycommons/datatype/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
sapiopycommons/datatype/attachment_util.py,sha256=
|
|
8
|
+
sapiopycommons/datatype/attachment_util.py,sha256=YlnMprj5IGBbAZDLG2khS1P7JIYTw_NYfpJAfRZfP3M,3219
|
|
9
9
|
sapiopycommons/eln/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
-
sapiopycommons/eln/experiment_handler.py,sha256=
|
|
10
|
+
sapiopycommons/eln/experiment_handler.py,sha256=v1pG4qtZb8OSNWfKtFo6NjnEkReqnu5R9i_hqWh_xxg,57198
|
|
11
11
|
sapiopycommons/eln/experiment_report_util.py,sha256=FTLw-6SLAMeoWTOO-qhGROE9g54pZdyoQJIhiIzlwGw,7848
|
|
12
12
|
sapiopycommons/eln/plate_designer.py,sha256=FYJfhhNq8hdfuXgDYOYHy6g0m2zNwQXZWF_MTPzElDg,7184
|
|
13
13
|
sapiopycommons/files/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
-
sapiopycommons/files/complex_data_loader.py,sha256=
|
|
15
|
-
sapiopycommons/files/file_bridge.py,sha256=
|
|
14
|
+
sapiopycommons/files/complex_data_loader.py,sha256=XSJOl676mIklJo78v07-70u1b015a5DI4sqZPI3C-Tw,1475
|
|
15
|
+
sapiopycommons/files/file_bridge.py,sha256=GI3-gWFzcL0q0c8jKOxTevbzJqtUpiElmkXfTnMsaOo,6224
|
|
16
16
|
sapiopycommons/files/file_bridge_handler.py,sha256=MU2wZR4VY606yx6Bnv8-LzG3mGCeuXeRBn914WNRFCo,13601
|
|
17
17
|
sapiopycommons/files/file_data_handler.py,sha256=3-guAdhJdeJWAFq1a27ijspkO7uMMZ6CapMCD_6o4jA,36746
|
|
18
|
-
sapiopycommons/files/file_util.py,sha256=
|
|
19
|
-
sapiopycommons/files/file_validator.py,sha256=
|
|
18
|
+
sapiopycommons/files/file_util.py,sha256=44mzhn3M_QltoncBB-ooX7_yO6u5k-XU_bzUXHGxUiw,26299
|
|
19
|
+
sapiopycommons/files/file_validator.py,sha256=BhXB2XnoNEzdBXuwul1s2RNoj-3ZoiMmephUCU_0o3Y,28113
|
|
20
20
|
sapiopycommons/files/file_writer.py,sha256=5u_iZXTQvuUU7ceHZr8Q001_tvgJhOqBwAnB_pxcAbQ,16027
|
|
21
21
|
sapiopycommons/general/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
22
|
sapiopycommons/general/accession_service.py,sha256=HYgyOsH_UaoRnoury-c2yTW8SeG4OtjLemdpCzoV4R8,13484
|
|
23
|
-
sapiopycommons/general/aliases.py,sha256=
|
|
24
|
-
sapiopycommons/general/audit_log.py,sha256=LNpBCpZ5mz47-HzHel8PUz1TYGCnxTY6dnerZu-cwOo,9076
|
|
23
|
+
sapiopycommons/general/aliases.py,sha256=i6af5o2oVFGNcyk7GkvTWXQs0H9xTbFKc_GIah8NKVU,3594
|
|
25
24
|
sapiopycommons/general/custom_report_util.py,sha256=cLgIR5Fn3M9uyAtgfTYRv3JRk2SKNevnsb_R5zidSYs,15557
|
|
26
25
|
sapiopycommons/general/exceptions.py,sha256=DOlLKnpCatxQF-lVCToa8ryJgusWLvip6N_1ALN00QE,1679
|
|
27
26
|
sapiopycommons/general/popup_util.py,sha256=-mN5IgYPrLrOEHJ4CHPi2rec4_WAN6X0yMxHwD5h3Bs,30126
|
|
28
|
-
sapiopycommons/general/sapio_links.py,sha256=UlqB09wmgDgbQiB8d3mEj7rxW_GMIXz3j3RlvADNt_A,2475
|
|
29
27
|
sapiopycommons/general/storage_util.py,sha256=ovmK_jN7v09BoX07XxwShpBUC5WYQOM7dbKV_VeLXJU,8892
|
|
30
28
|
sapiopycommons/general/time_util.py,sha256=jiJUh7jc1ZRCOem880S3HaLPZ4RboBtSl4_U9sqAQuM,7290
|
|
31
29
|
sapiopycommons/multimodal/multimodal.py,sha256=A1QsC8QTPmgZyPr7KtMbPRedn2Ie4WIErodUvQ9otgU,6724
|
|
@@ -33,13 +31,13 @@ sapiopycommons/multimodal/multimodal_data.py,sha256=zqgYHO-ULaPKV0POFWZVY9N-Sfm1
|
|
|
33
31
|
sapiopycommons/processtracking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
32
|
sapiopycommons/processtracking/endpoints.py,sha256=g5h_uCVByqacYm9zWAz8TyAdRsGfaO2o0b5RSJdOaSA,10926
|
|
35
33
|
sapiopycommons/recordmodel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
|
-
sapiopycommons/recordmodel/record_handler.py,sha256=
|
|
34
|
+
sapiopycommons/recordmodel/record_handler.py,sha256=AyK1H3x-g1eu1Mt9XD1h57yRrZp_TJjZlEaQ2kPP4Dc,54432
|
|
37
35
|
sapiopycommons/rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
|
-
sapiopycommons/rules/eln_rule_handler.py,sha256=
|
|
39
|
-
sapiopycommons/rules/on_save_rule_handler.py,sha256=
|
|
36
|
+
sapiopycommons/rules/eln_rule_handler.py,sha256=qfkBZtck0KK1i9s9Xe2UZqkzQOgPCzDxRkhxE8Si1xk,10671
|
|
37
|
+
sapiopycommons/rules/on_save_rule_handler.py,sha256=JY9F30IcHwFVdgPAMQtTYuRastV1jeezhVktyrzNASU,10763
|
|
40
38
|
sapiopycommons/webhook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
|
-
sapiopycommons/webhook/webhook_handlers.py,sha256=
|
|
42
|
-
sapiopycommons-2024.8.
|
|
43
|
-
sapiopycommons-2024.8.
|
|
44
|
-
sapiopycommons-2024.8.
|
|
45
|
-
sapiopycommons-2024.8.
|
|
39
|
+
sapiopycommons/webhook/webhook_handlers.py,sha256=ibpBY3Sk3Eij919bIdW0awzlogYoQSWYDDOg--NwsQE,13431
|
|
40
|
+
sapiopycommons-2024.8.15a304.dist-info/METADATA,sha256=MAySm4oWvEe5RSyNWOoiu0F3qnD-zTzWVUvqulqlKpQ,3176
|
|
41
|
+
sapiopycommons-2024.8.15a304.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
42
|
+
sapiopycommons-2024.8.15a304.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
|
|
43
|
+
sapiopycommons-2024.8.15a304.dist-info/RECORD,,
|