sapiopycommons 2025.1.7rc401__py3-none-any.whl → 2025.1.20a403__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 +1217 -363
- sapiopycommons/eln/experiment_handler.py +2 -1
- sapiopycommons/files/file_bridge.py +76 -0
- sapiopycommons/files/file_bridge_handler.py +325 -110
- sapiopycommons/files/file_data_handler.py +2 -2
- sapiopycommons/files/file_validator.py +3 -2
- sapiopycommons/general/aliases.py +40 -27
- sapiopycommons/general/directive_util.py +86 -0
- sapiopycommons/general/exceptions.py +41 -2
- sapiopycommons/webhook/webhook_handlers.py +445 -55
- {sapiopycommons-2025.1.7rc401.dist-info → sapiopycommons-2025.1.20a403.dist-info}/METADATA +1 -1
- {sapiopycommons-2025.1.7rc401.dist-info → sapiopycommons-2025.1.20a403.dist-info}/RECORD +14 -13
- {sapiopycommons-2025.1.7rc401.dist-info → sapiopycommons-2025.1.20a403.dist-info}/WHEEL +0 -0
- {sapiopycommons-2025.1.7rc401.dist-info → sapiopycommons-2025.1.20a403.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,50 +1,50 @@
|
|
|
1
1
|
from collections.abc import Iterable
|
|
2
|
-
from typing import Any
|
|
2
|
+
from typing import Any, TypeAlias
|
|
3
3
|
|
|
4
4
|
from sapiopylib.rest.User import SapioUser
|
|
5
5
|
from sapiopylib.rest.pojo.DataRecord import DataRecord
|
|
6
|
-
from sapiopylib.rest.pojo.datatype.FieldDefinition import FieldType
|
|
6
|
+
from sapiopylib.rest.pojo.datatype.FieldDefinition import FieldType, AbstractVeloxFieldDefinition
|
|
7
7
|
from sapiopylib.rest.pojo.eln.ElnExperiment import ElnExperiment
|
|
8
8
|
from sapiopylib.rest.pojo.eln.ExperimentEntry import ExperimentEntry
|
|
9
9
|
from sapiopylib.rest.pojo.eln.SapioELNEnums import ElnBaseDataType
|
|
10
10
|
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
11
11
|
from sapiopylib.rest.utils.Protocols import ElnExperimentProtocol, ElnEntryStep
|
|
12
|
-
from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
|
|
12
|
+
from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel, AbstractRecordModel
|
|
13
13
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedRecordModel, WrappedType, WrapperField
|
|
14
14
|
|
|
15
15
|
from sapiopycommons.general.exceptions import SapioException
|
|
16
16
|
|
|
17
|
-
FieldValue = int | float | str | bool | None
|
|
17
|
+
FieldValue: TypeAlias = int | float | str | bool | None
|
|
18
18
|
"""Allowable values for fields in the system."""
|
|
19
|
-
RecordModel = PyRecordModel | WrappedRecordModel
|
|
19
|
+
RecordModel: TypeAlias = PyRecordModel | AbstractRecordModel | WrappedRecordModel
|
|
20
20
|
"""Different forms that a record model could take."""
|
|
21
|
-
SapioRecord = DataRecord | RecordModel
|
|
21
|
+
SapioRecord: TypeAlias = DataRecord | RecordModel
|
|
22
22
|
"""A record could be provided as either a DataRecord, PyRecordModel, or WrappedRecordModel (WrappedType)."""
|
|
23
|
-
RecordIdentifier = SapioRecord | int
|
|
23
|
+
RecordIdentifier: TypeAlias = SapioRecord | int
|
|
24
24
|
"""A RecordIdentifier is either a record type or an integer for the record's record ID."""
|
|
25
|
-
DataTypeIdentifier = SapioRecord | type[WrappedType] | str
|
|
25
|
+
DataTypeIdentifier: TypeAlias = SapioRecord | type[WrappedType] | str
|
|
26
26
|
"""A DataTypeIdentifier is either a SapioRecord, a record model wrapper type, or a string."""
|
|
27
|
-
FieldIdentifier = WrapperField | str | tuple[str, FieldType]
|
|
27
|
+
FieldIdentifier: TypeAlias = AbstractVeloxFieldDefinition | WrapperField | str | tuple[str, FieldType]
|
|
28
28
|
"""A FieldIdentifier is either wrapper field from a record model wrapper, a string, or a tuple of string
|
|
29
29
|
and field type."""
|
|
30
|
-
FieldIdentifierKey = WrapperField | str
|
|
30
|
+
FieldIdentifierKey: TypeAlias = WrapperField | str
|
|
31
31
|
"""A FieldIdentifierKey is a FieldIdentifier, except it can't be a tuple, s tuples can't be used as keys in
|
|
32
32
|
dictionaries.."""
|
|
33
|
-
HasFieldWrappers = type[WrappedType] | WrappedRecordModel
|
|
33
|
+
HasFieldWrappers: TypeAlias = type[WrappedType] | WrappedRecordModel
|
|
34
34
|
"""An identifier for classes that have wrapper fields."""
|
|
35
|
-
ExperimentIdentifier = ElnExperimentProtocol | ElnExperiment | int
|
|
35
|
+
ExperimentIdentifier: TypeAlias = ElnExperimentProtocol | ElnExperiment | int
|
|
36
36
|
"""An ExperimentIdentifier is either an experiment protocol, experiment, or an integer for the experiment's notebook
|
|
37
37
|
ID."""
|
|
38
|
-
ExperimentEntryIdentifier = ElnEntryStep | ExperimentEntry | int
|
|
38
|
+
ExperimentEntryIdentifier: TypeAlias = ElnEntryStep | ExperimentEntry | int
|
|
39
39
|
"""An ExperimentEntryIdentifier is either an ELN entry step, experiment entry, or an integer for the entry's ID."""
|
|
40
|
-
FieldMap = dict[str, FieldValue]
|
|
40
|
+
FieldMap: TypeAlias = dict[str, FieldValue]
|
|
41
41
|
"""A field map is simply a dict of data field names to values. The purpose of aliasing this is to help distinguish
|
|
42
42
|
any random dict in a webhook from one which is explicitly used for record fields."""
|
|
43
|
-
FieldIdentifierMap = dict[FieldIdentifierKey, FieldValue]
|
|
43
|
+
FieldIdentifierMap: TypeAlias = dict[FieldIdentifierKey, FieldValue]
|
|
44
44
|
"""A field identifier map is the same thing as a field map, except the keys can be field identifiers instead
|
|
45
45
|
of just strings. Note that although one of the allowed field identifiers is a tuple, you can't use tuples as
|
|
46
46
|
keys in a dictionary."""
|
|
47
|
-
UserIdentifier = SapioWebhookContext | SapioUser
|
|
47
|
+
UserIdentifier: TypeAlias = SapioWebhookContext | SapioUser
|
|
48
48
|
"""An identifier for classes from which a user object can be used for sending requests."""
|
|
49
49
|
|
|
50
50
|
|
|
@@ -142,23 +142,25 @@ class AliasUtil:
|
|
|
142
142
|
@staticmethod
|
|
143
143
|
def to_data_field_name(value: FieldIdentifier) -> str:
|
|
144
144
|
"""
|
|
145
|
-
Convert
|
|
145
|
+
Convert an object that can be used to identify a data field to a data field name string.
|
|
146
146
|
|
|
147
|
-
:param value:
|
|
147
|
+
:param value: An object that can be used to identify a data field.
|
|
148
148
|
:return: A string of the data field name of the input value.
|
|
149
149
|
"""
|
|
150
150
|
if isinstance(value, tuple):
|
|
151
151
|
return value[0]
|
|
152
152
|
if isinstance(value, WrapperField):
|
|
153
153
|
return value.field_name
|
|
154
|
+
if isinstance(value, AbstractVeloxFieldDefinition):
|
|
155
|
+
return value.data_field_name
|
|
154
156
|
return value
|
|
155
157
|
|
|
156
158
|
@staticmethod
|
|
157
159
|
def to_data_field_names(values: Iterable[FieldIdentifier]) -> list[str]:
|
|
158
160
|
"""
|
|
159
|
-
Convert an iterable of
|
|
161
|
+
Convert an iterable of objects that can be used to identify data fields to a list of data field name strings.
|
|
160
162
|
|
|
161
|
-
:param values: An iterable of
|
|
163
|
+
:param values: An iterable of objects that can be used to identify a data field.
|
|
162
164
|
:return: A list of strings of the data field names of the input values.
|
|
163
165
|
"""
|
|
164
166
|
return [AliasUtil.to_data_field_name(x) for x in values]
|
|
@@ -204,21 +206,32 @@ class AliasUtil:
|
|
|
204
206
|
raise SapioException(f"The wrapper of data type \"{data_type.get_wrapper_data_type_name()}\" doesn't have a "
|
|
205
207
|
f"field with the name \"{field}\",")
|
|
206
208
|
|
|
209
|
+
@staticmethod
|
|
210
|
+
def to_field_map(record: SapioRecord) -> FieldMap:
|
|
211
|
+
"""
|
|
212
|
+
Convert a given record value to a field map. This includes the given RecordId of the given record.
|
|
213
|
+
|
|
214
|
+
:return: The field map for the input record.
|
|
215
|
+
"""
|
|
216
|
+
if isinstance(record, DataRecord):
|
|
217
|
+
# noinspection PyTypeChecker
|
|
218
|
+
fields: FieldMap = record.get_fields()
|
|
219
|
+
else:
|
|
220
|
+
fields: FieldMap = record.fields.copy_to_dict()
|
|
221
|
+
fields["RecordId"] = AliasUtil.to_record_id(record)
|
|
222
|
+
return fields
|
|
223
|
+
|
|
207
224
|
@staticmethod
|
|
208
225
|
def to_field_map_lists(records: Iterable[SapioRecord]) -> list[FieldMap]:
|
|
209
226
|
"""
|
|
210
|
-
Convert a list of variables that could either be DataRecords, PyRecordModels,
|
|
211
|
-
|
|
227
|
+
Convert a list of variables that could either be DataRecords, PyRecordModels, or WrappedRecordModels
|
|
228
|
+
to a list of their field maps. This includes the given RecordId of the given records.
|
|
212
229
|
|
|
213
230
|
:return: A list of field maps for the input records.
|
|
214
231
|
"""
|
|
215
232
|
field_map_list: list[FieldMap] = []
|
|
216
233
|
for record in records:
|
|
217
|
-
|
|
218
|
-
# noinspection PyTypeChecker
|
|
219
|
-
field_map_list.append(record.get_fields())
|
|
220
|
-
else:
|
|
221
|
-
field_map_list.append(record.fields.copy_to_dict())
|
|
234
|
+
field_map_list.append(AliasUtil.to_field_map(record))
|
|
222
235
|
return field_map_list
|
|
223
236
|
|
|
224
237
|
@staticmethod
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from typing import Iterable, cast
|
|
2
|
+
|
|
3
|
+
from sapiopylib.rest.User import SapioUser
|
|
4
|
+
from sapiopylib.rest.pojo.CustomReport import CustomReportCriteria, CustomReport
|
|
5
|
+
from sapiopylib.rest.pojo.webhook.WebhookDirective import HomePageDirective, FormDirective, TableDirective, \
|
|
6
|
+
CustomReportDirective, ElnExperimentDirective, ExperimentEntryDirective
|
|
7
|
+
|
|
8
|
+
from sapiopycommons.general.aliases import SapioRecord, AliasUtil, ExperimentIdentifier, ExperimentEntryIdentifier, \
|
|
9
|
+
UserIdentifier
|
|
10
|
+
from sapiopycommons.general.custom_report_util import CustomReportUtil
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# FR-47392: Create a DirectiveUtil class to simplify the creation of directives.
|
|
14
|
+
class DirectiveUtil:
|
|
15
|
+
"""
|
|
16
|
+
DirectiveUtil is a class for creating webhook directives. The utility functions reduce the provided variables
|
|
17
|
+
down to the exact type that the directives require, removing the need for the caller to handle the conversion.
|
|
18
|
+
"""
|
|
19
|
+
user: SapioUser
|
|
20
|
+
|
|
21
|
+
def __init__(self, context: UserIdentifier):
|
|
22
|
+
"""
|
|
23
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
24
|
+
"""
|
|
25
|
+
self.user = AliasUtil.to_sapio_user(context)
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def homepage() -> HomePageDirective:
|
|
29
|
+
"""
|
|
30
|
+
:return: A directive that sends the user back to their home page.
|
|
31
|
+
"""
|
|
32
|
+
return HomePageDirective()
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def record_form(record: SapioRecord) -> FormDirective:
|
|
36
|
+
"""
|
|
37
|
+
:param record: A record in the system.
|
|
38
|
+
:return: A directive that sends the user to a specific data record form.
|
|
39
|
+
"""
|
|
40
|
+
return FormDirective(AliasUtil.to_data_record(record))
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def record_table(records: Iterable[SapioRecord]) -> TableDirective:
|
|
44
|
+
"""
|
|
45
|
+
:param records: A list of records in the system.
|
|
46
|
+
:return: A directive that sends the user to a table of data records.
|
|
47
|
+
"""
|
|
48
|
+
return TableDirective(AliasUtil.to_data_records(records))
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def record_adaptive(records: Iterable[SapioRecord]) -> TableDirective | FormDirective:
|
|
52
|
+
"""
|
|
53
|
+
:param records: A list of records in the system.
|
|
54
|
+
:return: A directive that sends the user to a table of data records if there are multiple records,
|
|
55
|
+
or a directive that sends the user to a specific data record form if there is only one record.
|
|
56
|
+
"""
|
|
57
|
+
records: list[SapioRecord] = list(records)
|
|
58
|
+
if len(records) == 1:
|
|
59
|
+
return DirectiveUtil.record_form(records[0])
|
|
60
|
+
return DirectiveUtil.record_table(records)
|
|
61
|
+
|
|
62
|
+
def custom_report(self, report: CustomReport | CustomReportCriteria | str) -> CustomReportDirective:
|
|
63
|
+
"""
|
|
64
|
+
:param report: A custom report, the criteria for a custom report, or the name of a system report.
|
|
65
|
+
:return: A directive that sends the user to the results of the provided custom report.
|
|
66
|
+
"""
|
|
67
|
+
if isinstance(report, str):
|
|
68
|
+
report: CustomReport = CustomReportUtil.get_system_report_criteria(self.user, report)
|
|
69
|
+
return CustomReportDirective(cast(CustomReport, report))
|
|
70
|
+
|
|
71
|
+
@staticmethod
|
|
72
|
+
def eln_experiment(experiment: ExperimentIdentifier) -> ElnExperimentDirective:
|
|
73
|
+
"""
|
|
74
|
+
:param experiment: An identifier for an experiment.
|
|
75
|
+
:return: A directive that sends the user to the ELN experiment.
|
|
76
|
+
"""
|
|
77
|
+
return ElnExperimentDirective(AliasUtil.to_notebook_id(experiment))
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def eln_entry(experiment: ExperimentIdentifier, entry: ExperimentEntryIdentifier) -> ExperimentEntryDirective:
|
|
81
|
+
"""
|
|
82
|
+
:param experiment: An identifier for an experiment.
|
|
83
|
+
:param entry: An identifier for an entry in the experiment.
|
|
84
|
+
:return: A directive that sends the user to the provided experiment entry within its ELN experiment.
|
|
85
|
+
"""
|
|
86
|
+
return ExperimentEntryDirective(AliasUtil.to_notebook_id(experiment), AliasUtil.to_entry_id(entry))
|
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MessageDisplayType(Enum):
|
|
5
|
+
"""
|
|
6
|
+
An enum representing the different ways in which a message can be displayed to the user.
|
|
7
|
+
"""
|
|
8
|
+
TOASTER_SUCCESS = 0
|
|
9
|
+
TOASTER_INFO = 1
|
|
10
|
+
TOASTER_WARNING = 2
|
|
11
|
+
TOASTER_ERROR = 3
|
|
12
|
+
OK_DIALOG = 4
|
|
13
|
+
DISPLAY_INFO = 5
|
|
14
|
+
DISPLAY_WARNING = 6
|
|
15
|
+
DISPLAY_ERROR = 7
|
|
16
|
+
|
|
17
|
+
|
|
1
18
|
# FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
|
|
2
19
|
class SapioException(Exception):
|
|
3
20
|
"""
|
|
@@ -29,7 +46,29 @@ class SapioDialogTimeoutException(SapioException):
|
|
|
29
46
|
pass
|
|
30
47
|
|
|
31
48
|
|
|
32
|
-
class
|
|
49
|
+
class DisplayableException(SapioException):
|
|
50
|
+
"""
|
|
51
|
+
A generic exception that promises to return a user-friendly message explaining the error that should be displayed to
|
|
52
|
+
the user. Note that it is up to whichever class that catches this exception to actually display the message.
|
|
53
|
+
"""
|
|
54
|
+
msg: str
|
|
55
|
+
display_type: MessageDisplayType | None
|
|
56
|
+
title: str | None
|
|
57
|
+
|
|
58
|
+
def __init__(self, msg: str, display_type: MessageDisplayType | None = None, title: str | None = None):
|
|
59
|
+
"""
|
|
60
|
+
:param msg: The message that should be displayed to the user.
|
|
61
|
+
:param display_type: The manner in which the message should be displayed. If None, then the display type should
|
|
62
|
+
be controlled by the class that catches this exception.
|
|
63
|
+
:param title: If the display type is able to have a title, this is the title that will be displayed. If None,
|
|
64
|
+
then the title should be controlled by the class that catches this exception.
|
|
65
|
+
"""
|
|
66
|
+
self.msg = msg
|
|
67
|
+
self.display_type = display_type
|
|
68
|
+
self.title = title
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class SapioUserErrorException(DisplayableException):
|
|
33
72
|
"""
|
|
34
73
|
An exception caused by user error (e.g. user provided a CSV when an XLSX was expected), which promises to return a
|
|
35
74
|
user-friendly message explaining the error that should be displayed to the user.
|
|
@@ -39,7 +78,7 @@ class SapioUserErrorException(SapioException):
|
|
|
39
78
|
pass
|
|
40
79
|
|
|
41
80
|
|
|
42
|
-
class SapioCriticalErrorException(
|
|
81
|
+
class SapioCriticalErrorException(DisplayableException):
|
|
43
82
|
"""
|
|
44
83
|
A critical exception caused by user error, which promises to return a user-friendly message explaining the error
|
|
45
84
|
that should be displayed to the user.
|