sapiopycommons 2024.11.7a354__py3-none-any.whl → 2024.11.8a359__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 +83 -532
- sapiopycommons/chem/IndigoMolecules.py +0 -2
- sapiopycommons/chem/Molecules.py +18 -77
- sapiopycommons/datatype/attachment_util.py +10 -11
- sapiopycommons/eln/experiment_handler.py +70 -272
- sapiopycommons/files/complex_data_loader.py +4 -5
- sapiopycommons/files/file_bridge.py +24 -31
- sapiopycommons/files/file_data_handler.py +5 -2
- sapiopycommons/files/file_util.py +9 -59
- sapiopycommons/files/file_validator.py +6 -92
- sapiopycommons/files/file_writer.py +15 -44
- sapiopycommons/general/aliases.py +6 -207
- sapiopycommons/general/custom_report_util.py +37 -212
- sapiopycommons/general/exceptions.py +8 -21
- sapiopycommons/general/popup_util.py +0 -21
- sapiopycommons/general/time_util.py +2 -8
- sapiopycommons/processtracking/endpoints.py +22 -22
- sapiopycommons/recordmodel/record_handler.py +97 -481
- sapiopycommons/rules/eln_rule_handler.py +25 -34
- sapiopycommons/rules/on_save_rule_handler.py +31 -34
- sapiopycommons/webhook/webhook_handlers.py +42 -201
- {sapiopycommons-2024.11.7a354.dist-info → sapiopycommons-2024.11.8a359.dist-info}/METADATA +2 -4
- sapiopycommons-2024.11.8a359.dist-info/RECORD +38 -0
- sapiopycommons/callbacks/field_builder.py +0 -537
- sapiopycommons/customreport/__init__.py +0 -0
- sapiopycommons/customreport/column_builder.py +0 -60
- sapiopycommons/customreport/custom_report_builder.py +0 -130
- sapiopycommons/customreport/term_builder.py +0 -299
- sapiopycommons/datatype/data_fields.py +0 -61
- sapiopycommons/datatype/pseudo_data_types.py +0 -440
- sapiopycommons/eln/experiment_report_util.py +0 -653
- sapiopycommons/files/file_bridge_handler.py +0 -340
- sapiopycommons/flowcyto/flow_cyto.py +0 -77
- sapiopycommons/flowcyto/flowcyto_data.py +0 -75
- sapiopycommons/general/accession_service.py +0 -375
- sapiopycommons/general/audit_log.py +0 -189
- sapiopycommons/general/sapio_links.py +0 -50
- sapiopycommons/multimodal/multimodal.py +0 -146
- sapiopycommons/multimodal/multimodal_data.py +0 -489
- sapiopycommons/processtracking/custom_workflow_handler.py +0 -406
- sapiopycommons/sftpconnect/__init__.py +0 -0
- sapiopycommons/sftpconnect/sftp_builder.py +0 -69
- sapiopycommons/webhook/webhook_context.py +0 -39
- sapiopycommons/webhook/webservice_handlers.py +0 -67
- sapiopycommons-2024.11.7a354.dist-info/RECORD +0 -59
- {sapiopycommons-2024.11.7a354.dist-info → sapiopycommons-2024.11.8a359.dist-info}/WHEEL +0 -0
- {sapiopycommons-2024.11.7a354.dist-info → sapiopycommons-2024.11.8a359.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from weakref import WeakValueDictionary
|
|
4
|
-
|
|
5
|
-
from sapiopylib.rest.User import SapioUser
|
|
6
1
|
from sapiopylib.rest.pojo.DataRecord import DataRecord
|
|
7
|
-
from sapiopylib.rest.pojo.
|
|
2
|
+
from sapiopylib.rest.pojo.webhook.VeloxRules import VeloxRuleType, VeloxRuleParser
|
|
8
3
|
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
9
4
|
from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelManager, RecordModelInstanceManager
|
|
10
5
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
|
|
11
6
|
|
|
12
|
-
from sapiopycommons.general.aliases import FieldMap
|
|
7
|
+
from sapiopycommons.general.aliases import FieldMap
|
|
13
8
|
from sapiopycommons.general.exceptions import SapioException
|
|
14
9
|
|
|
15
10
|
|
|
@@ -17,6 +12,7 @@ from sapiopycommons.general.exceptions import SapioException
|
|
|
17
12
|
class ElnRuleHandler:
|
|
18
13
|
"""
|
|
19
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.
|
|
20
16
|
"""
|
|
21
17
|
__context: SapioWebhookContext
|
|
22
18
|
"""The context that this handler is working from."""
|
|
@@ -38,25 +34,7 @@ class ElnRuleHandler:
|
|
|
38
34
|
"""A mapping of entry name to the lists of field maps for that entry, each grouping of field maps being mapped by
|
|
39
35
|
its data type."""
|
|
40
36
|
|
|
41
|
-
__instances: WeakValueDictionary[SapioUser, ElnRuleHandler] = WeakValueDictionary()
|
|
42
|
-
__initialized: bool
|
|
43
|
-
|
|
44
|
-
def __new__(cls, context: SapioWebhookContext):
|
|
45
|
-
if context.velox_eln_rule_result_map is None:
|
|
46
|
-
raise SapioException("No Velox ELN rule result map in context for ElnRuleHandler to parse.")
|
|
47
|
-
user = context if isinstance(context, SapioUser) else context.user
|
|
48
|
-
obj = cls.__instances.get(user)
|
|
49
|
-
if not obj:
|
|
50
|
-
obj = object.__new__(cls)
|
|
51
|
-
obj.__initialized = False
|
|
52
|
-
cls.__instances[user] = obj
|
|
53
|
-
return obj
|
|
54
|
-
|
|
55
37
|
def __init__(self, context: SapioWebhookContext):
|
|
56
|
-
if self.__initialized:
|
|
57
|
-
return
|
|
58
|
-
self.__initialized = True
|
|
59
|
-
|
|
60
38
|
if context.velox_eln_rule_result_map is None:
|
|
61
39
|
raise SapioException("No Velox ELN rule result map in context for ElnRuleHandler to parse.")
|
|
62
40
|
self.__context = context
|
|
@@ -86,8 +64,13 @@ class ElnRuleHandler:
|
|
|
86
64
|
# Get the data type of this record. If this is an ELN type, ignore the digits.
|
|
87
65
|
data_type: str = record.data_type_name
|
|
88
66
|
# PR-46331: Ensure that all ELN types are converted to their base data type name.
|
|
89
|
-
|
|
90
|
-
|
|
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"
|
|
91
74
|
# Update the list of records of this type that exist so far globally.
|
|
92
75
|
self.__records.setdefault(data_type, set()).add(record)
|
|
93
76
|
# Do the same for the list of records of this type for this specific entry.
|
|
@@ -102,9 +85,19 @@ class ElnRuleHandler:
|
|
|
102
85
|
entry_dict: dict[str, dict[int, FieldMap]] = {}
|
|
103
86
|
for record_result in entry_results:
|
|
104
87
|
for result in record_result.velox_type_rule_field_map_result_list:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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"
|
|
108
101
|
for field_map in result.field_map_list:
|
|
109
102
|
rec_id: int = field_map.get("RecordId")
|
|
110
103
|
self.__field_maps.setdefault(data_type, {}).update({rec_id: field_map})
|
|
@@ -125,7 +118,7 @@ class ElnRuleHandler:
|
|
|
125
118
|
"""
|
|
126
119
|
return list(self.__entry_to_field_maps.keys())
|
|
127
120
|
|
|
128
|
-
def get_records(self, data_type:
|
|
121
|
+
def get_records(self, data_type: str, entry: str | None = None) -> list[DataRecord]:
|
|
129
122
|
"""
|
|
130
123
|
Get records from the cached context with the given data type. Capable of being filtered to searching within
|
|
131
124
|
the context of an entry name. If the given data type or entry does not exist in the context,
|
|
@@ -136,12 +129,11 @@ class ElnRuleHandler:
|
|
|
136
129
|
type from every entry. If an entry is provided, but it does not exist in the context, returns an empty list.
|
|
137
130
|
:return: The records from the context that match the input parameters.
|
|
138
131
|
"""
|
|
139
|
-
data_type: str = AliasUtil.to_data_type_name(data_type)
|
|
140
132
|
records: dict[str, set[DataRecord]] = self.__entry_to_records.get(entry, {}) if entry else self.__records
|
|
141
133
|
return list(records.get(data_type, []))
|
|
142
134
|
|
|
143
135
|
# FR-46701: Add functions to the rule handlers for accessing the field maps of inaccessible records in the context.
|
|
144
|
-
def get_field_maps(self, data_type:
|
|
136
|
+
def get_field_maps(self, data_type: str, entry: str | None = None) -> list[FieldMap]:
|
|
145
137
|
"""
|
|
146
138
|
Get field maps from the cached context with the given data type. Capable of being filtered to searching within
|
|
147
139
|
the context of an entry name. If the given data type or entry does not exist in the context,
|
|
@@ -157,7 +149,6 @@ class ElnRuleHandler:
|
|
|
157
149
|
list.
|
|
158
150
|
:return: The field maps from the context that match the input parameters.
|
|
159
151
|
"""
|
|
160
|
-
data_type: str = AliasUtil.to_data_type_name(data_type)
|
|
161
152
|
field_maps: dict[str, dict[int, FieldMap]] = self.__entry_to_field_maps.get(entry, {}) if entry else self.__field_maps
|
|
162
153
|
return list(field_maps.get(data_type, {}).values())
|
|
163
154
|
|
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from weakref import WeakValueDictionary
|
|
4
|
-
|
|
5
|
-
from sapiopylib.rest.User import SapioUser
|
|
6
1
|
from sapiopylib.rest.pojo.DataRecord import DataRecord
|
|
7
|
-
from sapiopylib.rest.pojo.
|
|
2
|
+
from sapiopylib.rest.pojo.webhook.VeloxRules import VeloxRuleType, VeloxRuleParser
|
|
8
3
|
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
9
4
|
from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelManager, RecordModelInstanceManager
|
|
10
5
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
|
|
11
6
|
|
|
12
|
-
from sapiopycommons.general.aliases import FieldMap
|
|
7
|
+
from sapiopycommons.general.aliases import FieldMap
|
|
13
8
|
from sapiopycommons.general.exceptions import SapioException
|
|
14
9
|
|
|
15
10
|
|
|
@@ -17,6 +12,7 @@ from sapiopycommons.general.exceptions import SapioException
|
|
|
17
12
|
class OnSaveRuleHandler:
|
|
18
13
|
"""
|
|
19
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.
|
|
20
16
|
"""
|
|
21
17
|
__context: SapioWebhookContext
|
|
22
18
|
"""The context that this handler is working from."""
|
|
@@ -38,25 +34,7 @@ class OnSaveRuleHandler:
|
|
|
38
34
|
"""A mapping of record IDs of records in the context.data_record_list to the field maps related to that
|
|
39
35
|
record, each grouping of field maps being mapped by its data type."""
|
|
40
36
|
|
|
41
|
-
__instances: WeakValueDictionary[SapioUser, OnSaveRuleHandler] = WeakValueDictionary()
|
|
42
|
-
__initialized: bool
|
|
43
|
-
|
|
44
|
-
def __new__(cls, context: SapioWebhookContext):
|
|
45
|
-
if context.velox_on_save_result_map is None:
|
|
46
|
-
raise SapioException("No Velox on save rule result map in context for OnSaveRuleHandler to parse.")
|
|
47
|
-
user = context if isinstance(context, SapioUser) else context.user
|
|
48
|
-
obj = cls.__instances.get(user)
|
|
49
|
-
if not obj:
|
|
50
|
-
obj = object.__new__(cls)
|
|
51
|
-
obj.__initialized = False
|
|
52
|
-
cls.__instances[user] = obj
|
|
53
|
-
return obj
|
|
54
|
-
|
|
55
37
|
def __init__(self, context: SapioWebhookContext):
|
|
56
|
-
if self.__initialized:
|
|
57
|
-
return
|
|
58
|
-
self.__initialized = True
|
|
59
|
-
|
|
60
38
|
if context.velox_on_save_result_map is None:
|
|
61
39
|
raise SapioException("No Velox on save rule result map in context for OnSaveRuleHandler to parse.")
|
|
62
40
|
self.__context = context
|
|
@@ -73,6 +51,9 @@ class OnSaveRuleHandler:
|
|
|
73
51
|
self.__base_id_to_records = {}
|
|
74
52
|
# Each record ID in the context has a list of results for that record.
|
|
75
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)
|
|
76
57
|
# Keep track of the records for this specific record ID.
|
|
77
58
|
id_dict: dict[str, set[DataRecord]] = {}
|
|
78
59
|
# The list of results for a record consist of a list of data records and a VeloxType that specifies
|
|
@@ -83,8 +64,13 @@ class OnSaveRuleHandler:
|
|
|
83
64
|
# Get the data type of this record. If this is an ELN type, ignore the digits.
|
|
84
65
|
data_type: str = record.data_type_name
|
|
85
66
|
# PR-46331: Ensure that all ELN types are converted to their base data type name.
|
|
86
|
-
|
|
87
|
-
|
|
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"
|
|
88
74
|
# Update the list of records of this type that exist so far globally.
|
|
89
75
|
self.__records.setdefault(data_type, set()).add(record)
|
|
90
76
|
# Do the same for the list of records of this type that relate to this record ID.
|
|
@@ -96,11 +82,24 @@ class OnSaveRuleHandler:
|
|
|
96
82
|
self.__base_id_to_field_maps = {}
|
|
97
83
|
# Repeat the same thing for the field map results.
|
|
98
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)
|
|
99
88
|
id_dict: dict[str, dict[int, FieldMap]] = {}
|
|
100
89
|
for record_result in rule_results:
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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"
|
|
104
103
|
for field_map in record_result.field_map_list:
|
|
105
104
|
rec_id: int = field_map.get("RecordId")
|
|
106
105
|
self.__field_maps.setdefault(data_type, {}).update({rec_id: field_map})
|
|
@@ -121,7 +120,7 @@ class OnSaveRuleHandler:
|
|
|
121
120
|
"""
|
|
122
121
|
return list(self.__base_id_to_field_maps.keys())
|
|
123
122
|
|
|
124
|
-
def get_records(self, data_type:
|
|
123
|
+
def get_records(self, data_type: str, record_id: int | None = None) -> list[DataRecord]:
|
|
125
124
|
"""
|
|
126
125
|
Get records from the cached context with the given data type. Capable of being filtered to searching within
|
|
127
126
|
the context of a record ID. If the given data type or record ID does not exist in the context,
|
|
@@ -132,12 +131,11 @@ class OnSaveRuleHandler:
|
|
|
132
131
|
data type from every ID. If an ID is provided, but it does not exist in the context, returns an empty list.
|
|
133
132
|
:return: The records from the context that match the input parameters.
|
|
134
133
|
"""
|
|
135
|
-
data_type: str = AliasUtil.to_data_type_name(data_type)
|
|
136
134
|
records: dict[str, set[DataRecord]] = self.__base_id_to_records.get(record_id, {}) if record_id else self.__records
|
|
137
135
|
return list(records.get(data_type, []))
|
|
138
136
|
|
|
139
137
|
# FR-46701: Add functions to the rule handlers for accessing the field maps of inaccessible records in the context.
|
|
140
|
-
def get_field_maps(self, data_type:
|
|
138
|
+
def get_field_maps(self, data_type: str, record_id: int | None = None) -> list[FieldMap]:
|
|
141
139
|
"""
|
|
142
140
|
Get field maps from the cached context with the given data type. Capable of being filtered to searching within
|
|
143
141
|
the context of a record ID. If the given data type or record ID does not exist in the context,
|
|
@@ -153,7 +151,6 @@ class OnSaveRuleHandler:
|
|
|
153
151
|
list.
|
|
154
152
|
:return: The field maps from the context that match the input parameters.
|
|
155
153
|
"""
|
|
156
|
-
data_type: str = AliasUtil.to_data_type_name(data_type)
|
|
157
154
|
field_maps: dict[str, dict[int, FieldMap]] = self.__base_id_to_field_maps.get(record_id, {}) if record_id else self.__field_maps
|
|
158
155
|
return list(field_maps.get(data_type, {}).values())
|
|
159
156
|
|
|
@@ -4,25 +4,16 @@ 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
|
|
13
|
-
from sapiopylib.rest.utils.DataTypeCacheManager import DataTypeCacheManager
|
|
14
11
|
from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelManager, RecordModelInstanceManager, \
|
|
15
12
|
RecordModelRelationshipManager
|
|
16
13
|
from sapiopylib.rest.utils.recordmodel.ancestry import RecordModelAncestorManager
|
|
17
14
|
|
|
18
|
-
from sapiopycommons.callbacks.callback_util import CallbackUtil
|
|
19
|
-
from sapiopycommons.eln.experiment_handler import ExperimentHandler
|
|
20
15
|
from sapiopycommons.general.exceptions import SapioUserErrorException, SapioCriticalErrorException, \
|
|
21
|
-
SapioUserCancelledException
|
|
22
|
-
from sapiopycommons.general.sapio_links import SapioNavigationLinker
|
|
23
|
-
from sapiopycommons.recordmodel.record_handler import RecordHandler
|
|
24
|
-
from sapiopycommons.rules.eln_rule_handler import ElnRuleHandler
|
|
25
|
-
from sapiopycommons.rules.on_save_rule_handler import OnSaveRuleHandler
|
|
16
|
+
SapioUserCancelledException
|
|
26
17
|
|
|
27
18
|
|
|
28
19
|
# FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
|
|
@@ -34,7 +25,6 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
|
|
|
34
25
|
"""
|
|
35
26
|
logger: Logger
|
|
36
27
|
|
|
37
|
-
user: SapioUser
|
|
38
28
|
context: SapioWebhookContext
|
|
39
29
|
|
|
40
30
|
dr_man: DataRecordManager
|
|
@@ -44,64 +34,29 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
|
|
|
44
34
|
# FR-46329: Add the ancestor manager to CommonsWebhookHandler.
|
|
45
35
|
an_man: RecordModelAncestorManager
|
|
46
36
|
|
|
47
|
-
dt_cache: DataTypeCacheManager
|
|
48
|
-
rec_handler: RecordHandler
|
|
49
|
-
callback: CallbackUtil
|
|
50
|
-
exp_handler: ExperimentHandler | None
|
|
51
|
-
rule_handler: OnSaveRuleHandler | ElnRuleHandler | None
|
|
52
|
-
|
|
53
37
|
def run(self, context: SapioWebhookContext) -> SapioWebhookResult:
|
|
54
|
-
self.user = context.user
|
|
55
38
|
self.context = context
|
|
56
|
-
|
|
57
|
-
self.logger = self.user.logger
|
|
39
|
+
self.logger = context.user.logger
|
|
58
40
|
|
|
59
41
|
self.dr_man = context.data_record_manager
|
|
60
|
-
self.rec_man = RecordModelManager(
|
|
42
|
+
self.rec_man = RecordModelManager(context.user)
|
|
61
43
|
self.inst_man = self.rec_man.instance_manager
|
|
62
44
|
self.rel_man = self.rec_man.relationship_manager
|
|
63
45
|
self.an_man = RecordModelAncestorManager(self.rec_man)
|
|
64
46
|
|
|
65
|
-
self.dt_cache = DataTypeCacheManager(self.user)
|
|
66
|
-
self.rec_handler = RecordHandler(context)
|
|
67
|
-
self.callback = CallbackUtil(context)
|
|
68
|
-
if context.eln_experiment is not None:
|
|
69
|
-
self.exp_handler = ExperimentHandler(context)
|
|
70
|
-
else:
|
|
71
|
-
self.exp_handler = None
|
|
72
|
-
if self.is_on_save_rule():
|
|
73
|
-
self.rule_handler = OnSaveRuleHandler(context)
|
|
74
|
-
elif self.is_eln_rule():
|
|
75
|
-
self.rule_handler = ElnRuleHandler(context)
|
|
76
|
-
else:
|
|
77
|
-
self.rule_handler = None
|
|
78
|
-
|
|
79
47
|
# Wrap the execution of each webhook in a try/catch. If an exception occurs, handle any special sapiopycommons
|
|
80
48
|
# exceptions. Otherwise, return a generic message stating that an error occurred.
|
|
81
49
|
try:
|
|
82
|
-
self.
|
|
83
|
-
result = self.execute(context)
|
|
84
|
-
if result is None:
|
|
85
|
-
raise SapioException("Your execute function returned a None result! Don't forget your return statement!")
|
|
86
|
-
return result
|
|
50
|
+
return self.execute(context)
|
|
87
51
|
except SapioUserErrorException as e:
|
|
88
52
|
return self.handle_user_error_exception(e)
|
|
89
53
|
except SapioCriticalErrorException as e:
|
|
90
54
|
return self.handle_critical_error_exception(e)
|
|
91
55
|
except SapioUserCancelledException as e:
|
|
92
56
|
return self.handle_user_cancelled_exception(e)
|
|
93
|
-
except SapioDialogTimeoutException as e:
|
|
94
|
-
return self.handle_dialog_timeout_exception(e)
|
|
95
57
|
except Exception as e:
|
|
96
58
|
return self.handle_unexpected_exception(e)
|
|
97
59
|
|
|
98
|
-
def initialize(self, context: SapioWebhookContext) -> None:
|
|
99
|
-
"""
|
|
100
|
-
A function that can be optionally overridden by your webhooks to initialize additional instance variables,
|
|
101
|
-
or set up whatever else you wish to set up before the execute function is ran. Default behavior does nothing.
|
|
102
|
-
"""
|
|
103
|
-
pass
|
|
104
|
-
|
|
105
60
|
@abstractmethod
|
|
106
61
|
def execute(self, context: SapioWebhookContext) -> SapioWebhookResult:
|
|
107
62
|
"""
|
|
@@ -113,12 +68,11 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
|
|
|
113
68
|
# the run method and into their own functions.
|
|
114
69
|
def handle_user_error_exception(self, e: SapioUserErrorException) -> SapioWebhookResult:
|
|
115
70
|
"""
|
|
116
|
-
Handle a SapioUserErrorException.
|
|
117
|
-
|
|
118
|
-
Default behavior returns a false result and the error message as display text in a webhook result.
|
|
71
|
+
Handle a SapioUserErrorException. Default behavior returns the error message as display text in a webhook
|
|
72
|
+
result.
|
|
119
73
|
|
|
120
74
|
:param e: The exception that was raised.
|
|
121
|
-
:return: A SapioWebhookResult
|
|
75
|
+
:return: A SapioWebhookResult reporting the exception to the user.
|
|
122
76
|
"""
|
|
123
77
|
result: SapioWebhookResult | None = self.handle_any_exception(e)
|
|
124
78
|
if result is not None:
|
|
@@ -128,83 +82,45 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
|
|
|
128
82
|
|
|
129
83
|
def handle_critical_error_exception(self, e: SapioCriticalErrorException) -> SapioWebhookResult:
|
|
130
84
|
"""
|
|
131
|
-
Handle a SapioCriticalErrorException.
|
|
132
|
-
|
|
133
|
-
Default behavior makes a display_error client callback with the error message and returns a false result.
|
|
85
|
+
Handle a SapioCriticalErrorException. Default behavior makes a display_error client callback.
|
|
134
86
|
|
|
135
87
|
:param e: The exception that was raised.
|
|
136
|
-
:return: A SapioWebhookResult
|
|
88
|
+
:return: A SapioWebhookResult reporting the exception to the user.
|
|
137
89
|
"""
|
|
138
90
|
result: SapioWebhookResult | None = self.handle_any_exception(e)
|
|
139
91
|
if result is not None:
|
|
140
92
|
return result
|
|
141
93
|
self.log_error(traceback.format_exc())
|
|
142
|
-
|
|
143
|
-
# sending display text instead.
|
|
144
|
-
if self.can_send_client_callback():
|
|
145
|
-
self.callback.display_error(e.args[0])
|
|
146
|
-
else:
|
|
147
|
-
return SapioWebhookResult(False, e.args[0])
|
|
94
|
+
DataMgmtServer.get_client_callback(self.context.user).display_error(e.args[0])
|
|
148
95
|
return SapioWebhookResult(False)
|
|
149
96
|
|
|
150
|
-
def
|
|
151
|
-
"""
|
|
152
|
-
Handle a SapioUserCancelledException.
|
|
153
|
-
|
|
154
|
-
Default behavior simply ends the webhook session with a true result (since the user cancelling is a valid
|
|
155
|
-
action).
|
|
156
|
-
|
|
157
|
-
:param e: The exception that was raised.
|
|
158
|
-
:return: A SapioWebhookResult to end the webhook session with.
|
|
159
|
-
"""
|
|
160
|
-
result: SapioWebhookResult | None = self.handle_any_exception(e)
|
|
161
|
-
if result is not None:
|
|
162
|
-
return result
|
|
163
|
-
return SapioWebhookResult(True)
|
|
164
|
-
|
|
165
|
-
def handle_dialog_timeout_exception(self, e: SapioDialogTimeoutException) -> SapioWebhookResult:
|
|
97
|
+
def handle_unexpected_exception(self, e: Exception) -> SapioWebhookResult:
|
|
166
98
|
"""
|
|
167
|
-
Handle a
|
|
168
|
-
|
|
169
|
-
Default behavior displays an OK popup notifying the user that the dialog has timed out and returns a false
|
|
170
|
-
webhook result.
|
|
99
|
+
Handle a generic exception which isn't a SapioUserErrorException or SapioCriticalErrorException. Default
|
|
100
|
+
behavior returns a generic error message as display text informing the user to contact Sapio support.
|
|
171
101
|
|
|
172
102
|
:param e: The exception that was raised.
|
|
173
|
-
:return: A SapioWebhookResult
|
|
103
|
+
:return: A SapioWebhookResult reporting the exception to the user.
|
|
174
104
|
"""
|
|
175
105
|
result: SapioWebhookResult | None = self.handle_any_exception(e)
|
|
176
106
|
if result is not None:
|
|
177
107
|
return result
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
try:
|
|
182
|
-
self.callback.ok_dialog("Notice", "You have remained idle for too long and this dialog has timed out. "
|
|
183
|
-
"Close and re-initiate it to continue.")
|
|
184
|
-
except SapioDialogTimeoutException:
|
|
185
|
-
pass
|
|
186
|
-
return SapioWebhookResult(False)
|
|
108
|
+
self.log_error(traceback.format_exc())
|
|
109
|
+
return SapioWebhookResult(False, display_text="Unexpected error occurred during webhook execution. "
|
|
110
|
+
"Please contact Sapio support.")
|
|
187
111
|
|
|
188
|
-
def
|
|
112
|
+
def handle_user_cancelled_exception(self, e: SapioUserCancelledException) -> SapioWebhookResult:
|
|
189
113
|
"""
|
|
190
|
-
Handle a
|
|
191
|
-
|
|
192
|
-
Default behavior returns a false webhook result with a generic error message as display text informing the user
|
|
193
|
-
to contact Sapio support. Additionally, the stace trace of the exception that was thrown is logged to the
|
|
194
|
-
execution log for the webhook call in the system.
|
|
114
|
+
Handle a SapioUserCancelledException. Default behavior returns "User Cancelled" as display text in a webhook
|
|
115
|
+
result.
|
|
195
116
|
|
|
196
117
|
:param e: The exception that was raised.
|
|
197
|
-
:return: A SapioWebhookResult
|
|
118
|
+
:return: A SapioWebhookResult with display text saying the user cancelled the request.
|
|
198
119
|
"""
|
|
199
120
|
result: SapioWebhookResult | None = self.handle_any_exception(e)
|
|
200
121
|
if result is not None:
|
|
201
122
|
return result
|
|
202
|
-
|
|
203
|
-
self.log_error(msg)
|
|
204
|
-
# FR-47079: Also log all unexpected exception messages to the webhook execution log within the platform.
|
|
205
|
-
self.log_error_to_webhook_execution_log(msg)
|
|
206
|
-
return SapioWebhookResult(False, display_text="Unexpected error occurred during webhook execution. "
|
|
207
|
-
"Please contact Sapio support.")
|
|
123
|
+
return SapioWebhookResult(False, display_text="User cancelled.")
|
|
208
124
|
|
|
209
125
|
# noinspection PyMethodMayBeStatic,PyUnusedLocal
|
|
210
126
|
def handle_any_exception(self, e: Exception) -> SapioWebhookResult | None:
|
|
@@ -214,65 +130,38 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
|
|
|
214
130
|
|
|
215
131
|
:param e: The exception that was raised.
|
|
216
132
|
:return: An optional SapioWebhookResult. May return a custom message to the client that wouldn't have been
|
|
217
|
-
sent by one of the normal exception handlers, or may return None if no result needs returned.
|
|
218
|
-
returned, then the default behavior of other exception handlers is skipped.
|
|
133
|
+
sent by one of the normal exception handlers, or may return None if no result needs returned.
|
|
219
134
|
"""
|
|
220
135
|
return None
|
|
221
136
|
|
|
222
137
|
def log_info(self, msg: str) -> None:
|
|
223
138
|
"""
|
|
224
|
-
Write an info message to the
|
|
225
|
-
|
|
139
|
+
Write an info message to the log. Log destination is stdout. This message will be prepended with the user's
|
|
140
|
+
username and the experiment ID of the experiment they are in, if any.
|
|
226
141
|
"""
|
|
227
|
-
|
|
142
|
+
exp_id = None
|
|
143
|
+
if self.context.eln_experiment is not None:
|
|
144
|
+
exp_id = self.context.eln_experiment.notebook_experiment_id
|
|
145
|
+
# CR-46333: Add the user's group to the logging message.
|
|
146
|
+
user = self.context.user
|
|
147
|
+
username = user.username
|
|
148
|
+
group_name = user.session_additional_data.current_group_name
|
|
149
|
+
self.logger.info(f"(User: {username}, Group: {group_name}, Experiment: {exp_id}):\n{msg}")
|
|
228
150
|
|
|
229
151
|
def log_error(self, msg: str) -> None:
|
|
230
152
|
"""
|
|
231
|
-
Write an error message to the
|
|
232
|
-
|
|
233
|
-
"""
|
|
234
|
-
# PR-46209: Use logger.error instead of logger.info when logging errors.
|
|
235
|
-
self.logger.error(self._format_log(msg, "log_error call"))
|
|
236
|
-
|
|
237
|
-
def log_error_to_webhook_execution_log(self, msg: str) -> None:
|
|
238
|
-
"""
|
|
239
|
-
Write an error message to the platform's webhook execution log. This can be reviewed by navigating to the
|
|
240
|
-
webhook configuration where the webhook that called this function is defined and clicking the "View Log"
|
|
241
|
-
button. From there, select one of the rows for the webhook executions and click "Download Log" from the right
|
|
242
|
-
side table.
|
|
153
|
+
Write an error message to the log. Log destination is stderr. This message will be prepended with the user's
|
|
154
|
+
username and the experiment ID of the experiment they are in, if any.
|
|
243
155
|
"""
|
|
244
|
-
|
|
245
|
-
messenger.log_message(VeloxLogMessage(message=self._format_log(msg, "Error occurred during webhook execution."),
|
|
246
|
-
log_level=VeloxLogLevel.ERROR,
|
|
247
|
-
originating_class=self.__class__.__name__))
|
|
248
|
-
|
|
249
|
-
def _format_log(self, msg: str, prefix: str | None = None) -> str:
|
|
250
|
-
"""
|
|
251
|
-
Given a message to log, populate it with some metadata about this particular webhook execution, including
|
|
252
|
-
the group of the user and the invocation type of the webhook call.
|
|
253
|
-
"""
|
|
254
|
-
# If we're able to, provide a link to the location that the error occurred at.
|
|
255
|
-
navigator = SapioNavigationLinker(self.context)
|
|
156
|
+
exp_id = None
|
|
256
157
|
if self.context.eln_experiment is not None:
|
|
257
|
-
|
|
258
|
-
elif self.context.data_record and not self.context.data_record_list:
|
|
259
|
-
link = navigator.data_record(self.context.data_record)
|
|
260
|
-
elif self.context.base_data_record:
|
|
261
|
-
link = navigator.data_record(self.context.base_data_record)
|
|
262
|
-
else:
|
|
263
|
-
link = None
|
|
264
|
-
|
|
265
|
-
message: str = ""
|
|
266
|
-
if prefix:
|
|
267
|
-
message += prefix + "\n"
|
|
268
|
-
message += f"Webhook invocation type: {self.context.end_point_type.display_name}\n"
|
|
269
|
-
message += f"Username: {self.user.username}\n"
|
|
158
|
+
exp_id = self.context.eln_experiment.notebook_experiment_id
|
|
270
159
|
# CR-46333: Add the user's group to the logging message.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
160
|
+
user = self.context.user
|
|
161
|
+
username = user.username
|
|
162
|
+
group_name = user.session_additional_data.current_group_name
|
|
163
|
+
# PR-46209: Use logger.error instead of logger.info when logging errors.
|
|
164
|
+
self.logger.error(f"(User: {username}, Group: {group_name}, Experiment: {exp_id}):\n{msg}")
|
|
276
165
|
|
|
277
166
|
def is_main_toolbar(self) -> bool:
|
|
278
167
|
"""
|
|
@@ -348,51 +237,3 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
|
|
|
348
237
|
:return: True if this endpoint was invoked as a scheduled action.
|
|
349
238
|
"""
|
|
350
239
|
return self.context.end_point_type == WebhookEndpointType.SCHEDULEDPLUGIN
|
|
351
|
-
|
|
352
|
-
def is_action_button_field(self) -> bool:
|
|
353
|
-
"""
|
|
354
|
-
:return: True if this endpoint was invoked as an action button field.
|
|
355
|
-
"""
|
|
356
|
-
return self.context.end_point_type == WebhookEndpointType.ACTIONDATAFIELD
|
|
357
|
-
|
|
358
|
-
def is_action_text_field(self) -> bool:
|
|
359
|
-
"""
|
|
360
|
-
:return: True if this endpoint was invoked as an action text field.
|
|
361
|
-
"""
|
|
362
|
-
return self.context.end_point_type == WebhookEndpointType.ACTION_TEXT_FIELD
|
|
363
|
-
|
|
364
|
-
def is_custom(self) -> bool:
|
|
365
|
-
"""
|
|
366
|
-
:return: True if this endpoint was invoked from a custom point, such as a custom queue.
|
|
367
|
-
"""
|
|
368
|
-
return self.context.end_point_type == WebhookEndpointType.CUSTOM
|
|
369
|
-
|
|
370
|
-
def is_calendar_event_click_handler(self) -> bool:
|
|
371
|
-
"""
|
|
372
|
-
:return: True if this endpoint was invoked from a calendar event click handler.
|
|
373
|
-
"""
|
|
374
|
-
return self.context.end_point_type == WebhookEndpointType.CALENDAR_EVENT_CLICK_HANDLER
|
|
375
|
-
|
|
376
|
-
def is_eln_menu_grabber(self) -> bool:
|
|
377
|
-
"""
|
|
378
|
-
:return: True if this endpoint was invoked as a notebook entry grabber.
|
|
379
|
-
"""
|
|
380
|
-
return self.context.end_point_type == WebhookEndpointType.NOTEBOOKEXPERIMENTGRABBER
|
|
381
|
-
|
|
382
|
-
def is_conversation_bot(self) -> bool:
|
|
383
|
-
"""
|
|
384
|
-
:return: True if this endpoint was invoked as from a conversation bot.
|
|
385
|
-
"""
|
|
386
|
-
return self.context.end_point_type == WebhookEndpointType.CONVERSATION_BOT
|
|
387
|
-
|
|
388
|
-
def is_multi_data_type_table_toolbar(self) -> bool:
|
|
389
|
-
"""
|
|
390
|
-
:return: True if this endpoint was invoked as a multi data type table toolbar button.
|
|
391
|
-
"""
|
|
392
|
-
return self.context.end_point_type == WebhookEndpointType.REPORTTOOLBAR
|
|
393
|
-
|
|
394
|
-
def can_send_client_callback(self) -> bool:
|
|
395
|
-
"""
|
|
396
|
-
:return: Whether client callbacks and directives can be sent from this webhook's endpoint type.
|
|
397
|
-
"""
|
|
398
|
-
return self.context.is_client_callback_available
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: sapiopycommons
|
|
3
|
-
Version: 2024.11.
|
|
3
|
+
Version: 2024.11.8a359
|
|
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>
|
|
@@ -17,8 +17,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
17
17
|
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
18
18
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
19
|
Requires-Python: >=3.10
|
|
20
|
-
Requires-Dist:
|
|
21
|
-
Requires-Dist: sapiopylib>=2024.5.24.210
|
|
20
|
+
Requires-Dist: sapiopylib>=2023.12.13.174
|
|
22
21
|
Description-Content-Type: text/markdown
|
|
23
22
|
|
|
24
23
|
|
|
@@ -51,7 +50,6 @@ This license does not provide any rights to use any other copyrighted artifacts
|
|
|
51
50
|
## Dependencies
|
|
52
51
|
The following dependencies are required for this package:
|
|
53
52
|
- [sapiopylib - The official Sapio Informatics Platform Python API package.](https://pypi.org/project/sapiopylib/)
|
|
54
|
-
- [databind - Databind is a library inspired by jackson-databind to de-/serialize Python dataclasses.](https://pypi.org/project/databind/)
|
|
55
53
|
|
|
56
54
|
## Getting Help
|
|
57
55
|
If you have a support contract with Sapio Sciences, please use our [technical support channels](https://sapio-sciences.atlassian.net/servicedesk/customer/portals).
|