sapiopycommons 2024.7.25a299__tar.gz → 2024.8.2a301__tar.gz
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-2024.7.25a299 → sapiopycommons-2024.8.2a301}/PKG-INFO +1 -1
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/pyproject.toml +1 -1
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/callbacks/callback_util.py +59 -22
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/datatype/attachment_util.py +15 -6
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/eln/experiment_handler.py +66 -29
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/files/complex_data_loader.py +1 -1
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/files/file_bridge.py +1 -1
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/files/file_util.py +13 -4
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/files/file_validator.py +1 -2
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/general/aliases.py +21 -1
- sapiopycommons-2024.8.2a301/src/sapiopycommons/general/audit_log.py +200 -0
- sapiopycommons-2024.8.2a301/src/sapiopycommons/general/sapio_links.py +48 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/recordmodel/record_handler.py +78 -16
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/rules/eln_rule_handler.py +6 -22
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/rules/on_save_rule_handler.py +6 -28
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/webhook/webhook_handlers.py +58 -23
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/.gitignore +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/LICENSE +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/README.md +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/__init__.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/callbacks/__init__.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/chem/IndigoMolecules.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/chem/Molecules.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/chem/__init__.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/datatype/__init__.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/eln/__init__.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/eln/experiment_report_util.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/eln/plate_designer.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/files/__init__.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/files/file_bridge_handler.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/files/file_data_handler.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/files/file_writer.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/general/__init__.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/general/accession_service.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/general/custom_report_util.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/general/exceptions.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/general/popup_util.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/general/storage_util.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/general/time_util.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/multimodal/multimodal.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/multimodal/multimodal_data.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/processtracking/__init__.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/processtracking/endpoints.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/recordmodel/__init__.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/rules/__init__.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/webhook/__init__.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/tests/_do_not_add_init_py_here +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/tests/accession_test.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/tests/bio_reg_test.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/tests/chem_test.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/tests/data_type_models.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/tests/kappa.chains.fasta +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/tests/mafft_test.py +0 -0
- {sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/tests/test.gb +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: sapiopycommons
|
|
3
|
-
Version: 2024.
|
|
3
|
+
Version: 2024.8.2a301
|
|
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>
|
|
@@ -15,7 +15,7 @@ from sapiopylib.rest.pojo.datatype.FieldDefinition import AbstractVeloxFieldDefi
|
|
|
15
15
|
from sapiopylib.rest.pojo.webhook.ClientCallbackRequest import OptionDialogRequest, ListDialogRequest, \
|
|
16
16
|
FormEntryDialogRequest, InputDialogCriteria, TableEntryDialogRequest, ESigningRequestPojo, \
|
|
17
17
|
DataRecordDialogRequest, InputSelectionRequest, FilePromptRequest, MultiFilePromptRequest, \
|
|
18
|
-
TempTableSelectionRequest
|
|
18
|
+
TempTableSelectionRequest, DisplayPopupRequest, PopupType
|
|
19
19
|
from sapiopylib.rest.pojo.webhook.ClientCallbackResult import ESigningResponsePojo
|
|
20
20
|
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
21
21
|
from sapiopylib.rest.pojo.webhook.WebhookEnums import FormAccessLevel, ScanToSelectCriteria, SearchType
|
|
@@ -52,11 +52,23 @@ class CallbackUtil:
|
|
|
52
52
|
Set the width that dialogs will appear as for those dialogs that support specifying their width.
|
|
53
53
|
|
|
54
54
|
:param width_pixels: The number of pixels wide that dialogs will appear as.
|
|
55
|
-
:param width_percent: The percentage of the client's screen width that dialogs
|
|
55
|
+
:param width_percent: The percentage (as a value between 0 and 1) of the client's screen width that dialogs
|
|
56
|
+
will appear as.
|
|
56
57
|
"""
|
|
57
58
|
self.width_pixels = width_pixels
|
|
58
59
|
self.width_percent = width_percent
|
|
59
|
-
|
|
60
|
+
|
|
61
|
+
def toaster_popup(self, message: str, title: str = "", popup_type: PopupType = PopupType.Info) -> None:
|
|
62
|
+
"""
|
|
63
|
+
Display a toaster popup in the bottom right corner of the user's screen.
|
|
64
|
+
|
|
65
|
+
:param message: The message to display in the toaster.
|
|
66
|
+
:param title: The title to display at the top of the toaster.
|
|
67
|
+
:param popup_type: The popup type to use for the toaster. This controls the color that the toaster appears with.
|
|
68
|
+
Info is blue, Success is green, Warning is yellow, and Error is red
|
|
69
|
+
"""
|
|
70
|
+
self.callback.display_popup(DisplayPopupRequest(title, message, popup_type))
|
|
71
|
+
|
|
60
72
|
def option_dialog(self, title: str, msg: str, options: list[str], default_option: int = 0,
|
|
61
73
|
user_can_cancel: bool = False) -> str:
|
|
62
74
|
"""
|
|
@@ -71,7 +83,8 @@ class CallbackUtil:
|
|
|
71
83
|
SapioUserCancelledException is thrown.
|
|
72
84
|
:return: The name of the button that the user selected.
|
|
73
85
|
"""
|
|
74
|
-
request = OptionDialogRequest(title, msg, options, default_option, user_can_cancel
|
|
86
|
+
request = OptionDialogRequest(title, msg, options, default_option, user_can_cancel,
|
|
87
|
+
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
75
88
|
response: int | None = self.callback.show_option_dialog(request)
|
|
76
89
|
if response is None:
|
|
77
90
|
raise SapioUserCancelledException()
|
|
@@ -111,16 +124,20 @@ class CallbackUtil:
|
|
|
111
124
|
"""
|
|
112
125
|
return self.option_dialog(title, msg, ["Yes", "No"], 0 if default_yes else 1, False) == "Yes"
|
|
113
126
|
|
|
114
|
-
def list_dialog(self, title: str, options: list[str], multi_select: bool = False
|
|
127
|
+
def list_dialog(self, title: str, options: list[str], multi_select: bool = False,
|
|
128
|
+
preselected_values: list[str] | None = None) -> list[str]:
|
|
115
129
|
"""
|
|
116
130
|
Create a list dialog with the given options for the user to choose from.
|
|
117
131
|
|
|
118
132
|
:param title: The title of the dialog.
|
|
119
133
|
:param options: The list options that the user has to choose from.
|
|
120
134
|
:param multi_select: Whether the user is able to select multiple options from the list.
|
|
135
|
+
:param preselected_values: A list of values that will already be selected when the list dialog is created. The
|
|
136
|
+
user can unselect these values if they want to.
|
|
121
137
|
:return: The list of options that the user selected.
|
|
122
138
|
"""
|
|
123
|
-
request = ListDialogRequest(title, multi_select, options
|
|
139
|
+
request = ListDialogRequest(title, multi_select, options, preselected_values,
|
|
140
|
+
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
124
141
|
response: list[str] | None = self.callback.show_list_dialog(request)
|
|
125
142
|
if response is None:
|
|
126
143
|
raise SapioUserCancelledException()
|
|
@@ -163,8 +180,6 @@ class CallbackUtil:
|
|
|
163
180
|
builder = FormBuilder(data_type, display_name, plural_display_name)
|
|
164
181
|
for field_def in fields:
|
|
165
182
|
field_name = field_def.data_field_name
|
|
166
|
-
if values and hasattr(field_def, "default_value"):
|
|
167
|
-
field_def.default_value = values.get(field_name)
|
|
168
183
|
column: int = 0
|
|
169
184
|
span: int = 4
|
|
170
185
|
if column_positions and field_name in column_positions:
|
|
@@ -173,7 +188,8 @@ class CallbackUtil:
|
|
|
173
188
|
span = position[1]
|
|
174
189
|
builder.add_field(field_def, column, span)
|
|
175
190
|
|
|
176
|
-
request = FormEntryDialogRequest(title, msg, builder.get_temporary_data_type()
|
|
191
|
+
request = FormEntryDialogRequest(title, msg, builder.get_temporary_data_type(), values,
|
|
192
|
+
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
177
193
|
response: FieldMap | None = self.callback.show_form_entry_dialog(request)
|
|
178
194
|
if response is None:
|
|
179
195
|
raise SapioUserCancelledException()
|
|
@@ -215,13 +231,13 @@ class CallbackUtil:
|
|
|
215
231
|
modifier = FieldModifier(visible=True, editable=editable)
|
|
216
232
|
|
|
217
233
|
# Build the form using only those fields that are desired.
|
|
234
|
+
values: dict[str, Any] = {}
|
|
218
235
|
builder = FormBuilder(data_type, type_def.display_name, type_def.plural_display_name)
|
|
219
236
|
for field_name in fields:
|
|
220
237
|
field_def = field_defs.get(field_name)
|
|
221
238
|
if field_def is None:
|
|
222
239
|
raise SapioException(f"No field of name \"{field_name}\" in field definitions of type \"{data_type}\"")
|
|
223
|
-
|
|
224
|
-
field_def.default_value = record.get_field_value(field_name)
|
|
240
|
+
values[field_name] = record.get_field_value(field_name)
|
|
225
241
|
column: int = 0
|
|
226
242
|
span: int = 4
|
|
227
243
|
if column_positions and field_name in column_positions:
|
|
@@ -230,7 +246,8 @@ class CallbackUtil:
|
|
|
230
246
|
span = position[1]
|
|
231
247
|
builder.add_field(modifier.modify_field(field_def), column, span)
|
|
232
248
|
|
|
233
|
-
request = FormEntryDialogRequest(title, msg, builder.get_temporary_data_type()
|
|
249
|
+
request = FormEntryDialogRequest(title, msg, builder.get_temporary_data_type(), values,
|
|
250
|
+
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
234
251
|
response: FieldMap | None = self.callback.show_form_entry_dialog(request)
|
|
235
252
|
if response is None:
|
|
236
253
|
raise SapioUserCancelledException()
|
|
@@ -245,7 +262,8 @@ class CallbackUtil:
|
|
|
245
262
|
:param field: The definition for a field that the user must provide input to.
|
|
246
263
|
:return: The response value from the user for the given field.
|
|
247
264
|
"""
|
|
248
|
-
request = InputDialogCriteria(title, msg, field,
|
|
265
|
+
request = InputDialogCriteria(title, msg, field,
|
|
266
|
+
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
249
267
|
response: Any | None = self.callback.show_input_dialog(request)
|
|
250
268
|
if response is None:
|
|
251
269
|
raise SapioUserCancelledException()
|
|
@@ -322,6 +340,8 @@ class CallbackUtil:
|
|
|
322
340
|
msg: str,
|
|
323
341
|
fields: list[AbstractVeloxFieldDefinition],
|
|
324
342
|
values: list[FieldMap],
|
|
343
|
+
group_by: str | None = None,
|
|
344
|
+
image_data: list[bytes] | None = None,
|
|
325
345
|
*,
|
|
326
346
|
data_type: str = "Default",
|
|
327
347
|
display_name: str | None = None,
|
|
@@ -335,6 +355,10 @@ class CallbackUtil:
|
|
|
335
355
|
:param fields: The definitions of the fields to display as table columns. Fields will be displayed in the order
|
|
336
356
|
they are provided in this list.
|
|
337
357
|
:param values: The values to set for each row of the table.
|
|
358
|
+
:param group_by: If provided, the created table dialog will be grouped by the field with this name by default.
|
|
359
|
+
The user may remove this grouping if they want to.
|
|
360
|
+
:param image_data: The bytes to the images that should be displayed in the rows of the table. Each element in
|
|
361
|
+
the image data list corresponds to the element at the same index in the values list.
|
|
338
362
|
:param data_type: The data type name for the temporary data type that will be created for this table.
|
|
339
363
|
:param display_name: The display name for the temporary data type. If not provided, defaults to the data type
|
|
340
364
|
name.
|
|
@@ -356,7 +380,9 @@ class CallbackUtil:
|
|
|
356
380
|
for field in fields:
|
|
357
381
|
builder.add_field(modifier.modify_field(field))
|
|
358
382
|
|
|
359
|
-
request = TableEntryDialogRequest(title, msg, builder.get_temporary_data_type(), values
|
|
383
|
+
request = TableEntryDialogRequest(title, msg, builder.get_temporary_data_type(), values,
|
|
384
|
+
record_image_data_list=image_data, group_by_field=group_by,
|
|
385
|
+
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
360
386
|
response: list[FieldMap] | None = self.callback.show_table_entry_dialog(request)
|
|
361
387
|
if response is None:
|
|
362
388
|
raise SapioUserCancelledException()
|
|
@@ -367,7 +393,9 @@ class CallbackUtil:
|
|
|
367
393
|
msg: str,
|
|
368
394
|
fields: list[str],
|
|
369
395
|
records: list[SapioRecord],
|
|
370
|
-
editable: bool | None = True
|
|
396
|
+
editable: bool | None = True,
|
|
397
|
+
group_by: str | None = None,
|
|
398
|
+
image_data: list[bytes] | None = None) -> list[FieldMap]:
|
|
371
399
|
"""
|
|
372
400
|
Create a table dialog where the user may input data into the fields of the table. The table is constructed from
|
|
373
401
|
a given list of records of a singular type. Provided field names must match fields on the definition of the data
|
|
@@ -384,6 +412,10 @@ class CallbackUtil:
|
|
|
384
412
|
they are provided in this list.
|
|
385
413
|
:param editable: If true, all fields are displayed as editable. If false, all fields are displayed as
|
|
386
414
|
uneditable. If none, only those fields that are defined as editable by the data designer will be editable.
|
|
415
|
+
:param group_by: If provided, the created table dialog will be grouped by the field with this name by default.
|
|
416
|
+
The user may remove this grouping if they want to.
|
|
417
|
+
:param image_data: The bytes to the images that should be displayed in the rows of the table. Each element in
|
|
418
|
+
the image data list corresponds to the element at the same index in the records list.
|
|
387
419
|
:return: A list of dictionaries mapping the data field names of the given field definitions to the response
|
|
388
420
|
value from the user for that field for each row.
|
|
389
421
|
"""
|
|
@@ -410,7 +442,9 @@ class CallbackUtil:
|
|
|
410
442
|
raise SapioException(f"No field of name \"{field_name}\" in field definitions of type \"{data_type}\"")
|
|
411
443
|
builder.add_field(modifier.modify_field(field_def))
|
|
412
444
|
|
|
413
|
-
request = TableEntryDialogRequest(title, msg, builder.get_temporary_data_type(), field_map_list
|
|
445
|
+
request = TableEntryDialogRequest(title, msg, builder.get_temporary_data_type(), field_map_list,
|
|
446
|
+
record_image_data_list=image_data, group_by_field=group_by,
|
|
447
|
+
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
414
448
|
response: list[FieldMap] | None = self.callback.show_table_entry_dialog(request)
|
|
415
449
|
if response is None:
|
|
416
450
|
raise SapioUserCancelledException()
|
|
@@ -567,7 +601,8 @@ class CallbackUtil:
|
|
|
567
601
|
for field in final_fields:
|
|
568
602
|
builder.add_field(field)
|
|
569
603
|
|
|
570
|
-
request = TableEntryDialogRequest(title, msg, builder.get_temporary_data_type(), values
|
|
604
|
+
request = TableEntryDialogRequest(title, msg, builder.get_temporary_data_type(), values,
|
|
605
|
+
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
571
606
|
response: list[FieldMap] | None = self.callback.show_table_entry_dialog(request)
|
|
572
607
|
if response is None:
|
|
573
608
|
raise SapioUserCancelledException()
|
|
@@ -616,7 +651,8 @@ class CallbackUtil:
|
|
|
616
651
|
raise SapioException(f"The data type \"{data_type}\" does not have a layout by the name "
|
|
617
652
|
f"\"{layout_name}\" in the system.")
|
|
618
653
|
|
|
619
|
-
request = DataRecordDialogRequest(title, record, layout, minimized, access_level, plugin_path_list
|
|
654
|
+
request = DataRecordDialogRequest(title, record, layout, minimized, access_level, plugin_path_list,
|
|
655
|
+
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
620
656
|
response: bool = self.callback.data_record_form_view_dialog(request)
|
|
621
657
|
if not response:
|
|
622
658
|
raise SapioUserCancelledException()
|
|
@@ -663,7 +699,7 @@ class CallbackUtil:
|
|
|
663
699
|
return response
|
|
664
700
|
|
|
665
701
|
def record_selection_dialog(self, msg: str, fields: list[str], records: list[SapioRecord],
|
|
666
|
-
multi_select: bool = True) -> list[
|
|
702
|
+
multi_select: bool = True) -> list[SapioRecord]:
|
|
667
703
|
"""
|
|
668
704
|
Create a record selection dialog for a list of records for the user to choose from. Provided field names must
|
|
669
705
|
match fields on the definition of the data type of the given records.
|
|
@@ -676,7 +712,7 @@ class CallbackUtil:
|
|
|
676
712
|
they are provided in this list.
|
|
677
713
|
:param records: The records to display as rows in the table.
|
|
678
714
|
:param multi_select: Whether the user is able to select multiple records from the list.
|
|
679
|
-
:return: A list of
|
|
715
|
+
:return: A list of the selected records.
|
|
680
716
|
"""
|
|
681
717
|
data_types: set[str] = {x.data_type_name for x in records}
|
|
682
718
|
if len(data_types) > 1:
|
|
@@ -807,7 +843,8 @@ class CallbackUtil:
|
|
|
807
843
|
for field in additional_fields:
|
|
808
844
|
builder.add_field(field)
|
|
809
845
|
temp_dt = builder.get_temporary_data_type()
|
|
810
|
-
request = ESigningRequestPojo(title, msg, show_comment, temp_dt
|
|
846
|
+
request = ESigningRequestPojo(title, msg, show_comment, temp_dt,
|
|
847
|
+
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
811
848
|
response: ESigningResponsePojo | None = self.callback.show_esign_dialog(request)
|
|
812
849
|
if response is None:
|
|
813
850
|
raise SapioUserCancelledException()
|
|
@@ -906,7 +943,7 @@ class CallbackUtil:
|
|
|
906
943
|
:param file_name: The name of the file.
|
|
907
944
|
:param file_data: The data of the file, provided as either a string or as a bytes array.
|
|
908
945
|
"""
|
|
909
|
-
data = io.
|
|
946
|
+
data = io.BytesIO(file_data.encode() if isinstance(file_data, str) else file_data)
|
|
910
947
|
self.callback.send_file(file_name, False, data)
|
|
911
948
|
|
|
912
949
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import io
|
|
2
2
|
|
|
3
|
+
from sapiopylib.rest.DataMgmtService import DataMgmtServer
|
|
3
4
|
from sapiopylib.rest.User import SapioUser
|
|
4
5
|
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
5
6
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
|
|
@@ -12,31 +13,35 @@ from sapiopycommons.recordmodel.record_handler import RecordHandler
|
|
|
12
13
|
# FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
|
|
13
14
|
class AttachmentUtil:
|
|
14
15
|
@staticmethod
|
|
15
|
-
def get_attachment_bytes(context: SapioWebhookContext, attachment: SapioRecord) -> bytes:
|
|
16
|
+
def get_attachment_bytes(context: SapioWebhookContext | SapioUser, attachment: SapioRecord) -> bytes:
|
|
16
17
|
"""
|
|
17
18
|
Get the data bytes for the given attachment record. Makes a webservice call to retrieve the data.
|
|
18
19
|
|
|
19
|
-
:param context: The current webhook context.
|
|
20
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
20
21
|
:param attachment: The attachment record.
|
|
21
22
|
:return: The bytes for the attachment's file data.
|
|
22
23
|
"""
|
|
23
24
|
attachment = AliasUtil.to_data_record(attachment)
|
|
25
|
+
if isinstance(context, SapioWebhookContext):
|
|
26
|
+
dr_man = context.data_record_manager
|
|
27
|
+
else:
|
|
28
|
+
dr_man = DataMgmtServer.get_data_record_manager(context)
|
|
24
29
|
with io.BytesIO() as data_sink:
|
|
25
30
|
def consume_data(chunk: bytes):
|
|
26
31
|
data_sink.write(chunk)
|
|
27
|
-
|
|
32
|
+
dr_man.get_attachment_data(attachment, consume_data)
|
|
28
33
|
data_sink.flush()
|
|
29
34
|
data_sink.seek(0)
|
|
30
35
|
file_bytes = data_sink.read()
|
|
31
36
|
return file_bytes
|
|
32
37
|
|
|
33
38
|
@staticmethod
|
|
34
|
-
def set_attachment_bytes(context: SapioWebhookContext, attachment: SapioRecord,
|
|
39
|
+
def set_attachment_bytes(context: SapioWebhookContext | SapioUser, attachment: SapioRecord,
|
|
35
40
|
file_name: str, file_bytes: bytes) -> None:
|
|
36
41
|
"""
|
|
37
42
|
Set the attachment data for a given attachment record. Makes a webservice call to set the data.
|
|
38
43
|
|
|
39
|
-
:param context: The current webhook context.
|
|
44
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
40
45
|
:param attachment: The attachment record. Must be an existing data record that is an attachment type.
|
|
41
46
|
:param file_name: The name of the attachment.
|
|
42
47
|
:param file_bytes: The bytes of the attachment data.
|
|
@@ -45,8 +50,12 @@ class AttachmentUtil:
|
|
|
45
50
|
raise SapioException("Provided record cannot have its attachment data set, as it does not exist in the "
|
|
46
51
|
"system yet.")
|
|
47
52
|
attachment = AliasUtil.to_data_record(attachment)
|
|
53
|
+
if isinstance(context, SapioWebhookContext):
|
|
54
|
+
dr_man = context.data_record_manager
|
|
55
|
+
else:
|
|
56
|
+
dr_man = DataMgmtServer.get_data_record_manager(context)
|
|
48
57
|
with io.BytesIO(file_bytes) as stream:
|
|
49
|
-
|
|
58
|
+
dr_man.set_attachment_data(attachment, file_name, stream)
|
|
50
59
|
|
|
51
60
|
@staticmethod
|
|
52
61
|
def create_attachment(context: SapioWebhookContext | SapioUser, file_name: str, file_bytes: bytes,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import time
|
|
2
2
|
from collections.abc import Mapping, Iterable
|
|
3
3
|
|
|
4
|
+
from sapiopylib.rest.DataMgmtService import DataMgmtServer
|
|
4
5
|
from sapiopylib.rest.ELNService import ElnManager
|
|
6
|
+
from sapiopylib.rest.User import SapioUser
|
|
5
7
|
from sapiopylib.rest.pojo.DataRecord import DataRecord
|
|
6
8
|
from sapiopylib.rest.pojo.eln.ElnExperiment import ElnExperiment, TemplateExperimentQueryPojo, ElnTemplate, \
|
|
7
9
|
InitializeNotebookExperimentPojo, ElnExperimentUpdateCriteria
|
|
@@ -17,7 +19,7 @@ from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
|
|
|
17
19
|
from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelInstanceManager, RecordModelManager
|
|
18
20
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
|
|
19
21
|
|
|
20
|
-
from sapiopycommons.general.aliases import AliasUtil, SapioRecord
|
|
22
|
+
from sapiopycommons.general.aliases import AliasUtil, SapioRecord, ExperimentIdentifier
|
|
21
23
|
from sapiopycommons.general.exceptions import SapioException
|
|
22
24
|
|
|
23
25
|
Step = str | ElnEntryStep
|
|
@@ -27,7 +29,8 @@ itself."""
|
|
|
27
29
|
|
|
28
30
|
# FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
|
|
29
31
|
class ExperimentHandler:
|
|
30
|
-
|
|
32
|
+
user: SapioUser
|
|
33
|
+
context: SapioWebhookContext | None
|
|
31
34
|
"""The context that this handler is working from."""
|
|
32
35
|
|
|
33
36
|
# Basic experiment info from the context.
|
|
@@ -77,31 +80,57 @@ class ExperimentHandler:
|
|
|
77
80
|
ElnExperimentStatus.Canceled]
|
|
78
81
|
"""The set of statuses that an ELN experiment could have and be considered locked."""
|
|
79
82
|
|
|
80
|
-
def __init__(self, context: SapioWebhookContext, experiment:
|
|
83
|
+
def __init__(self, context: SapioWebhookContext | SapioUser, experiment: ExperimentIdentifier | SapioRecord | None = None):
|
|
81
84
|
"""
|
|
82
85
|
Initialization will throw an exception if there is no ELN Experiment in the provided context and no experiment
|
|
83
86
|
is provided.
|
|
84
87
|
|
|
85
|
-
:param context: The current webhook context.
|
|
88
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
86
89
|
:param experiment: If an experiment is provided that is separate from the experiment that is in the context,
|
|
87
|
-
that experiment will be used by this ExperimentHandler instead.
|
|
88
|
-
|
|
90
|
+
that experiment will be used by this ExperimentHandler instead. An experiment can be provided in various
|
|
91
|
+
forms, including an ElnExperiment, ElnExperimentProtocol, an experiment record, or a notebook experiment ID.
|
|
92
|
+
"""
|
|
93
|
+
if isinstance(context, SapioWebhookContext):
|
|
94
|
+
self.user = context.user
|
|
95
|
+
self.context = context
|
|
96
|
+
else:
|
|
97
|
+
self.user = context
|
|
98
|
+
self.context = None
|
|
89
99
|
# FR-46495 - Allow the init function of ExperimentHandler to take in an ElnExperiment that is separate from the
|
|
90
100
|
# context.
|
|
91
|
-
|
|
101
|
+
# CR-37038 - Allow other experiment object types to be provided. Convert them all down to ElnExperiment.
|
|
102
|
+
if experiment is not None:
|
|
103
|
+
# If this object is already an ElnExperiment, do nothing.
|
|
104
|
+
if isinstance(experiment, ElnExperiment):
|
|
105
|
+
pass
|
|
106
|
+
# If this object is an ElnExperimentProtocol, then we can get the ElnExperiment from the object.
|
|
107
|
+
elif isinstance(experiment, ElnExperimentProtocol):
|
|
108
|
+
experiment: ElnExperiment = experiment.eln_experiment
|
|
109
|
+
# If this object is an integer, assume it is a notebook ID that we can query the system with.
|
|
110
|
+
elif isinstance(experiment, int):
|
|
111
|
+
notebook_id: int = experiment
|
|
112
|
+
experiment: ElnExperiment = context.eln_manager.get_eln_experiment_by_id(notebook_id)
|
|
113
|
+
if not experiment:
|
|
114
|
+
raise SapioException(f"No experiment with notebook ID {notebook_id} located in the system.")
|
|
115
|
+
# If this object is a record, assume it is an experiment record that we can query the system with.
|
|
116
|
+
else:
|
|
117
|
+
record_id: int = AliasUtil.to_record_ids([experiment])[0]
|
|
118
|
+
experiment: ElnExperiment = context.eln_manager.get_eln_experiment_by_record_id(record_id)
|
|
119
|
+
if not experiment:
|
|
120
|
+
raise SapioException(f"No experiment with record ID {record_id} located in the system.")
|
|
121
|
+
if (context is None or context.eln_experiment is None) and experiment is None:
|
|
92
122
|
raise SapioException("Cannot initialize ExperimentHandler. No ELN Experiment in the context.")
|
|
93
|
-
if context.eln_experiment == experiment:
|
|
94
|
-
experiment = None
|
|
95
|
-
self.context = context
|
|
123
|
+
if context and context.eln_experiment == experiment:
|
|
124
|
+
experiment: ElnExperiment | None = None
|
|
96
125
|
|
|
97
126
|
# Get the basic information about this experiment that already exists in the context and is often used.
|
|
98
127
|
self.__eln_exp = experiment if experiment else context.eln_experiment
|
|
99
|
-
self.__protocol = ElnExperimentProtocol(experiment,
|
|
128
|
+
self.__protocol = ElnExperimentProtocol(experiment, self.user) if experiment else context.active_protocol
|
|
100
129
|
self.__exp_id = self.__protocol.get_id()
|
|
101
130
|
|
|
102
131
|
# Grab various managers that may be used.
|
|
103
|
-
self.__eln_man =
|
|
104
|
-
self.__inst_man = RecordModelManager(
|
|
132
|
+
self.__eln_man = DataMgmtServer.get_eln_manager(self.user)
|
|
133
|
+
self.__inst_man = RecordModelManager(self.user).instance_manager
|
|
105
134
|
|
|
106
135
|
# Create empty caches to fill when necessary.
|
|
107
136
|
self.__steps = {}
|
|
@@ -263,7 +292,7 @@ class ExperimentHandler:
|
|
|
263
292
|
:return: The data record for this experiment. None if it has no record.
|
|
264
293
|
"""
|
|
265
294
|
if not hasattr(self, "_ExperimentHandler__exp_record"):
|
|
266
|
-
drm = self.
|
|
295
|
+
drm = DataMgmtServer.get_data_record_manager(self.user)
|
|
267
296
|
dt = self.__eln_exp.experiment_data_type_name
|
|
268
297
|
results = drm.query_data_records_by_id(dt, [self.__eln_exp.experiment_record_id]).result_list
|
|
269
298
|
# PR-46504: Set the exp_record to None if there are no results.
|
|
@@ -580,8 +609,6 @@ class ExperimentHandler:
|
|
|
580
609
|
step.set_records(AliasUtil.to_data_records(records))
|
|
581
610
|
|
|
582
611
|
# FR-46496 - Provide alias of set_step_records for use with form entries.
|
|
583
|
-
# TODO: Provide a similar aliased function for attachment entries once sapiopylib allows setting multiple
|
|
584
|
-
# attachments to an attachment step.
|
|
585
612
|
def set_form_record(self, step: Step, record: SapioRecord) -> None:
|
|
586
613
|
"""
|
|
587
614
|
Sets the record for a form entry.
|
|
@@ -617,7 +644,7 @@ class ExperimentHandler:
|
|
|
617
644
|
if step.eln_entry.entry_type != ElnEntryType.Table:
|
|
618
645
|
raise SapioException("The provided step is not a table entry.")
|
|
619
646
|
dt: str = step.get_data_type_names()[0]
|
|
620
|
-
if not
|
|
647
|
+
if not ElnBaseDataType.is_eln_type(dt):
|
|
621
648
|
raise SapioException("The provided step is not an ELN data type entry.")
|
|
622
649
|
return self.__inst_man.add_new_records(dt, count)
|
|
623
650
|
|
|
@@ -641,7 +668,7 @@ class ExperimentHandler:
|
|
|
641
668
|
"""
|
|
642
669
|
step = self.__to_eln_step(step)
|
|
643
670
|
dt: str = step.get_data_type_names()[0]
|
|
644
|
-
if not
|
|
671
|
+
if not ElnBaseDataType.is_eln_type(dt):
|
|
645
672
|
raise SapioException("The provided step is not an ELN data type entry.")
|
|
646
673
|
if any([x.data_type_name != dt for x in records]):
|
|
647
674
|
raise SapioException("Not all of the provided records match the data type of the step.")
|
|
@@ -658,17 +685,6 @@ class ExperimentHandler:
|
|
|
658
685
|
for record in record_models:
|
|
659
686
|
record.delete()
|
|
660
687
|
|
|
661
|
-
# TODO: Remove and use the function of the same name in ElnBaseDataType in the future. Currently this function is
|
|
662
|
-
# bugged in sapiopylib and is comparing against base_type.name instead of base_type.value.
|
|
663
|
-
@staticmethod
|
|
664
|
-
def __is_eln_type(data_type: str):
|
|
665
|
-
if data_type is None or not data_type:
|
|
666
|
-
return False
|
|
667
|
-
for base_type in ElnBaseDataType:
|
|
668
|
-
if data_type.lower().startswith(base_type.value.lower()):
|
|
669
|
-
return True
|
|
670
|
-
return False
|
|
671
|
-
|
|
672
688
|
def update_step(self, step: Step,
|
|
673
689
|
entry_name: str | None = None,
|
|
674
690
|
related_entry_set: Iterable[int] | None = None,
|
|
@@ -956,6 +972,27 @@ class ExperimentHandler:
|
|
|
956
972
|
step.unlock_step()
|
|
957
973
|
step.eln_entry.entry_status = ExperimentEntryStatus.UnlockedChangesRequired
|
|
958
974
|
|
|
975
|
+
def disable_step(self, step: Step) -> None:
|
|
976
|
+
"""
|
|
977
|
+
Set the status of the input step to Disabled. This is the state that entries are in when they are waiting for
|
|
978
|
+
entries that they are dependent upon to be submitted before they can be enabled. If you have unsubmitted an
|
|
979
|
+
entry and want its dependent entries to be locked again, then you would use this to set their status to
|
|
980
|
+
disabled.
|
|
981
|
+
|
|
982
|
+
Makes a webservice call to update the step. Checks if the step is already unlocked, and does nothing if so.
|
|
983
|
+
|
|
984
|
+
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
985
|
+
list of steps in the experiment and caches them.
|
|
986
|
+
|
|
987
|
+
:param step:
|
|
988
|
+
The step to disable.
|
|
989
|
+
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
990
|
+
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
991
|
+
"""
|
|
992
|
+
step = self.__to_eln_step(step)
|
|
993
|
+
if step.eln_entry.entry_status in self.__ENTRY_LOCKED_STATUSES:
|
|
994
|
+
self.update_step(step, entry_status=ExperimentEntryStatus.Disabled)
|
|
995
|
+
|
|
959
996
|
def step_is_submitted(self, step: Step) -> bool:
|
|
960
997
|
"""
|
|
961
998
|
Determine if the input step has already been submitted.
|
|
@@ -23,7 +23,7 @@ class CDL:
|
|
|
23
23
|
"fileName": file_name
|
|
24
24
|
}
|
|
25
25
|
user: SapioUser = context if isinstance(context, SapioUser) else context.user
|
|
26
|
-
with io.
|
|
26
|
+
with io.BytesIO(file_data.encode() if isinstance(file_data, str) else file_data) as data_stream:
|
|
27
27
|
response = user.post_data_stream(sub_path, params=params, data_stream=data_stream)
|
|
28
28
|
user.raise_for_status(response)
|
|
29
29
|
# The response content is returned as bytes for a comma separated string of record IDs.
|
{sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/files/file_bridge.py
RENAMED
|
@@ -54,7 +54,7 @@ class FileBridge:
|
|
|
54
54
|
'Filepath': f"bridge://{bridge_name}/{file_path}"
|
|
55
55
|
}
|
|
56
56
|
user: SapioUser = context if isinstance(context, SapioUser) else context.user
|
|
57
|
-
with io.
|
|
57
|
+
with io.BytesIO(file_data.encode() if isinstance(file_data, str) else file_data) as data_stream:
|
|
58
58
|
response = user.post_data_stream(sub_path, params=params, data_stream=data_stream)
|
|
59
59
|
user.raise_for_status(response)
|
|
60
60
|
|
{sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/files/file_util.py
RENAMED
|
@@ -21,7 +21,8 @@ class FileUtil:
|
|
|
21
21
|
"""
|
|
22
22
|
@staticmethod
|
|
23
23
|
def tokenize_csv(file_bytes: bytes, required_headers: list[str] | None = None, header_row_index: int | None = 0,
|
|
24
|
-
seperator: str = ",", *, encoding: str | None = None
|
|
24
|
+
seperator: str = ",", *, encoding: str | None = None, exception_on_empty: bool = True) \
|
|
25
|
+
-> tuple[list[dict[str, str]], list[list[str]]]:
|
|
25
26
|
"""
|
|
26
27
|
Tokenize a CSV file. The provided file must be uniform. That is, if row 1 has 10 cells, all the rows in the file
|
|
27
28
|
must have 10 cells. Otherwise, the Pandas parser throws a tokenizer exception.
|
|
@@ -37,6 +38,8 @@ class FileUtil:
|
|
|
37
38
|
:param encoding: The encoding used to read the given file bytes. If not provided, uses utf-8. If your file
|
|
38
39
|
contains a non-utf-8 character, then a UnicodeDecodeError will be thrown. If this happens, consider using
|
|
39
40
|
ISO-8859-1 as the encoding.
|
|
41
|
+
:param exception_on_empty: Throw a user error exception if the provided file bytes result in an empty list in
|
|
42
|
+
the first element of the returned tuple.
|
|
40
43
|
:return: The CSV parsed into a list of dicts where each dict is a row, mapping the headers to the cells for
|
|
41
44
|
that row. Also returns a list of each row above the headers (the metadata), parsed into a list of each cell.
|
|
42
45
|
If the header row index is 0 or None, this list will be empty.
|
|
@@ -49,11 +52,13 @@ class FileUtil:
|
|
|
49
52
|
metadata: list[list[str]] = FileUtil.data_frame_to_lists(file_metadata)
|
|
50
53
|
# Parse the data from the file body into a list of dicts.
|
|
51
54
|
rows: list[dict[str, str]] = FileUtil.data_frame_to_dicts(file_body, required_headers, header_row_index)
|
|
55
|
+
if exception_on_empty and not rows:
|
|
56
|
+
raise SapioUserErrorException("The provided file contains no rows of information below the headers.")
|
|
52
57
|
return rows, metadata
|
|
53
58
|
|
|
54
59
|
@staticmethod
|
|
55
|
-
def tokenize_xlsx(file_bytes: bytes, required_headers: list[str] | None = None, header_row_index: int | None = 0
|
|
56
|
-
|
|
60
|
+
def tokenize_xlsx(file_bytes: bytes, required_headers: list[str] | None = None, header_row_index: int | None = 0,
|
|
61
|
+
*, exception_on_empty: bool = True) -> tuple[list[dict[str, str]], list[list[str]]]:
|
|
57
62
|
"""
|
|
58
63
|
Tokenize an XLSX file row by row.
|
|
59
64
|
|
|
@@ -64,6 +69,8 @@ class FileUtil:
|
|
|
64
69
|
row is returned in the metadata list. If input is None, then no row is considered to be the header row,
|
|
65
70
|
meaning that required headers are also ignored if any are provided. By default, the first row (0th index)
|
|
66
71
|
is assumed to be the header row.
|
|
72
|
+
:param exception_on_empty: Throw a user error exception if the provided file bytes result in an empty list in
|
|
73
|
+
the first element of the returned tuple.
|
|
67
74
|
:return: The XLSX parsed into a list of dicts where each dict is a row, mapping the headers to the cells for
|
|
68
75
|
that row. Also returns a list of each row above the headers (the metadata), parsed into a list of each cell.
|
|
69
76
|
If the header row index is 0 or None, this list will be empty.
|
|
@@ -75,6 +82,8 @@ class FileUtil:
|
|
|
75
82
|
metadata: list[list[str]] = FileUtil.data_frame_to_lists(file_metadata)
|
|
76
83
|
# Parse the data from the file body into a list of dicts.
|
|
77
84
|
rows: list[dict[str, str]] = FileUtil.data_frame_to_dicts(file_body, required_headers, header_row_index)
|
|
85
|
+
if exception_on_empty and not rows:
|
|
86
|
+
raise SapioUserErrorException("The provided file contains no rows of information below the headers.")
|
|
78
87
|
return rows, metadata
|
|
79
88
|
|
|
80
89
|
@staticmethod
|
|
@@ -229,7 +238,7 @@ class FileUtil:
|
|
|
229
238
|
:param file_data: The CSV file to be converted.
|
|
230
239
|
:return: The bytes of the CSV file converted to an XLSX file.
|
|
231
240
|
"""
|
|
232
|
-
with (io.BytesIO(file_data) if isinstance(file_data,
|
|
241
|
+
with (io.BytesIO(file_data.encode() if isinstance(file_data, str) else file_data)) as csv:
|
|
233
242
|
# Setting header to false makes pandas read the CSV as-is.
|
|
234
243
|
data_frame = pandas.read_csv(csv, sep=",", header=None)
|
|
235
244
|
|
|
@@ -8,7 +8,6 @@ from sapiopylib.rest.pojo.CustomReport import RawReportTerm, RawTermOperation
|
|
|
8
8
|
from sapiopylib.rest.pojo.datatype.FieldDefinition import VeloxIntegerFieldDefinition, VeloxStringFieldDefinition, \
|
|
9
9
|
AbstractVeloxFieldDefinition
|
|
10
10
|
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
11
|
-
from sapiopylib.rest.pojo.webhook.WebhookResult import SapioWebhookResult
|
|
12
11
|
|
|
13
12
|
from sapiopycommons.callbacks.callback_util import CallbackUtil
|
|
14
13
|
from sapiopycommons.files.file_data_handler import FileDataHandler, FilterList
|
|
@@ -80,7 +79,7 @@ class FileValidator:
|
|
|
80
79
|
|
|
81
80
|
return failed_rows
|
|
82
81
|
|
|
83
|
-
def build_violation_report(self, context:
|
|
82
|
+
def build_violation_report(self, context: SapioWebhookContext | SapioUser,
|
|
84
83
|
rule_violations: dict[int, list[ValidationRule]]) -> None:
|
|
85
84
|
"""
|
|
86
85
|
Build a simple report of any rule violations in the file to display to the user as a table dialog.
|
{sapiopycommons-2024.7.25a299 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/general/aliases.py
RENAMED
|
@@ -50,7 +50,27 @@ class AliasUtil:
|
|
|
50
50
|
|
|
51
51
|
:return: A list of record IDs for the input records.
|
|
52
52
|
"""
|
|
53
|
-
return [(
|
|
53
|
+
return [(AliasUtil.to_record_id(x)) for x in records]
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def to_record_id(record: RecordIdentifier):
|
|
57
|
+
"""
|
|
58
|
+
Convert a single variable that could be either an integer, DataRecord, PyRecordModel,
|
|
59
|
+
or WrappedRecordModel to just an integer (taking the record ID from the record).
|
|
60
|
+
|
|
61
|
+
:return: A record ID for the input record.
|
|
62
|
+
"""
|
|
63
|
+
return record if isinstance(record, int) else record.record_id
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def to_data_type_name(record: SapioRecord) -> str:
|
|
67
|
+
"""
|
|
68
|
+
Convert a single variable that could be either a Data Record, PyRecordModel, or WrappedRecordModel to the
|
|
69
|
+
data type name for that record.
|
|
70
|
+
|
|
71
|
+
:return: The data type name for the input record.
|
|
72
|
+
"""
|
|
73
|
+
return record.data_type_name
|
|
54
74
|
|
|
55
75
|
@staticmethod
|
|
56
76
|
def to_field_map_lists(records: Iterable[SapioRecord]) -> list[FieldMap]:
|