sapiopycommons 2025.5.7a514__py3-none-any.whl → 2025.5.12a519__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/ai/__init__.py +0 -0
- sapiopycommons/ai/api/fielddefinitions/proto/fields_pb2.py +43 -0
- sapiopycommons/ai/api/fielddefinitions/proto/fields_pb2.pyi +31 -0
- sapiopycommons/ai/api/fielddefinitions/proto/fields_pb2_grpc.py +24 -0
- sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def_pb2.py +123 -0
- sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def_pb2.pyi +598 -0
- sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def_pb2_grpc.py +24 -0
- sapiopycommons/ai/api/plan/proto/step_output_pb2.py +45 -0
- sapiopycommons/ai/api/plan/proto/step_output_pb2.pyi +42 -0
- sapiopycommons/ai/api/plan/proto/step_output_pb2_grpc.py +24 -0
- sapiopycommons/ai/api/plan/proto/step_pb2.py +43 -0
- sapiopycommons/ai/api/plan/proto/step_pb2.pyi +43 -0
- sapiopycommons/ai/api/plan/proto/step_pb2_grpc.py +24 -0
- sapiopycommons/ai/api/plan/script/proto/script_pb2.py +53 -0
- sapiopycommons/ai/api/plan/script/proto/script_pb2.pyi +99 -0
- sapiopycommons/ai/api/plan/script/proto/script_pb2_grpc.py +153 -0
- sapiopycommons/ai/api/plan/tool/proto/entry_pb2.py +57 -0
- sapiopycommons/ai/api/plan/tool/proto/entry_pb2.pyi +96 -0
- sapiopycommons/ai/api/plan/tool/proto/entry_pb2_grpc.py +24 -0
- sapiopycommons/ai/api/plan/tool/proto/tool_pb2.py +67 -0
- sapiopycommons/ai/api/plan/tool/proto/tool_pb2.pyi +220 -0
- sapiopycommons/ai/api/plan/tool/proto/tool_pb2_grpc.py +154 -0
- sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2.py +39 -0
- sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2.pyi +32 -0
- sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2_grpc.py +24 -0
- sapiopycommons/ai/protobuf_utils.py +454 -0
- sapiopycommons/ai/tool_service_base.py +740 -0
- sapiopycommons/callbacks/callback_util.py +64 -116
- sapiopycommons/callbacks/field_builder.py +0 -2
- sapiopycommons/customreport/auto_pagers.py +1 -2
- sapiopycommons/customreport/term_builder.py +1 -1
- sapiopycommons/datatype/pseudo_data_types.py +326 -349
- sapiopycommons/eln/experiment_handler.py +719 -336
- sapiopycommons/eln/plate_designer.py +2 -7
- sapiopycommons/files/file_util.py +4 -4
- sapiopycommons/general/accession_service.py +2 -2
- sapiopycommons/general/aliases.py +1 -4
- sapiopycommons/general/html_formatter.py +456 -0
- sapiopycommons/general/sapio_links.py +12 -4
- sapiopycommons/processtracking/custom_workflow_handler.py +1 -2
- sapiopycommons/recordmodel/record_handler.py +27 -357
- sapiopycommons/rules/eln_rule_handler.py +1 -8
- sapiopycommons/rules/on_save_rule_handler.py +1 -8
- sapiopycommons/webhook/webhook_handlers.py +0 -3
- sapiopycommons/webhook/webservice_handlers.py +2 -2
- {sapiopycommons-2025.5.7a514.dist-info → sapiopycommons-2025.5.12a519.dist-info}/METADATA +2 -2
- sapiopycommons-2025.5.12a519.dist-info/RECORD +91 -0
- sapiopycommons/eln/experiment_cache.py +0 -188
- sapiopycommons/eln/experiment_step_factory.py +0 -476
- sapiopycommons/eln/step_creation.py +0 -236
- sapiopycommons/general/data_structure_util.py +0 -115
- sapiopycommons-2025.5.7a514.dist-info/RECORD +0 -67
- {sapiopycommons-2025.5.7a514.dist-info → sapiopycommons-2025.5.12a519.dist-info}/WHEEL +0 -0
- {sapiopycommons-2025.5.7a514.dist-info → sapiopycommons-2025.5.12a519.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,7 +4,7 @@ import io
|
|
|
4
4
|
import re
|
|
5
5
|
import warnings
|
|
6
6
|
from copy import copy
|
|
7
|
-
from typing import Iterable, TypeAlias, Any, Callable
|
|
7
|
+
from typing import Iterable, TypeAlias, Any, Callable
|
|
8
8
|
from weakref import WeakValueDictionary
|
|
9
9
|
|
|
10
10
|
from requests import ReadTimeout
|
|
@@ -38,18 +38,11 @@ from sapiopycommons.general.aliases import FieldMap, SapioRecord, AliasUtil, Rec
|
|
|
38
38
|
from sapiopycommons.general.custom_report_util import CustomReportUtil
|
|
39
39
|
from sapiopycommons.general.exceptions import SapioUserCancelledException, SapioException, SapioUserErrorException, \
|
|
40
40
|
SapioDialogTimeoutException
|
|
41
|
-
from sapiopycommons.general.time_util import TimeUtil
|
|
42
41
|
from sapiopycommons.recordmodel.record_handler import RecordHandler
|
|
43
42
|
|
|
44
43
|
DataTypeLayoutIdentifier: TypeAlias = DataTypeLayout | str | None
|
|
45
44
|
|
|
46
45
|
|
|
47
|
-
# CR-47521: Updated various parameter type hints from list or Iterable to more specific type hints.
|
|
48
|
-
# If we need to iterate over the parameter, then it is Iterable.
|
|
49
|
-
# If we need to see if the parameter contains a value, then it is Container.
|
|
50
|
-
# If the length/size of the parameter is needed, then it is Collection.
|
|
51
|
-
# If we need to access the parameter by an index, then it is Sequence. (This excludes sets and dictionaries, so it's
|
|
52
|
-
# probably better to accept a Collection then cast the parameter to a list if you need to get an element from it.)
|
|
53
46
|
class CallbackUtil:
|
|
54
47
|
user: SapioUser
|
|
55
48
|
callback: ClientCallback
|
|
@@ -200,7 +193,7 @@ class CallbackUtil:
|
|
|
200
193
|
def ok_cancel_dialog(self, title: str, msg: str, default_ok: bool = True) -> bool:
|
|
201
194
|
"""
|
|
202
195
|
Create an option dialog where the only options are "OK" and "Cancel". Doesn't allow the user to cancel the
|
|
203
|
-
dialog using the X
|
|
196
|
+
dialog using the X at the top right corner.
|
|
204
197
|
|
|
205
198
|
:param title: The title of the dialog.
|
|
206
199
|
:param msg: The message to display in the dialog. This can be formatted using HTML elements.
|
|
@@ -212,7 +205,7 @@ class CallbackUtil:
|
|
|
212
205
|
def yes_no_dialog(self, title: str, msg: str, default_yes: bool = True) -> bool:
|
|
213
206
|
"""
|
|
214
207
|
Create an option dialog where the only options are "Yes" and "No". Doesn't allow the user to cancel the
|
|
215
|
-
dialog using the X
|
|
208
|
+
dialog using the X at the top right corner.
|
|
216
209
|
|
|
217
210
|
:param title: The title of the dialog.
|
|
218
211
|
:param msg: The message to display in the dialog. This can be formatted using HTML elements.
|
|
@@ -265,7 +258,7 @@ class CallbackUtil:
|
|
|
265
258
|
def form_dialog(self,
|
|
266
259
|
title: str,
|
|
267
260
|
msg: str,
|
|
268
|
-
fields:
|
|
261
|
+
fields: list[AbstractVeloxFieldDefinition],
|
|
269
262
|
values: FieldMap = None,
|
|
270
263
|
column_positions: dict[str, tuple[int, int]] = None,
|
|
271
264
|
*,
|
|
@@ -303,7 +296,7 @@ class CallbackUtil:
|
|
|
303
296
|
def record_form_dialog(self,
|
|
304
297
|
title: str,
|
|
305
298
|
msg: str,
|
|
306
|
-
fields:
|
|
299
|
+
fields: list[FieldIdentifier | FieldFilterCriteria] | DataTypeLayoutIdentifier,
|
|
307
300
|
record: SapioRecord,
|
|
308
301
|
column_positions: dict[str, tuple[int, int]] | None = None,
|
|
309
302
|
editable=None,
|
|
@@ -376,7 +369,7 @@ class CallbackUtil:
|
|
|
376
369
|
def set_record_form_dialog(self,
|
|
377
370
|
title: str,
|
|
378
371
|
msg: str,
|
|
379
|
-
fields:
|
|
372
|
+
fields: list[FieldIdentifier | FieldFilterCriteria] | DataTypeLayoutIdentifier,
|
|
380
373
|
record: SapioRecord,
|
|
381
374
|
column_positions: dict[str, tuple[int, int]] | None = None,
|
|
382
375
|
*,
|
|
@@ -419,7 +412,7 @@ class CallbackUtil:
|
|
|
419
412
|
def create_record_form_dialog(self,
|
|
420
413
|
title: str,
|
|
421
414
|
msg: str,
|
|
422
|
-
fields:
|
|
415
|
+
fields: list[FieldIdentifier | FieldFilterCriteria] | DataTypeLayoutIdentifier,
|
|
423
416
|
wrapper_type: type[WrappedType] | str,
|
|
424
417
|
column_positions: dict[str, tuple[int, int]] | None = None,
|
|
425
418
|
*,
|
|
@@ -625,14 +618,14 @@ class CallbackUtil:
|
|
|
625
618
|
def table_dialog(self,
|
|
626
619
|
title: str,
|
|
627
620
|
msg: str,
|
|
628
|
-
fields:
|
|
629
|
-
values:
|
|
621
|
+
fields: list[AbstractVeloxFieldDefinition],
|
|
622
|
+
values: list[FieldMap],
|
|
630
623
|
*,
|
|
631
624
|
data_type: DataTypeIdentifier = "Default",
|
|
632
625
|
display_name: str | None = None,
|
|
633
626
|
plural_display_name: str | None = None,
|
|
634
627
|
group_by: FieldIdentifier | None = None,
|
|
635
|
-
image_data:
|
|
628
|
+
image_data: list[bytes] | None = None) -> list[FieldMap]:
|
|
636
629
|
"""
|
|
637
630
|
Create a table dialog where the user may input data into the fields of the table. Requires that the caller
|
|
638
631
|
provide the definitions of every field in the table.
|
|
@@ -667,7 +660,7 @@ class CallbackUtil:
|
|
|
667
660
|
temp_dt.record_image_assignable = bool(image_data)
|
|
668
661
|
|
|
669
662
|
# Send the request to the user.
|
|
670
|
-
request = TableEntryDialogRequest(title, msg, temp_dt,
|
|
663
|
+
request = TableEntryDialogRequest(title, msg, temp_dt, values,
|
|
671
664
|
record_image_data_list=image_data, group_by_field=group_by,
|
|
672
665
|
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
673
666
|
response: list[FieldMap] = self.__handle_dialog_request(request, self.callback.show_table_entry_dialog)
|
|
@@ -676,15 +669,14 @@ class CallbackUtil:
|
|
|
676
669
|
def record_table_dialog(self,
|
|
677
670
|
title: str,
|
|
678
671
|
msg: str,
|
|
679
|
-
fields:
|
|
680
|
-
records:
|
|
672
|
+
fields: list[FieldIdentifier | FieldFilterCriteria] | DataTypeLayoutIdentifier,
|
|
673
|
+
records: list[SapioRecord],
|
|
681
674
|
editable=None,
|
|
682
675
|
*,
|
|
683
676
|
default_modifier: FieldModifier | None = None,
|
|
684
677
|
field_modifiers: dict[FieldIdentifier, FieldModifier] | None = None,
|
|
685
678
|
group_by: FieldIdentifier | None = None,
|
|
686
|
-
image_data:
|
|
687
|
-
index_field: str | None = None) -> list[FieldMap]:
|
|
679
|
+
image_data: list[bytes] | None = None) -> list[FieldMap]:
|
|
688
680
|
"""
|
|
689
681
|
Create a table dialog where the user may input data into the fields of the table. The table is constructed from
|
|
690
682
|
a given list of records of a singular type.
|
|
@@ -714,12 +706,6 @@ class CallbackUtil:
|
|
|
714
706
|
The user may remove this grouping if they want to.
|
|
715
707
|
:param image_data: The bytes to the images that should be displayed in the rows of the table. Each element in
|
|
716
708
|
the image data list corresponds to the element at the same index in the records list.
|
|
717
|
-
:param index_field: If provided, the returned field maps will contain a field with this name that is equal to
|
|
718
|
-
the record ID of the record at the same index in the records list. This can be used to map the results
|
|
719
|
-
back to the original records. This is used instead of using a RecordId field, as the RecordId field has
|
|
720
|
-
special behavior in the system that can cause issues if the given records are uncommitted record models
|
|
721
|
-
with negative record IDs, meaning we don't want to have a RecordId field in the field maps provided to the
|
|
722
|
-
system.
|
|
723
709
|
:return: A list of dictionaries mapping the data field names of the given field definitions to the response
|
|
724
710
|
value from the user for that field for each row.
|
|
725
711
|
"""
|
|
@@ -731,10 +717,7 @@ class CallbackUtil:
|
|
|
731
717
|
if not records:
|
|
732
718
|
raise SapioException("No records provided.")
|
|
733
719
|
data_type: str = AliasUtil.to_singular_data_type_name(records)
|
|
734
|
-
|
|
735
|
-
field_map_list: list[FieldMap] = self.__get_indexed_field_maps(records, index_field)
|
|
736
|
-
else:
|
|
737
|
-
field_map_list: list[FieldMap] = AliasUtil.to_field_map_list(records)
|
|
720
|
+
field_map_list: list[FieldMap] = AliasUtil.to_field_map_list(records)
|
|
738
721
|
|
|
739
722
|
# Convert the group_by parameter to a field name.
|
|
740
723
|
if group_by is not None:
|
|
@@ -767,13 +750,13 @@ class CallbackUtil:
|
|
|
767
750
|
def set_record_table_dialog(self,
|
|
768
751
|
title: str,
|
|
769
752
|
msg: str,
|
|
770
|
-
fields:
|
|
771
|
-
records:
|
|
753
|
+
fields: list[FieldValue] | DataTypeLayoutIdentifier,
|
|
754
|
+
records: list[SapioRecord],
|
|
772
755
|
*,
|
|
773
756
|
default_modifier: FieldModifier | None = None,
|
|
774
757
|
field_modifiers: dict[FieldIdentifier, FieldModifier] | None = None,
|
|
775
758
|
group_by: FieldIdentifier | None = None,
|
|
776
|
-
image_data:
|
|
759
|
+
image_data: list[bytes] | None = None):
|
|
777
760
|
"""
|
|
778
761
|
Create a table dialog where the user may input data into the fields of the table. The table is constructed from
|
|
779
762
|
a given list of records of a singular type. After the user submits this dialog, the values that the user
|
|
@@ -804,30 +787,25 @@ class CallbackUtil:
|
|
|
804
787
|
:param image_data: The bytes to the images that should be displayed in the rows of the table. Each element in
|
|
805
788
|
the image data list corresponds to the element at the same index in the records list.
|
|
806
789
|
"""
|
|
807
|
-
# Index the records with a field name that is the current time in milliseconds. This is done to avoid
|
|
808
|
-
# collisions with any existing field names.
|
|
809
|
-
index_field: str = f"_{TimeUtil.now_in_millis()}"
|
|
810
790
|
results: list[FieldMap] = self.record_table_dialog(title, msg, fields, records,
|
|
811
791
|
default_modifier=default_modifier,
|
|
812
792
|
field_modifiers=field_modifiers,
|
|
813
|
-
group_by=group_by, image_data=image_data
|
|
814
|
-
index_field=index_field)
|
|
793
|
+
group_by=group_by, image_data=image_data)
|
|
815
794
|
records_by_id: dict[int, SapioRecord] = self.rec_handler.map_by_id(records)
|
|
816
795
|
for result in results:
|
|
817
|
-
|
|
818
|
-
records_by_id[index].set_field_values(result)
|
|
796
|
+
records_by_id[result["RecordId"]].set_field_values(result)
|
|
819
797
|
|
|
820
798
|
def create_record_table_dialog(self,
|
|
821
799
|
title: str,
|
|
822
800
|
msg: str,
|
|
823
|
-
fields:
|
|
801
|
+
fields: list[FieldValue] | DataTypeLayoutIdentifier,
|
|
824
802
|
wrapper_type: type[WrappedType] | str,
|
|
825
803
|
count: int | tuple[int, int],
|
|
826
804
|
*,
|
|
827
805
|
default_modifier: FieldModifier | None = None,
|
|
828
806
|
field_modifiers: dict[FieldIdentifier, FieldModifier] | None = None,
|
|
829
807
|
group_by: FieldIdentifier | None = None,
|
|
830
|
-
image_data:
|
|
808
|
+
image_data: list[bytes] | None = None,
|
|
831
809
|
require_input: bool = False,
|
|
832
810
|
repeat_message: str | None = "Please provide a value to continue.") \
|
|
833
811
|
-> list[WrappedType] | list[PyRecordModel]:
|
|
@@ -882,15 +860,14 @@ class CallbackUtil:
|
|
|
882
860
|
def record_adaptive_dialog(self,
|
|
883
861
|
title: str,
|
|
884
862
|
msg: str,
|
|
885
|
-
fields:
|
|
886
|
-
records:
|
|
863
|
+
fields: list[FieldIdentifier | FieldFilterCriteria] | DataTypeLayoutIdentifier,
|
|
864
|
+
records: list[SapioRecord],
|
|
887
865
|
*,
|
|
888
866
|
default_modifier: FieldModifier | None = None,
|
|
889
867
|
field_modifiers: dict[FieldIdentifier, FieldModifier] | None = None,
|
|
890
868
|
column_positions: dict[str, tuple[int, int]] | None = None,
|
|
891
869
|
group_by: FieldIdentifier | None = None,
|
|
892
|
-
image_data:
|
|
893
|
-
index_field: str | None = None) -> list[FieldMap]:
|
|
870
|
+
image_data: list[bytes] | None = None) -> list[FieldMap]:
|
|
894
871
|
"""
|
|
895
872
|
Create a dialog where the user may input data into the specified fields. The dialog is constructed from
|
|
896
873
|
a given list of records of a singular type.
|
|
@@ -926,12 +903,6 @@ class CallbackUtil:
|
|
|
926
903
|
:param image_data: The bytes to the images that should be displayed in the rows of the table. Each element in
|
|
927
904
|
the image data list corresponds to the element at the same index in the records list. Only used if the
|
|
928
905
|
adaptive dialog becomes a table.
|
|
929
|
-
:param index_field: If provided, the returned field maps will contain a field with this name that is equal to
|
|
930
|
-
the record ID of the record at the same index in the records list. This can be used to map the results
|
|
931
|
-
back to the original records. This is used instead of using a RecordId field, as the RecordId field has
|
|
932
|
-
special behavior in the system that can cause issues if the given records are uncommitted record models
|
|
933
|
-
with negative record IDs, meaning we don't want to have a RecordId field in the field maps provided to the
|
|
934
|
-
system. Only used if the adaptive dialog becomes a table.
|
|
935
906
|
:return: A list of dictionaries mapping the data field names of the given field definitions to the response
|
|
936
907
|
value from the user for that field for each row. Even if a form was displayed, the field values will still
|
|
937
908
|
be returned in a list.
|
|
@@ -940,23 +911,23 @@ class CallbackUtil:
|
|
|
940
911
|
if not count:
|
|
941
912
|
raise SapioException("No records provided.")
|
|
942
913
|
if count == 1:
|
|
943
|
-
return [self.record_form_dialog(title, msg, fields,
|
|
914
|
+
return [self.record_form_dialog(title, msg, fields, records[0], column_positions,
|
|
944
915
|
default_modifier=default_modifier, field_modifiers=field_modifiers)]
|
|
945
916
|
return self.record_table_dialog(title, msg, fields, records,
|
|
946
917
|
default_modifier=default_modifier, field_modifiers=field_modifiers,
|
|
947
|
-
group_by=group_by, image_data=image_data
|
|
918
|
+
group_by=group_by, image_data=image_data)
|
|
948
919
|
|
|
949
920
|
def set_record_adaptive_dialog(self,
|
|
950
921
|
title: str,
|
|
951
922
|
msg: str,
|
|
952
|
-
fields:
|
|
953
|
-
records:
|
|
923
|
+
fields: list[FieldIdentifier | FieldFilterCriteria] | DataTypeLayoutIdentifier,
|
|
924
|
+
records: list[SapioRecord],
|
|
954
925
|
*,
|
|
955
926
|
default_modifier: FieldModifier | None = None,
|
|
956
927
|
field_modifiers: dict[FieldIdentifier, FieldModifier] | None = None,
|
|
957
928
|
column_positions: dict[str, tuple[int, int]] | None = None,
|
|
958
929
|
group_by: FieldIdentifier | None = None,
|
|
959
|
-
image_data:
|
|
930
|
+
image_data: list[bytes] | None = None) -> None:
|
|
960
931
|
"""
|
|
961
932
|
Create a dialog where the user may input data into the fields of the dialog. The dialog is constructed from
|
|
962
933
|
a given list of records of a singular type. After the user submits this dialog, the values that the user
|
|
@@ -998,7 +969,7 @@ class CallbackUtil:
|
|
|
998
969
|
if not count:
|
|
999
970
|
raise SapioException("No records provided.")
|
|
1000
971
|
if count == 1:
|
|
1001
|
-
self.set_record_form_dialog(title, msg, fields,
|
|
972
|
+
self.set_record_form_dialog(title, msg, fields, records[0], column_positions,
|
|
1002
973
|
default_modifier=default_modifier, field_modifiers=field_modifiers)
|
|
1003
974
|
else:
|
|
1004
975
|
self.set_record_table_dialog(title, msg, fields, records,
|
|
@@ -1008,7 +979,7 @@ class CallbackUtil:
|
|
|
1008
979
|
def create_record_adaptive_dialog(self,
|
|
1009
980
|
title: str,
|
|
1010
981
|
msg: str,
|
|
1011
|
-
fields:
|
|
982
|
+
fields: list[FieldValue] | DataTypeLayoutIdentifier,
|
|
1012
983
|
wrapper_type: type[WrappedType] | str,
|
|
1013
984
|
count: int | tuple[int, int],
|
|
1014
985
|
*,
|
|
@@ -1016,7 +987,7 @@ class CallbackUtil:
|
|
|
1016
987
|
field_modifiers: dict[FieldIdentifier, FieldModifier] | None = None,
|
|
1017
988
|
column_positions: dict[str, tuple[int, int]] | None = None,
|
|
1018
989
|
group_by: FieldIdentifier | None = None,
|
|
1019
|
-
image_data:
|
|
990
|
+
image_data: list[bytes] | None = None,
|
|
1020
991
|
require_input: bool = False,
|
|
1021
992
|
repeat_message: str | None = "Please provide a value to continue.") \
|
|
1022
993
|
-> list[WrappedType]:
|
|
@@ -1079,8 +1050,8 @@ class CallbackUtil:
|
|
|
1079
1050
|
def multi_type_table_dialog(self,
|
|
1080
1051
|
title: str,
|
|
1081
1052
|
msg: str,
|
|
1082
|
-
fields:
|
|
1083
|
-
row_contents:
|
|
1053
|
+
fields: list[tuple[DataTypeIdentifier, FieldIdentifier] | AbstractVeloxFieldDefinition],
|
|
1054
|
+
row_contents: list[list[SapioRecord | FieldMap]],
|
|
1084
1055
|
*,
|
|
1085
1056
|
default_modifier: FieldModifier | None = None,
|
|
1086
1057
|
field_modifiers: dict[FieldIdentifier, FieldModifier] | None = None,
|
|
@@ -1216,7 +1187,7 @@ class CallbackUtil:
|
|
|
1216
1187
|
if rec is None:
|
|
1217
1188
|
continue
|
|
1218
1189
|
# Map records to their data type name. Map field maps to Default.
|
|
1219
|
-
dt: str = "Default" if isinstance(rec, dict) else AliasUtil.
|
|
1190
|
+
dt: str = "Default" if isinstance(rec, dict) else AliasUtil.to_data_type_names(rec)
|
|
1220
1191
|
# Warn if the same data type name appears more than once.
|
|
1221
1192
|
if dt in row_records:
|
|
1222
1193
|
raise SapioException(f"The data type \"{dt}\" appears more than once in the given row contents.")
|
|
@@ -1255,7 +1226,7 @@ class CallbackUtil:
|
|
|
1255
1226
|
layout: DataTypeLayoutIdentifier = None,
|
|
1256
1227
|
minimized: bool = False,
|
|
1257
1228
|
access_level: FormAccessLevel | None = None,
|
|
1258
|
-
plugin_path_list:
|
|
1229
|
+
plugin_path_list: list[str] | None = None) -> None:
|
|
1259
1230
|
"""
|
|
1260
1231
|
Create an IDV dialog for the given record. This IDV may use an existing layout already defined in the system,
|
|
1261
1232
|
and can be created to allow the user to edit the field in the IDV, or to be read-only for the user to review.
|
|
@@ -1292,15 +1263,15 @@ class CallbackUtil:
|
|
|
1292
1263
|
# CR-47326: Allow the selection dialog functions to preselect rows/records in the table.
|
|
1293
1264
|
def selection_dialog(self,
|
|
1294
1265
|
msg: str,
|
|
1295
|
-
fields:
|
|
1296
|
-
values:
|
|
1266
|
+
fields: list[AbstractVeloxFieldDefinition],
|
|
1267
|
+
values: list[FieldMap],
|
|
1297
1268
|
multi_select: bool = True,
|
|
1298
|
-
preselected_rows:
|
|
1269
|
+
preselected_rows: list[FieldMap | RecordIdentifier] | None = None,
|
|
1299
1270
|
*,
|
|
1300
1271
|
data_type: DataTypeIdentifier = "Default",
|
|
1301
1272
|
display_name: str | None = None,
|
|
1302
1273
|
plural_display_name: str | None = None,
|
|
1303
|
-
image_data:
|
|
1274
|
+
image_data: list[bytes] | None = None,
|
|
1304
1275
|
require_selection: bool = False,
|
|
1305
1276
|
repeat_message: str | None = "Please provide a selection to continue.") -> list[FieldMap]:
|
|
1306
1277
|
"""
|
|
@@ -1358,7 +1329,6 @@ class CallbackUtil:
|
|
|
1358
1329
|
|
|
1359
1330
|
# Add a RecordId definition to the fields if one is not already present. This is necessary for the
|
|
1360
1331
|
# pre-selected records parameter to function.
|
|
1361
|
-
fields = list(fields)
|
|
1362
1332
|
if "RecordId" not in [x.data_field_name for x in fields]:
|
|
1363
1333
|
builder = FieldBuilder(data_type)
|
|
1364
1334
|
fields.append(builder.long_field("RecordId", abstract_info=AnyFieldInfo(visible=False)))
|
|
@@ -1368,7 +1338,7 @@ class CallbackUtil:
|
|
|
1368
1338
|
temp_dt.record_image_assignable = bool(image_data)
|
|
1369
1339
|
|
|
1370
1340
|
# Send the request to the user.
|
|
1371
|
-
request = TempTableSelectionRequest(temp_dt, msg,
|
|
1341
|
+
request = TempTableSelectionRequest(temp_dt, msg, values, image_data, preselected_rows, multi_select)
|
|
1372
1342
|
# If require_selection is true, repeat the request if the user didn't make a selection.
|
|
1373
1343
|
while True:
|
|
1374
1344
|
response: list[FieldMap] = self.__handle_dialog_request(request,
|
|
@@ -1381,12 +1351,12 @@ class CallbackUtil:
|
|
|
1381
1351
|
|
|
1382
1352
|
def record_selection_dialog(self,
|
|
1383
1353
|
msg: str,
|
|
1384
|
-
fields:
|
|
1385
|
-
records:
|
|
1354
|
+
fields: list[FieldIdentifier | FieldFilterCriteria] | DataTypeLayoutIdentifier,
|
|
1355
|
+
records: list[SapioRecord],
|
|
1386
1356
|
multi_select: bool = True,
|
|
1387
|
-
preselected_records:
|
|
1357
|
+
preselected_records: list[RecordIdentifier] | None = None,
|
|
1388
1358
|
*,
|
|
1389
|
-
image_data:
|
|
1359
|
+
image_data: list[bytes] | None = None,
|
|
1390
1360
|
require_selection: bool = False,
|
|
1391
1361
|
repeat_message: str | None = "Please provide a selection to continue.") \
|
|
1392
1362
|
-> list[SapioRecord]:
|
|
@@ -1468,12 +1438,12 @@ class CallbackUtil:
|
|
|
1468
1438
|
msg: str,
|
|
1469
1439
|
multi_select: bool = True,
|
|
1470
1440
|
only_key_fields: bool = False,
|
|
1471
|
-
search_types:
|
|
1441
|
+
search_types: list[SearchType] | None = None,
|
|
1472
1442
|
scan_criteria: ScanToSelectCriteria | None = None,
|
|
1473
1443
|
custom_search: CustomReport | CustomReportCriteria | str | None = None,
|
|
1474
|
-
preselected_records:
|
|
1475
|
-
record_blacklist:
|
|
1476
|
-
record_whitelist:
|
|
1444
|
+
preselected_records: list[RecordIdentifier] | None = None,
|
|
1445
|
+
record_blacklist: list[RecordIdentifier] | None = None,
|
|
1446
|
+
record_whitelist: list[RecordIdentifier] | None = None,
|
|
1477
1447
|
allow_creation: bool = False,
|
|
1478
1448
|
default_creation_number: int = 1,
|
|
1479
1449
|
*,
|
|
@@ -1563,7 +1533,7 @@ class CallbackUtil:
|
|
|
1563
1533
|
title: str,
|
|
1564
1534
|
msg: str,
|
|
1565
1535
|
show_comment: bool = True,
|
|
1566
|
-
additional_fields:
|
|
1536
|
+
additional_fields: list[AbstractVeloxFieldDefinition] | None = None,
|
|
1567
1537
|
*,
|
|
1568
1538
|
require_authentication: bool = False) -> ESigningResponsePojo:
|
|
1569
1539
|
"""
|
|
@@ -1602,7 +1572,7 @@ class CallbackUtil:
|
|
|
1602
1572
|
popup_type=PopupType.Error)
|
|
1603
1573
|
return response
|
|
1604
1574
|
|
|
1605
|
-
def request_file(self, title: str, exts:
|
|
1575
|
+
def request_file(self, title: str, exts: list[str] | None = None,
|
|
1606
1576
|
show_image_editor: bool = False, show_camera_button: bool = False) -> tuple[str, bytes]:
|
|
1607
1577
|
"""
|
|
1608
1578
|
Request a single file from the user.
|
|
@@ -1635,7 +1605,7 @@ class CallbackUtil:
|
|
|
1635
1605
|
self.__verify_file(file_path, sink.data, exts)
|
|
1636
1606
|
return file_path, sink.data
|
|
1637
1607
|
|
|
1638
|
-
def request_files(self, title: str, exts:
|
|
1608
|
+
def request_files(self, title: str, exts: list[str] | None = None,
|
|
1639
1609
|
show_image_editor: bool = False, show_camera_button: bool = False) -> dict[str, bytes]:
|
|
1640
1610
|
"""
|
|
1641
1611
|
Request multiple files from the user.
|
|
@@ -1667,7 +1637,7 @@ class CallbackUtil:
|
|
|
1667
1637
|
return ret_dict
|
|
1668
1638
|
|
|
1669
1639
|
@staticmethod
|
|
1670
|
-
def __verify_file(file_path: str, file_bytes: bytes, allowed_extensions:
|
|
1640
|
+
def __verify_file(file_path: str, file_bytes: bytes, allowed_extensions: list[str]) -> None:
|
|
1671
1641
|
"""
|
|
1672
1642
|
Verify that the provided file was read (i.e. the file path and file bytes aren't None or empty) and that it
|
|
1673
1643
|
has the correct file extension. Raises a user error exception if something about the file is incorrect.
|
|
@@ -1678,7 +1648,7 @@ class CallbackUtil:
|
|
|
1678
1648
|
"""
|
|
1679
1649
|
if file_path is None or len(file_path) == 0 or file_bytes is None or len(file_bytes) == 0:
|
|
1680
1650
|
raise SapioUserErrorException("Empty file provided or file unable to be read.")
|
|
1681
|
-
if allowed_extensions:
|
|
1651
|
+
if len(allowed_extensions) != 0:
|
|
1682
1652
|
matches: bool = False
|
|
1683
1653
|
for ext in allowed_extensions:
|
|
1684
1654
|
if file_path.endswith("." + ext.lstrip(".")):
|
|
@@ -1695,8 +1665,8 @@ class CallbackUtil:
|
|
|
1695
1665
|
:param file_name: The name of the file.
|
|
1696
1666
|
:param file_data: The data of the file, provided as either a string or as a bytes array.
|
|
1697
1667
|
"""
|
|
1698
|
-
|
|
1699
|
-
|
|
1668
|
+
data = io.BytesIO(file_data.encode() if isinstance(file_data, str) else file_data)
|
|
1669
|
+
self.callback.send_file(file_name, False, data)
|
|
1700
1670
|
|
|
1701
1671
|
def write_zip_file(self, zip_name: str, files: dict[str, str | bytes]) -> None:
|
|
1702
1672
|
"""
|
|
@@ -1705,33 +1675,12 @@ class CallbackUtil:
|
|
|
1705
1675
|
:param zip_name: The name of the zip file.
|
|
1706
1676
|
:param files: A dictionary of the files to add to the zip file.
|
|
1707
1677
|
"""
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
@staticmethod
|
|
1712
|
-
def __get_indexed_field_maps(records: Iterable[SapioRecord], index_field: str) -> list[FieldMap]:
|
|
1713
|
-
"""
|
|
1714
|
-
For dialogs that accept multiple records, we may want to be able to match the returned results back to the
|
|
1715
|
-
records that they're for. In this case, we need to add an index to each record so that we can match them back
|
|
1716
|
-
to the original records. We can't use the RecordId field, as new record models have negative record IDs that
|
|
1717
|
-
cause the callback dialogs to bug out if the RecordId field is present and negative.
|
|
1718
|
-
|
|
1719
|
-
:param records: The records to return indexed field maps of.
|
|
1720
|
-
:param index_field: The name of the field to use as the index. Make sure that this field doesn't exist on the
|
|
1721
|
-
records, as then it will overwrite the existing value.
|
|
1722
|
-
:return: A list of field maps for the records, with an index field added to each. The value of the index on
|
|
1723
|
-
each field map is the record's record ID (even if it's a record model with a negative ID).
|
|
1724
|
-
"""
|
|
1725
|
-
ret_val: list[FieldMap] = []
|
|
1726
|
-
for record in records:
|
|
1727
|
-
field_map: FieldMap = AliasUtil.to_field_map(record)
|
|
1728
|
-
field_map[index_field] = AliasUtil.to_record_id(record)
|
|
1729
|
-
ret_val.append(field_map)
|
|
1730
|
-
return ret_val
|
|
1678
|
+
data = io.BytesIO(FileUtil.zip_files(files))
|
|
1679
|
+
self.callback.send_file(zip_name, False, data)
|
|
1731
1680
|
|
|
1732
1681
|
@staticmethod
|
|
1733
1682
|
def __temp_dt_from_field_defs(data_type: DataTypeIdentifier, display_name: str | None,
|
|
1734
|
-
plural_display_name: str | None, fields:
|
|
1683
|
+
plural_display_name: str | None, fields: list[AbstractVeloxFieldDefinition],
|
|
1735
1684
|
column_positions: dict[str, tuple[int, int]] | None) -> TemporaryDataType:
|
|
1736
1685
|
"""
|
|
1737
1686
|
Construct a Temporary Data Type definition from a provided list of field definitions for use in a callback.
|
|
@@ -1765,7 +1714,7 @@ class CallbackUtil:
|
|
|
1765
1714
|
builder.add_field(field_def, column, span)
|
|
1766
1715
|
return builder.get_temporary_data_type()
|
|
1767
1716
|
|
|
1768
|
-
def __temp_dt_from_field_names(self, data_type: str, fields:
|
|
1717
|
+
def __temp_dt_from_field_names(self, data_type: str, fields: list[FieldIdentifier | FieldFilterCriteria],
|
|
1769
1718
|
column_positions: dict[str, tuple[int, int]] | None,
|
|
1770
1719
|
default_modifier: FieldModifier, field_modifiers: dict[str, FieldModifier]) \
|
|
1771
1720
|
-> TemporaryDataType:
|
|
@@ -1782,7 +1731,6 @@ class CallbackUtil:
|
|
|
1782
1731
|
|
|
1783
1732
|
# Determine if any FieldFilterCriteria were provided. If so, remove them from the fields list so that it
|
|
1784
1733
|
# contains only field identifiers.
|
|
1785
|
-
fields = list(fields)
|
|
1786
1734
|
filter_criteria: list[FieldFilterCriteria] = [x for x in fields if isinstance(x, FieldFilterCriteria)]
|
|
1787
1735
|
for criteria in filter_criteria:
|
|
1788
1736
|
fields.remove(criteria)
|
|
@@ -2035,15 +1983,15 @@ class FieldFilterCriteria:
|
|
|
2035
1983
|
key_field: bool | None
|
|
2036
1984
|
identifier: bool | None
|
|
2037
1985
|
system_field: bool | None
|
|
2038
|
-
field_types:
|
|
2039
|
-
not_field_types:
|
|
1986
|
+
field_types: list[FieldType] | None
|
|
1987
|
+
not_field_types: list[FieldType] | None
|
|
2040
1988
|
matches_tag: str | None
|
|
2041
1989
|
contains_tag: str | None
|
|
2042
1990
|
regex_tag: str | re.Pattern[str] | None
|
|
2043
1991
|
|
|
2044
1992
|
def __init__(self, *, required: bool | None = None, editable: bool | None = None, key_field: bool | None = None,
|
|
2045
1993
|
identifier: bool | None = None, system_field: bool | None = None,
|
|
2046
|
-
field_types:
|
|
1994
|
+
field_types: list[FieldType] | None = None, not_field_types: list[FieldType] | None = None,
|
|
2047
1995
|
matches_tag: str | None = None, contains_tag: str | None = None,
|
|
2048
1996
|
regex_tag: str | re.Pattern[str] | None = None):
|
|
2049
1997
|
"""
|
|
@@ -443,8 +443,6 @@ class FieldBuilder:
|
|
|
443
443
|
raise SapioException("Unable to set multiple list modes at once for a selection list.")
|
|
444
444
|
# Static values don't have a list mode. Evaluate this last so that the multiple list modes check doesn't
|
|
445
445
|
# need to be more complex.
|
|
446
|
-
# PR-47531: Even though static values don't use an existing list mode, a list mode must still be set.
|
|
447
|
-
list_mode = ListMode.USER
|
|
448
446
|
|
|
449
447
|
if not list_mode and static_values is None:
|
|
450
448
|
raise SapioException("A list mode must be chosen for selection list fields.")
|
|
@@ -6,7 +6,6 @@ from sapiopylib.rest.CustomReportService import CustomReportManager
|
|
|
6
6
|
from sapiopylib.rest.DataMgmtService import DataMgmtServer
|
|
7
7
|
from sapiopylib.rest.pojo.CustomReport import CustomReportCriteria, CustomReport, RawReportTerm, ReportColumn
|
|
8
8
|
from sapiopylib.rest.pojo.datatype.FieldDefinition import FieldType
|
|
9
|
-
# noinspection PyProtectedMember
|
|
10
9
|
from sapiopylib.rest.utils.autopaging import SapioPyAutoPager, PagerResultCriteriaType, _default_report_page_size, \
|
|
11
10
|
_default_record_page_size
|
|
12
11
|
from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
|
|
@@ -154,7 +153,7 @@ class _RecordReportPagerBase(SapioPyAutoPager[CustomReportCriteria, WrappedType
|
|
|
154
153
|
if id_index == -1:
|
|
155
154
|
raise SapioException(f"This report does not contain a Record ID column for the given record model type "
|
|
156
155
|
f"{self._data_type}.")
|
|
157
|
-
ids:
|
|
156
|
+
ids: list[int] = [row[id_index] for row in report.result_table]
|
|
158
157
|
for row in self._rec_handler.query_models_by_id(self._query_type, ids, page_size=report.page_size):
|
|
159
158
|
queue.put(row)
|
|
160
159
|
if report.has_next_page:
|
|
@@ -279,7 +279,7 @@ class TermBuilder:
|
|
|
279
279
|
|
|
280
280
|
:param a: The first term in the operation.
|
|
281
281
|
:param b: The second term in the operation.
|
|
282
|
-
:param is_negated: Whether the returned term should be negated (i.e. turn this into an
|
|
282
|
+
:param is_negated: Whether the returned term should be negated (i.e. turn this into an xnor operation).
|
|
283
283
|
:return: A composite report term for "A xor B".
|
|
284
284
|
"""
|
|
285
285
|
return TermBuilder.and_terms(TermBuilder.or_terms(a, b),
|