sapiopycommons 2024.3.19a157__py3-none-any.whl → 2025.1.17a402__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/__init__.py +0 -0
- sapiopycommons/callbacks/callback_util.py +2041 -0
- sapiopycommons/callbacks/field_builder.py +545 -0
- sapiopycommons/chem/IndigoMolecules.py +46 -1
- sapiopycommons/chem/Molecules.py +100 -21
- sapiopycommons/customreport/__init__.py +0 -0
- sapiopycommons/customreport/column_builder.py +60 -0
- sapiopycommons/customreport/custom_report_builder.py +137 -0
- sapiopycommons/customreport/term_builder.py +315 -0
- sapiopycommons/datatype/attachment_util.py +14 -15
- sapiopycommons/datatype/data_fields.py +61 -0
- sapiopycommons/datatype/pseudo_data_types.py +440 -0
- sapiopycommons/eln/experiment_handler.py +355 -91
- sapiopycommons/eln/experiment_report_util.py +649 -0
- sapiopycommons/eln/plate_designer.py +152 -0
- sapiopycommons/files/complex_data_loader.py +31 -0
- sapiopycommons/files/file_bridge.py +149 -25
- sapiopycommons/files/file_bridge_handler.py +555 -0
- sapiopycommons/files/file_data_handler.py +633 -0
- sapiopycommons/files/file_util.py +263 -163
- sapiopycommons/files/file_validator.py +569 -0
- sapiopycommons/files/file_writer.py +377 -0
- sapiopycommons/flowcyto/flow_cyto.py +77 -0
- sapiopycommons/flowcyto/flowcyto_data.py +75 -0
- sapiopycommons/general/accession_service.py +375 -0
- sapiopycommons/general/aliases.py +250 -15
- sapiopycommons/general/audit_log.py +185 -0
- sapiopycommons/general/custom_report_util.py +251 -31
- sapiopycommons/general/directive_util.py +86 -0
- sapiopycommons/general/exceptions.py +69 -7
- sapiopycommons/general/popup_util.py +59 -7
- sapiopycommons/general/sapio_links.py +50 -0
- sapiopycommons/general/storage_util.py +148 -0
- sapiopycommons/general/time_util.py +91 -7
- sapiopycommons/multimodal/multimodal.py +146 -0
- sapiopycommons/multimodal/multimodal_data.py +490 -0
- sapiopycommons/processtracking/__init__.py +0 -0
- sapiopycommons/processtracking/custom_workflow_handler.py +406 -0
- sapiopycommons/processtracking/endpoints.py +192 -0
- sapiopycommons/recordmodel/record_handler.py +621 -148
- sapiopycommons/rules/eln_rule_handler.py +87 -8
- sapiopycommons/rules/on_save_rule_handler.py +87 -12
- sapiopycommons/sftpconnect/__init__.py +0 -0
- sapiopycommons/sftpconnect/sftp_builder.py +70 -0
- sapiopycommons/webhook/webhook_context.py +39 -0
- sapiopycommons/webhook/webhook_handlers.py +614 -71
- sapiopycommons/webhook/webservice_handlers.py +317 -0
- {sapiopycommons-2024.3.19a157.dist-info → sapiopycommons-2025.1.17a402.dist-info}/METADATA +5 -4
- sapiopycommons-2025.1.17a402.dist-info/RECORD +60 -0
- {sapiopycommons-2024.3.19a157.dist-info → sapiopycommons-2025.1.17a402.dist-info}/WHEEL +1 -1
- sapiopycommons-2024.3.19a157.dist-info/RECORD +0 -28
- {sapiopycommons-2024.3.19a157.dist-info → sapiopycommons-2025.1.17a402.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,26 +1,88 @@
|
|
|
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
|
"""
|
|
4
21
|
A generic exception thrown by sapiopycommons methods. Typically caused by programmer error, but may also be from
|
|
5
22
|
extremely edge case user errors. For expected user errors, use SapioUserErrorException.
|
|
23
|
+
|
|
24
|
+
CommonsWebhookHandler's default behavior for this and any other exception that doesn't extend SapioException is
|
|
25
|
+
to return a generic toaster message saying that an unexpected error has occurred.
|
|
26
|
+
"""
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SapioUserCancelledException(SapioException):
|
|
31
|
+
"""
|
|
32
|
+
An exception thrown when the user cancels a client callback.
|
|
33
|
+
|
|
34
|
+
CommonsWebhookHandler's default behavior is to simply end the webhook session with a true result without logging
|
|
35
|
+
the exception.
|
|
6
36
|
"""
|
|
7
37
|
pass
|
|
8
38
|
|
|
9
39
|
|
|
10
|
-
class
|
|
40
|
+
class SapioDialogTimeoutException(SapioException):
|
|
41
|
+
"""
|
|
42
|
+
An exception thrown when the user leaves a client callback open for too long.
|
|
43
|
+
|
|
44
|
+
CommonsWebhookHandler's default behavior is to display an OK popup notifying the user that the dialog has timed out.
|
|
45
|
+
"""
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
|
|
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):
|
|
11
72
|
"""
|
|
12
73
|
An exception caused by user error (e.g. user provided a CSV when an XLSX was expected), which promises to return a
|
|
13
|
-
user-friendly message explaining the error that should be displayed to the user.
|
|
14
|
-
|
|
15
|
-
|
|
74
|
+
user-friendly message explaining the error that should be displayed to the user.
|
|
75
|
+
|
|
76
|
+
CommonsWebhookHandler's default behavior is to return the error message in a toaster popup.
|
|
16
77
|
"""
|
|
17
78
|
pass
|
|
18
79
|
|
|
19
80
|
|
|
20
|
-
class SapioCriticalErrorException(
|
|
81
|
+
class SapioCriticalErrorException(DisplayableException):
|
|
21
82
|
"""
|
|
22
83
|
A critical exception caused by user error, which promises to return a user-friendly message explaining the error
|
|
23
|
-
that should be displayed to the user.
|
|
24
|
-
|
|
84
|
+
that should be displayed to the user.
|
|
85
|
+
|
|
86
|
+
CommonsWebhookHandler's default behavior is to return the error message in a display_error callback.
|
|
25
87
|
"""
|
|
26
88
|
pass
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import warnings
|
|
2
2
|
|
|
3
3
|
from sapiopylib.rest.DataMgmtService import DataMgmtServer
|
|
4
4
|
from sapiopylib.rest.pojo.datatype.DataType import DataTypeDefinition
|
|
@@ -10,7 +10,7 @@ from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
|
10
10
|
from sapiopylib.rest.pojo.webhook.WebhookResult import SapioWebhookResult
|
|
11
11
|
from sapiopylib.rest.utils.FormBuilder import FormBuilder
|
|
12
12
|
|
|
13
|
-
from sapiopycommons.general.aliases import SapioRecord, AliasUtil
|
|
13
|
+
from sapiopycommons.general.aliases import SapioRecord, AliasUtil, FieldMap
|
|
14
14
|
from sapiopycommons.general.exceptions import SapioException
|
|
15
15
|
|
|
16
16
|
|
|
@@ -19,16 +19,21 @@ from sapiopycommons.general.exceptions import SapioException
|
|
|
19
19
|
# and one FormEntryDialogRequest methods.)
|
|
20
20
|
# CR-46332 - For any functions that use temporary data types, set the data type name, display name, and plural display
|
|
21
21
|
# name in the form builder.
|
|
22
|
+
# FR-46716 - Add comments noting that this class is deprecated in favor of CallbackUtil.
|
|
22
23
|
class PopupUtil:
|
|
23
24
|
"""
|
|
25
|
+
DEPRECATED: Make use of CallbackUtil as of 24.5.
|
|
26
|
+
|
|
24
27
|
Methods for creating boilerplate SapioWebhookResults with client callback requests to create popup dialogs.
|
|
25
28
|
"""
|
|
26
29
|
@staticmethod
|
|
27
|
-
def form_popup(title: str, msg: str, fields: list[AbstractVeloxFieldDefinition], values:
|
|
30
|
+
def form_popup(title: str, msg: str, fields: list[AbstractVeloxFieldDefinition], values: FieldMap = None,
|
|
28
31
|
column_positions: dict[str, tuple[int, int]] = None, data_type: str = "Default",
|
|
29
32
|
*, display_name: str | None = None, plural_display_name: str | None = None,
|
|
30
33
|
request_context: str | None = None) -> SapioWebhookResult:
|
|
31
34
|
"""
|
|
35
|
+
DEPRECATED: Make use of CallbackUtil as of 24.5.
|
|
36
|
+
|
|
32
37
|
Create a basic form entry dialog.
|
|
33
38
|
|
|
34
39
|
The calling webhook must catch the FormEntryDialogResult that the client will send back.
|
|
@@ -48,6 +53,7 @@ class PopupUtil:
|
|
|
48
53
|
:param request_context: Context that will be returned to the webhook server in the client callback result.
|
|
49
54
|
:return: A SapioWebhookResult with the popup as its client callback request.
|
|
50
55
|
"""
|
|
56
|
+
warnings.warn("PopupUtil is deprecated as of 24.5+. Use CallbackUtil instead.", DeprecationWarning)
|
|
51
57
|
if display_name is None:
|
|
52
58
|
display_name = data_type
|
|
53
59
|
if plural_display_name is None:
|
|
@@ -74,12 +80,14 @@ class PopupUtil:
|
|
|
74
80
|
column_positions: dict[str, tuple[int, int]] = None, editable: bool | None = True,
|
|
75
81
|
*, request_context: str | None = None) -> SapioWebhookResult:
|
|
76
82
|
"""
|
|
83
|
+
DEPRECATED: Make use of CallbackUtil as of 24.5.
|
|
84
|
+
|
|
77
85
|
Create a basic form dialog for a record with displayed fields from the record data type.
|
|
78
86
|
|
|
79
87
|
Makes webservice calls to get the data field and type definitions of the given data type.
|
|
80
88
|
The calling webhook must catch the FormEntryDialogResult that the client will send back.
|
|
81
89
|
|
|
82
|
-
:param context: The current webhook context
|
|
90
|
+
:param context: The current webhook context.
|
|
83
91
|
:param title: The title of the dialog.
|
|
84
92
|
:param msg: The message to display in the dialog.
|
|
85
93
|
:param fields: The data field names of the fields from the record to display in the form. Fields will be
|
|
@@ -92,6 +100,7 @@ class PopupUtil:
|
|
|
92
100
|
:param request_context: Context that will be returned to the webhook server in the client callback result.
|
|
93
101
|
:return: A SapioWebhookResult with the popup as its client callback request.
|
|
94
102
|
"""
|
|
103
|
+
warnings.warn("PopupUtil is deprecated as of 24.5+. Use CallbackUtil instead.", DeprecationWarning)
|
|
95
104
|
# Get the field definitions of the data type.
|
|
96
105
|
data_type: str = record.data_type_name
|
|
97
106
|
type_man = DataMgmtServer.get_data_type_manager(context.user)
|
|
@@ -128,6 +137,8 @@ class PopupUtil:
|
|
|
128
137
|
*, display_name: str | None = None, plural_display_name: str | None = None,
|
|
129
138
|
request_context: str | None = None, **kwargs) -> SapioWebhookResult:
|
|
130
139
|
"""
|
|
140
|
+
DEPRECATED: Make use of CallbackUtil as of 24.5.
|
|
141
|
+
|
|
131
142
|
Create a form dialog with a single string field. May take additional parameters to be passed to the string field
|
|
132
143
|
definition.
|
|
133
144
|
|
|
@@ -148,6 +159,7 @@ class PopupUtil:
|
|
|
148
159
|
:param request_context: Context that will be returned to the webhook server in the client callback result.
|
|
149
160
|
:return: A SapioWebhookResult with the popup as its client callback request.
|
|
150
161
|
"""
|
|
162
|
+
warnings.warn("PopupUtil is deprecated as of 24.5+. Use CallbackUtil instead.", DeprecationWarning)
|
|
151
163
|
if max_length is None:
|
|
152
164
|
max_length = len(default_value) if default_value else 100
|
|
153
165
|
string_field = VeloxStringFieldDefinition(data_type, field_name, field_name, default_value=default_value,
|
|
@@ -161,6 +173,8 @@ class PopupUtil:
|
|
|
161
173
|
*, display_name: str | None = None, plural_display_name: str | None = None,
|
|
162
174
|
request_context: str | None = None, **kwargs) -> SapioWebhookResult:
|
|
163
175
|
"""
|
|
176
|
+
DEPRECATED: Make use of CallbackUtil as of 24.5.
|
|
177
|
+
|
|
164
178
|
Create a form dialog with a single integer field. May take additional parameters to be passed to the integer
|
|
165
179
|
field definition.
|
|
166
180
|
|
|
@@ -182,6 +196,7 @@ class PopupUtil:
|
|
|
182
196
|
:param request_context: Context that will be returned to the webhook server in the client callback result.
|
|
183
197
|
:return: A SapioWebhookResult with the popup as its client callback request.
|
|
184
198
|
"""
|
|
199
|
+
warnings.warn("PopupUtil is deprecated as of 24.5+. Use CallbackUtil instead.", DeprecationWarning)
|
|
185
200
|
if default_value is None:
|
|
186
201
|
default_value = max(0, min_value)
|
|
187
202
|
integer_field = VeloxIntegerFieldDefinition(data_type, field_name, field_name, default_value=default_value,
|
|
@@ -197,6 +212,8 @@ class PopupUtil:
|
|
|
197
212
|
plural_display_name: str | None = None, request_context: str | None = None, **kwargs) \
|
|
198
213
|
-> SapioWebhookResult:
|
|
199
214
|
"""
|
|
215
|
+
DEPRECATED: Make use of CallbackUtil as of 24.5.
|
|
216
|
+
|
|
200
217
|
Create a form dialog with a single double field. May take additional parameters to be passed to the double
|
|
201
218
|
field definition.
|
|
202
219
|
|
|
@@ -218,6 +235,7 @@ class PopupUtil:
|
|
|
218
235
|
:param request_context: Context that will be returned to the webhook server in the client callback result.
|
|
219
236
|
:return: A SapioWebhookResult with the popup as its client callback request.
|
|
220
237
|
"""
|
|
238
|
+
warnings.warn("PopupUtil is deprecated as of 24.5+. Use CallbackUtil instead.", DeprecationWarning)
|
|
221
239
|
if default_value is None:
|
|
222
240
|
default_value = min_value
|
|
223
241
|
double_field = VeloxDoubleFieldDefinition(data_type, field_name, field_name, default_value=default_value,
|
|
@@ -226,10 +244,12 @@ class PopupUtil:
|
|
|
226
244
|
plural_display_name=plural_display_name, request_context=request_context)
|
|
227
245
|
|
|
228
246
|
@staticmethod
|
|
229
|
-
def table_popup(title: str, msg: str, fields: list[AbstractVeloxFieldDefinition], values: list[
|
|
247
|
+
def table_popup(title: str, msg: str, fields: list[AbstractVeloxFieldDefinition], values: list[FieldMap],
|
|
230
248
|
*, data_type: str = "Default", display_name: str | None = None,
|
|
231
249
|
plural_display_name: str | None = None, request_context: str | None = None) -> SapioWebhookResult:
|
|
232
250
|
"""
|
|
251
|
+
DEPRECATED: Make use of CallbackUtil as of 24.5.
|
|
252
|
+
|
|
233
253
|
Create a basic table entry dialog.
|
|
234
254
|
|
|
235
255
|
The calling webhook must catch the TableEntryDialogResult that the client will send back.
|
|
@@ -247,6 +267,7 @@ class PopupUtil:
|
|
|
247
267
|
:param request_context: Context that will be returned to the webhook server in the client callback result.
|
|
248
268
|
:return: A SapioWebhookResult with the popup as its client callback request.
|
|
249
269
|
"""
|
|
270
|
+
warnings.warn("PopupUtil is deprecated as of 24.5+. Use CallbackUtil instead.", DeprecationWarning)
|
|
250
271
|
if display_name is None:
|
|
251
272
|
display_name = data_type
|
|
252
273
|
if plural_display_name is None:
|
|
@@ -264,6 +285,8 @@ class PopupUtil:
|
|
|
264
285
|
title: str, msg: str, fields: list[str], records: list[SapioRecord],
|
|
265
286
|
editable: bool | None = True, *, request_context: str | None = None) -> SapioWebhookResult:
|
|
266
287
|
"""
|
|
288
|
+
DEPRECATED: Make use of CallbackUtil as of 24.5.
|
|
289
|
+
|
|
267
290
|
Create a table dialog for a list of record where the columns are specific fields from the record data type.
|
|
268
291
|
|
|
269
292
|
Makes webservice calls to get the data field and type definitions of the given data type.
|
|
@@ -280,12 +303,15 @@ class PopupUtil:
|
|
|
280
303
|
:param request_context: Context that will be returned to the webhook server in the client callback result.
|
|
281
304
|
:return: A SapioWebhookResult with the popup as its client callback request.
|
|
282
305
|
"""
|
|
306
|
+
warnings.warn("PopupUtil is deprecated as of 24.5+. Use CallbackUtil instead.", DeprecationWarning)
|
|
307
|
+
if not records:
|
|
308
|
+
raise SapioException("No records provided.")
|
|
283
309
|
data_types: set[str] = {x.data_type_name for x in records}
|
|
284
310
|
if len(data_types) > 1:
|
|
285
311
|
raise SapioException("Multiple data type names encountered in records list for record table popup.")
|
|
286
312
|
data_type: str = data_types.pop()
|
|
287
313
|
# Get the field maps from the records.
|
|
288
|
-
field_map_list: list[
|
|
314
|
+
field_map_list: list[FieldMap] = AliasUtil.to_field_map_lists(records)
|
|
289
315
|
# Get the field definitions of the data type.
|
|
290
316
|
type_man = DataMgmtServer.get_data_type_manager(context.user)
|
|
291
317
|
type_def: DataTypeDefinition = type_man.get_data_type_definition(data_type)
|
|
@@ -315,6 +341,8 @@ class PopupUtil:
|
|
|
315
341
|
msg: str, fields: list[str], records: list[SapioRecord], multi_select: bool = True,
|
|
316
342
|
*, request_context: str | None = None) -> SapioWebhookResult:
|
|
317
343
|
"""
|
|
344
|
+
DEPRECATED: Make use of CallbackUtil as of 24.5.
|
|
345
|
+
|
|
318
346
|
Create a record selection dialog for a list of record where the columns are specific fields from the record data
|
|
319
347
|
type.
|
|
320
348
|
|
|
@@ -330,12 +358,15 @@ class PopupUtil:
|
|
|
330
358
|
:param request_context: Context that will be returned to the webhook server in the client callback result.
|
|
331
359
|
:return: A SapioWebhookResult with the popup as its client callback request.
|
|
332
360
|
"""
|
|
361
|
+
warnings.warn("PopupUtil is deprecated as of 24.5+. Use CallbackUtil instead.", DeprecationWarning)
|
|
362
|
+
if not records:
|
|
363
|
+
raise SapioException("No records provided.")
|
|
333
364
|
data_types: set[str] = {x.data_type_name for x in records}
|
|
334
365
|
if len(data_types) > 1:
|
|
335
366
|
raise SapioException("Multiple data type names encountered in records list for record table popup.")
|
|
336
367
|
data_type: str = data_types.pop()
|
|
337
368
|
# Get the field maps from the records.
|
|
338
|
-
field_map_list: list[
|
|
369
|
+
field_map_list: list[FieldMap] = AliasUtil.to_field_map_lists(records)
|
|
339
370
|
# Get the field definitions of the data type.
|
|
340
371
|
type_man = DataMgmtServer.get_data_type_manager(context.user)
|
|
341
372
|
type_def: DataTypeDefinition = type_man.get_data_type_definition(data_type)
|
|
@@ -362,6 +393,8 @@ class PopupUtil:
|
|
|
362
393
|
def list_popup(title: str, options: list[str], multi_select: bool = False,
|
|
363
394
|
*, request_context: str | None = None) -> SapioWebhookResult:
|
|
364
395
|
"""
|
|
396
|
+
DEPRECATED: Make use of CallbackUtil as of 24.5.
|
|
397
|
+
|
|
365
398
|
Create a list dialog with the given options for the user to choose from.
|
|
366
399
|
|
|
367
400
|
The calling webhook must catch the ListDialogResult that the client will send back.
|
|
@@ -372,6 +405,7 @@ class PopupUtil:
|
|
|
372
405
|
:param request_context: Context that will be returned to the webhook server in the client callback result.
|
|
373
406
|
:return: A SapioWebhookResult with the popup as its client callback request.
|
|
374
407
|
"""
|
|
408
|
+
warnings.warn("PopupUtil is deprecated as of 24.5+. Use CallbackUtil instead.", DeprecationWarning)
|
|
375
409
|
callback = ListDialogRequest(title, multi_select, options,
|
|
376
410
|
callback_context_data=request_context)
|
|
377
411
|
return SapioWebhookResult(True, client_callback_request=callback)
|
|
@@ -380,6 +414,8 @@ class PopupUtil:
|
|
|
380
414
|
def option_popup(title: str, msg: str, options: list[str], default_option: int = 0, user_can_cancel: bool = False,
|
|
381
415
|
*, request_context: str | None = None) -> SapioWebhookResult:
|
|
382
416
|
"""
|
|
417
|
+
DEPRECATED: Make use of CallbackUtil as of 24.5.
|
|
418
|
+
|
|
383
419
|
Create an option dialog with the given options for the user to choose from.
|
|
384
420
|
|
|
385
421
|
The calling webhook must catch the OptionDialogResult that the client will send back.
|
|
@@ -394,6 +430,7 @@ class PopupUtil:
|
|
|
394
430
|
:param request_context: Context that will be returned to the webhook server in the client callback result.
|
|
395
431
|
:return: A SapioWebhookResult with the popup as its client callback request.
|
|
396
432
|
"""
|
|
433
|
+
warnings.warn("PopupUtil is deprecated as of 24.5+. Use CallbackUtil instead.", DeprecationWarning)
|
|
397
434
|
callback = OptionDialogRequest(title, msg, options, default_option, user_can_cancel,
|
|
398
435
|
callback_context_data=request_context)
|
|
399
436
|
return SapioWebhookResult(True, client_callback_request=callback)
|
|
@@ -402,6 +439,8 @@ class PopupUtil:
|
|
|
402
439
|
def ok_popup(title: str, msg: str, user_can_cancel: bool = False, *, request_context: str | None = None) \
|
|
403
440
|
-> SapioWebhookResult:
|
|
404
441
|
"""
|
|
442
|
+
DEPRECATED: Make use of CallbackUtil as of 24.5.
|
|
443
|
+
|
|
405
444
|
Create an option dialog where the only option is "OK".
|
|
406
445
|
|
|
407
446
|
The calling webhook must catch the OptionDialogResult that the client will send back.
|
|
@@ -414,12 +453,15 @@ class PopupUtil:
|
|
|
414
453
|
:param request_context: Context that will be returned to the webhook server in the client callback result.
|
|
415
454
|
:return: A SapioWebhookResult with the popup as its client callback request.
|
|
416
455
|
"""
|
|
456
|
+
warnings.warn("PopupUtil is deprecated as of 24.5+. Use CallbackUtil instead.", DeprecationWarning)
|
|
417
457
|
return PopupUtil.option_popup(title, msg, ["OK"], 0, user_can_cancel, request_context=request_context)
|
|
418
458
|
|
|
419
459
|
@staticmethod
|
|
420
460
|
def yes_no_popup(title: str, msg: str, default_yes: bool = True, user_can_cancel: bool = False,
|
|
421
461
|
*, request_context: str | None = None) -> SapioWebhookResult:
|
|
422
462
|
"""
|
|
463
|
+
DEPRECATED: Make use of CallbackUtil as of 24.5.
|
|
464
|
+
|
|
423
465
|
Create an option dialog where the only options are "Yes" and "No".
|
|
424
466
|
|
|
425
467
|
The calling webhook must catch the OptionDialogResult that the client will send back.
|
|
@@ -433,6 +475,7 @@ class PopupUtil:
|
|
|
433
475
|
:param request_context: Context that will be returned to the webhook server in the client callback result.
|
|
434
476
|
:return: A SapioWebhookResult with the popup as its client callback request.
|
|
435
477
|
"""
|
|
478
|
+
warnings.warn("PopupUtil is deprecated as of 24.5+. Use CallbackUtil instead.", DeprecationWarning)
|
|
436
479
|
return PopupUtil.option_popup(title, msg, ["Yes", "No"], 0 if default_yes else 1, user_can_cancel,
|
|
437
480
|
request_context=request_context)
|
|
438
481
|
|
|
@@ -441,8 +484,11 @@ class PopupUtil:
|
|
|
441
484
|
def display_form_popup(title: str, field_name: str, msg: str, data_type: str = "Popup",
|
|
442
485
|
request_context: str | None = None) -> SapioWebhookResult:
|
|
443
486
|
"""
|
|
487
|
+
DEPRECATED: Make use of CallbackUtil as of 24.5.
|
|
488
|
+
|
|
444
489
|
Deprecated for PopupUtil.text_field_popup.
|
|
445
490
|
"""
|
|
491
|
+
warnings.warn("PopupUtil is deprecated as of 24.5+. Use CallbackUtil instead.", DeprecationWarning)
|
|
446
492
|
return PopupUtil.string_field_popup(title, "", field_name, msg, len(msg), False, data_type,
|
|
447
493
|
request_context=request_context, auto_size=True)
|
|
448
494
|
|
|
@@ -450,13 +496,19 @@ class PopupUtil:
|
|
|
450
496
|
def display_option_popup(title: str, msg: str, options: list[str], user_can_cancel: bool = False,
|
|
451
497
|
request_context: str | None = None) -> SapioWebhookResult:
|
|
452
498
|
"""
|
|
499
|
+
DEPRECATED: Make use of CallbackUtil as of 24.5.
|
|
500
|
+
|
|
453
501
|
Deprecated for PopupUtil.option_popup.
|
|
454
502
|
"""
|
|
503
|
+
warnings.warn("PopupUtil is deprecated as of 24.5+. Use CallbackUtil instead.", DeprecationWarning)
|
|
455
504
|
return PopupUtil.option_popup(title, msg, options, 0, user_can_cancel, request_context=request_context)
|
|
456
505
|
|
|
457
506
|
@staticmethod
|
|
458
507
|
def display_ok_popup(title: str, msg: str, request_context: str | None = None) -> SapioWebhookResult:
|
|
459
508
|
"""
|
|
509
|
+
DEPRECATED: Make use of CallbackUtil as of 24.5.
|
|
510
|
+
|
|
460
511
|
Deprecated for PopupUtil.ok_popup.
|
|
461
512
|
"""
|
|
513
|
+
warnings.warn("PopupUtil is deprecated as of 24.5+. Use CallbackUtil instead.", DeprecationWarning)
|
|
462
514
|
return PopupUtil.ok_popup(title, msg, False, request_context=request_context)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from sapiopylib.rest.User import SapioUser
|
|
2
|
+
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
3
|
+
|
|
4
|
+
from sapiopycommons.general.aliases import RecordIdentifier, ExperimentIdentifier, AliasUtil, DataTypeIdentifier
|
|
5
|
+
from sapiopycommons.general.exceptions import SapioException
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SapioNavigationLinker:
|
|
9
|
+
"""
|
|
10
|
+
Given a URL to a system's webservice API (example: https://company.exemplareln.com/webservice/api), construct
|
|
11
|
+
URLs for navigation links to various locations in the system.
|
|
12
|
+
"""
|
|
13
|
+
base_url: str
|
|
14
|
+
|
|
15
|
+
def __init__(self, url: str | SapioUser | SapioWebhookContext):
|
|
16
|
+
"""
|
|
17
|
+
:param url: A user or context object that is being used to send requests to a Sapio system, or a URL to a
|
|
18
|
+
system's webservice API.
|
|
19
|
+
"""
|
|
20
|
+
if isinstance(url, SapioWebhookContext):
|
|
21
|
+
url = url.user.url
|
|
22
|
+
elif isinstance(url, SapioUser):
|
|
23
|
+
url = url.url
|
|
24
|
+
self.base_url = url.rstrip("/").replace('webservice/api', 'veloxClient')
|
|
25
|
+
|
|
26
|
+
def data_record(self, record_identifier: RecordIdentifier, data_type_name: DataTypeIdentifier | None = None) -> str:
|
|
27
|
+
"""
|
|
28
|
+
:param record_identifier: An object that can be used to identify a record in the system, be that a record ID,
|
|
29
|
+
a data record, or a record model.
|
|
30
|
+
:param data_type_name: If the provided record identifier is a record ID, then the data type name of the record
|
|
31
|
+
must be provided in this parameter. Otherwise, this parameter is ignored.
|
|
32
|
+
:return: A URL for navigating to the input record.
|
|
33
|
+
"""
|
|
34
|
+
record_id: int = AliasUtil.to_record_id(record_identifier)
|
|
35
|
+
if data_type_name:
|
|
36
|
+
data_type_name = AliasUtil.to_data_type_name(data_type_name)
|
|
37
|
+
if not isinstance(record_identifier, int):
|
|
38
|
+
data_type_name = AliasUtil.to_data_type_name(record_identifier)
|
|
39
|
+
if not data_type_name:
|
|
40
|
+
raise SapioException("Unable to create a data record link without a data type name. "
|
|
41
|
+
"Only a record ID was provided.")
|
|
42
|
+
return self.base_url + f"/#dataType={data_type_name};recordId={record_id};view=dataRecord"
|
|
43
|
+
|
|
44
|
+
def experiment(self, experiment: ExperimentIdentifier) -> str:
|
|
45
|
+
"""
|
|
46
|
+
:param experiment: An object that can be used to identify an experiment in the system, be that an experiment
|
|
47
|
+
object, experiment protocol, or a notebook ID.
|
|
48
|
+
:return: A URL for navigating to the input experiment.
|
|
49
|
+
"""
|
|
50
|
+
return self.base_url + f"/#notebookExperimentId={AliasUtil.to_notebook_id(experiment)};view=eln"
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
class StorageUtil:
|
|
2
|
+
"""
|
|
3
|
+
A collection of utilities intended for converting to and from various forms of representing positions on a storage
|
|
4
|
+
unit, such as character + integer (e.g. A1), two integers (e.g. (0, 0)), or a single integer index (e.g. 1).
|
|
5
|
+
Integers are also sometimes zero-indexed and sometimes one-indexed, so both are supported.
|
|
6
|
+
"""
|
|
7
|
+
@staticmethod
|
|
8
|
+
def map_index_to_coordinate(index: int, size: int, fill_by_row: bool = True, zero_indexed_input: bool = False,
|
|
9
|
+
zero_indexed_output: bool = False, char_row_output: bool = True) \
|
|
10
|
+
-> tuple[int | str, int]:
|
|
11
|
+
"""
|
|
12
|
+
Convert an index representing a position on a plate/storage unit to a coordinate pair on that plate/storage
|
|
13
|
+
unit, able to be used in row/column storage fields on records. This will output a value for any input index;
|
|
14
|
+
it is up to the caller to determine if the output will actually fit on the plate/storage unit.
|
|
15
|
+
(The index should be within the range [1 < index < rows * columns] for one-indexed values.)
|
|
16
|
+
|
|
17
|
+
By default, expects the input to be a one-indexed integer filled row-by-row with the output being a
|
|
18
|
+
character, integer pair where the output integer is one-indexed.
|
|
19
|
+
|
|
20
|
+
:param index: The index to map to a coordinate position.
|
|
21
|
+
:param size: The number of columns or rows in the plate/storage unit, depending on whether you are filling by
|
|
22
|
+
row or by column.
|
|
23
|
+
:param fill_by_row: If true, map positions row-by-row (A1, A2, A3... B1, B2...) and use the above size as the
|
|
24
|
+
number of columns in the plate/storage unit. If false, map positions column-by-column (A1, B1, C1... A2,
|
|
25
|
+
B2...) and use the above size as the number of rows.
|
|
26
|
+
:param zero_indexed_input: If true, the input index is zero-indexed. If false, then they are one-indexed.
|
|
27
|
+
This does not influence the output, only the function's understanding of the input.
|
|
28
|
+
:param zero_indexed_output: If true, the output index is zero-indexed. If false, then it is one-indexed.
|
|
29
|
+
Has no effect on the column output if the column is set to output as a character.
|
|
30
|
+
:param char_row_output: If true, the output row value is converted to a character where 0 = A, 1 = B, 25 = Z,
|
|
31
|
+
26 = AA, 27 = AB, etc. If false, then it is returned as an integer.
|
|
32
|
+
:return: A tuple representing a coordinate pair (row value, column value). The row value may be either an
|
|
33
|
+
integer or a string, while the column value is always an integer, influenced by the input parameters.
|
|
34
|
+
"""
|
|
35
|
+
# If the given index isn't zero-indexed, then make it zero-indexed by subtracting one.
|
|
36
|
+
if not zero_indexed_input:
|
|
37
|
+
index -= 1
|
|
38
|
+
|
|
39
|
+
row: int = index // size
|
|
40
|
+
col: int = index % size
|
|
41
|
+
|
|
42
|
+
# If fill by row is false, then the above calculations are flipped,
|
|
43
|
+
# meaning the row is actually the column and vice versa.
|
|
44
|
+
if not fill_by_row:
|
|
45
|
+
temp = row
|
|
46
|
+
row = col
|
|
47
|
+
col = temp
|
|
48
|
+
# The column and row are zero-indexed by default. If it should be one-indexed, add one.
|
|
49
|
+
if not zero_indexed_output:
|
|
50
|
+
col += 1
|
|
51
|
+
# Only add one to the row if it won't be converted to a character.
|
|
52
|
+
if not char_row_output:
|
|
53
|
+
row += 1
|
|
54
|
+
return StorageUtil.map_index_to_char(row, True) if char_row_output else row, col
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def map_coordinate_to_index(row: int | str, col: int | str, size: int, fill_by_row: bool = True,
|
|
58
|
+
zero_indexed_input: bool = False, zero_indexed_output: bool = False) -> int:
|
|
59
|
+
"""
|
|
60
|
+
Map row and column coordinates on a plate/storage unit to the index of that position.
|
|
61
|
+
|
|
62
|
+
By default, expects the input to be provided as a character, integer pair and outputs a single row-by-row
|
|
63
|
+
one-indexed integer.
|
|
64
|
+
|
|
65
|
+
:param row: The row coordinate of the position as a string of characters from A-Z or a zero-indexed integer.
|
|
66
|
+
:param col: The column coordinate of the position as an integer which may be zero-indexed or one-indexed.
|
|
67
|
+
(This integer may be in string form, such as is the case with column fields on storable records.)
|
|
68
|
+
:param size: The number of columns or rows in the plate/storage unit, depending on whether you are filling by
|
|
69
|
+
row or by column.
|
|
70
|
+
:param fill_by_row: If true, map positions row-by-row (A1, A2, A3... B1, B2...) and use the above size as the
|
|
71
|
+
number of columns in the plate/storage unit. If false, map positions column-by-column (A1, B1, C1... A2,
|
|
72
|
+
B2...) and use the above size as the number of rows.
|
|
73
|
+
:param zero_indexed_input: If true, the input coordinates for the row and column is zero-indexed. If false,
|
|
74
|
+
then they are one-indexed. This does not influence the output, only the function's understanding of the
|
|
75
|
+
input. This also has no effect if the input row is a string.
|
|
76
|
+
:param zero_indexed_output: If true, the output index is zero-indexed. If false, then it is one-indexed.
|
|
77
|
+
:return: The index of the storage position at the input row and column.
|
|
78
|
+
"""
|
|
79
|
+
# If the column was provided as a string, cast it to an int.
|
|
80
|
+
if isinstance(col, str):
|
|
81
|
+
col: int = int(col)
|
|
82
|
+
# If the input isn't zero-indexed, then make it zero-indexed.
|
|
83
|
+
if not zero_indexed_input:
|
|
84
|
+
col -= 1
|
|
85
|
+
# Only subtract from the row if it's already in integer form.
|
|
86
|
+
# If it's a string, it'll be converted to a zero-indexed integer.
|
|
87
|
+
if isinstance(row, int):
|
|
88
|
+
row -= 1
|
|
89
|
+
# If the input row is a string, convert it to a zero-indexed integer.
|
|
90
|
+
if isinstance(row, str):
|
|
91
|
+
row: int = StorageUtil.map_char_to_index(row, True)
|
|
92
|
+
|
|
93
|
+
# Convert the row and column indices to a singular index across the entire storage unit.
|
|
94
|
+
index: int = row * size + col if fill_by_row else col * size + row
|
|
95
|
+
|
|
96
|
+
# The index is zero-indexed by default. If it should be one-indexed, add one.
|
|
97
|
+
if not zero_indexed_output:
|
|
98
|
+
index += 1
|
|
99
|
+
return index
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def map_index_to_char(index: int, zero_indexed_input: bool = False) -> str:
|
|
103
|
+
"""
|
|
104
|
+
Map a given base-10 integer to a base-26 value where 0 = A, 1 = B, 25 = Z, 26 = AA, 27 = AB, etc.
|
|
105
|
+
Useful for mapping the index of a row to the character(s) representing that row in a storage unit.
|
|
106
|
+
May also be used for mapping the index to an Excel sheet's columns.
|
|
107
|
+
|
|
108
|
+
By default, expects the input as a one-indexed value.
|
|
109
|
+
|
|
110
|
+
:param index: The index to map to a character.
|
|
111
|
+
:param zero_indexed_input: If true, the input index is zero-indexed. If false, then they are one-indexed.
|
|
112
|
+
This does not influence the output, only the function's understanding of the input.
|
|
113
|
+
:return: The input integer mapped to a string representing that integer's position.
|
|
114
|
+
"""
|
|
115
|
+
# If the given index isn't zero-indexed, then make it zero-indexed by subtracting one.
|
|
116
|
+
if not zero_indexed_input:
|
|
117
|
+
index -= 1
|
|
118
|
+
chars: str = ""
|
|
119
|
+
while index >= 0:
|
|
120
|
+
# Add new characters to the front of the string.
|
|
121
|
+
chars = chr(ord("A") + index % 26) + chars
|
|
122
|
+
# Reduce the index by the amount accounted for by the character that was just added.
|
|
123
|
+
index = index // 26 - 1
|
|
124
|
+
return chars
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def map_char_to_index(chars: str, zero_indexed_output: bool = False) -> int:
|
|
128
|
+
"""
|
|
129
|
+
Map a given base-26 value of characters to a base-10 integer where A = 0, B = 1, Z = 25, AA = 26, AB = 27, etc.
|
|
130
|
+
Useful for mapping the character(s) representing a row in a storage unit to that row's index.
|
|
131
|
+
May also be used for mapping the index of an Excel sheet's columns.
|
|
132
|
+
|
|
133
|
+
By default, provides the output as a one-indexed value.
|
|
134
|
+
|
|
135
|
+
:param chars: A string of characters to be converted to an index. Characters are expected to be uppercase
|
|
136
|
+
characters in the range A to Z.
|
|
137
|
+
:param zero_indexed_output: If true, the output index is zero-indexed. If false, then it is one-indexed.
|
|
138
|
+
:return: The input character(s) converted to an index.
|
|
139
|
+
"""
|
|
140
|
+
# Reverse iterate over the characters of the string and determine the value of each individual character.
|
|
141
|
+
# The value is multiplied by the base of the character given its digit position (26^0, 26^1, etc.)
|
|
142
|
+
value: int = 0
|
|
143
|
+
for i, c in enumerate(reversed(chars)):
|
|
144
|
+
value += (ord(c) - ord("A") + 1) * (26 ** i)
|
|
145
|
+
# The character value is one-indexed by default. If it should be zero-indexed, subtract one.
|
|
146
|
+
if zero_indexed_output:
|
|
147
|
+
value -= 1
|
|
148
|
+
return value
|