sapiopycommons 2025.3.26a460__tar.gz → 2025.3.31a466__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-2025.3.26a460 → sapiopycommons-2025.3.31a466}/PKG-INFO +1 -1
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/pyproject.toml +1 -1
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/callbacks/callback_util.py +67 -59
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/callbacks/field_builder.py +2 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/customreport/auto_pagers.py +2 -1
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/customreport/term_builder.py +1 -1
- sapiopycommons-2025.3.31a466/src/sapiopycommons/datatype/experiment_cache.py +173 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/eln/experiment_handler.py +191 -116
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/files/file_util.py +4 -4
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/general/accession_service.py +2 -2
- sapiopycommons-2025.3.31a466/src/sapiopycommons/general/data_structure_util.py +115 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/recordmodel/record_handler.py +356 -27
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/rules/eln_rule_handler.py +8 -1
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/rules/on_save_rule_handler.py +8 -1
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/webhook/webhook_handlers.py +3 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/webhook/webservice_handlers.py +2 -2
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/.gitignore +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/LICENSE +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/README.md +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/__init__.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/callbacks/__init__.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/chem/IndigoMolecules.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/chem/Molecules.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/chem/__init__.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/customreport/__init__.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/customreport/column_builder.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/customreport/custom_report_builder.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/datatype/__init__.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/datatype/attachment_util.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/datatype/data_fields.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/datatype/pseudo_data_types.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/eln/__init__.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/eln/experiment_report_util.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/eln/experiment_tags.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/eln/plate_designer.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/files/__init__.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/files/complex_data_loader.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/files/file_bridge.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/files/file_bridge_handler.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/files/file_data_handler.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/files/file_validator.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/files/file_writer.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/flowcyto/flow_cyto.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/flowcyto/flowcyto_data.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/general/__init__.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/general/aliases.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/general/audit_log.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/general/custom_report_util.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/general/directive_util.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/general/exceptions.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/general/popup_util.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/general/sapio_links.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/general/storage_util.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/general/time_util.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/multimodal/multimodal.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/multimodal/multimodal_data.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/processtracking/__init__.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/processtracking/custom_workflow_handler.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/processtracking/endpoints.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/recordmodel/__init__.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/rules/__init__.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/samples/aliquot.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/sftpconnect/__init__.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/sftpconnect/sftp_builder.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/webhook/__init__.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/src/sapiopycommons/webhook/webhook_context.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/tests/AF-A0A009IHW8-F1-model_v4.cif +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/tests/_do_not_add_init_py_here +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/tests/accession_test.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/tests/aliquot_test.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/tests/bio_reg_test.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/tests/chem_test.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/tests/chem_test_curation_queue.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/tests/curation_queue_test.sdf +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/tests/data_type_models.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/tests/flowcyto/101_DEN084Y5_15_E01_008_clean.fcs +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/tests/flowcyto/101_DEN084Y5_15_E03_009_clean.fcs +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/tests/flowcyto/101_DEN084Y5_15_E05_010_clean.fcs +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/tests/flowcyto/8_color_ICS.wsp +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/tests/flowcyto/COVID19_W_001_O.fcs +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/tests/flowcyto_test.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/tests/kappa.chains.fasta +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/tests/mafft_test.py +0 -0
- {sapiopycommons-2025.3.26a460 → sapiopycommons-2025.3.31a466}/tests/test.gb +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: sapiopycommons
|
|
3
|
-
Version: 2025.3.
|
|
3
|
+
Version: 2025.3.31a466
|
|
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>
|
|
@@ -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, Container, Collection
|
|
8
8
|
from weakref import WeakValueDictionary
|
|
9
9
|
|
|
10
10
|
from requests import ReadTimeout
|
|
@@ -43,6 +43,12 @@ from sapiopycommons.recordmodel.record_handler import RecordHandler
|
|
|
43
43
|
DataTypeLayoutIdentifier: TypeAlias = DataTypeLayout | str | None
|
|
44
44
|
|
|
45
45
|
|
|
46
|
+
# CR-47521: Updated various parameter type hints from list or Iterable to more specific type hints.
|
|
47
|
+
# If we need to iterate over the parameter, then it is Iterable.
|
|
48
|
+
# If we need to see if the parameter contains a value, then it is Container.
|
|
49
|
+
# If the length/size of the parameter is needed, then it is Collection.
|
|
50
|
+
# If we need to access the parameter by an index, then it is Sequence. (This excludes sets and dictionaries, so it's
|
|
51
|
+
# probably better to accept a Collection then cast the parameter to a list if you need to get an element from it.)
|
|
46
52
|
class CallbackUtil:
|
|
47
53
|
user: SapioUser
|
|
48
54
|
callback: ClientCallback
|
|
@@ -193,7 +199,7 @@ class CallbackUtil:
|
|
|
193
199
|
def ok_cancel_dialog(self, title: str, msg: str, default_ok: bool = True) -> bool:
|
|
194
200
|
"""
|
|
195
201
|
Create an option dialog where the only options are "OK" and "Cancel". Doesn't allow the user to cancel the
|
|
196
|
-
dialog using the X
|
|
202
|
+
dialog using the X in the top right corner.
|
|
197
203
|
|
|
198
204
|
:param title: The title of the dialog.
|
|
199
205
|
:param msg: The message to display in the dialog. This can be formatted using HTML elements.
|
|
@@ -205,7 +211,7 @@ class CallbackUtil:
|
|
|
205
211
|
def yes_no_dialog(self, title: str, msg: str, default_yes: bool = True) -> bool:
|
|
206
212
|
"""
|
|
207
213
|
Create an option dialog where the only options are "Yes" and "No". Doesn't allow the user to cancel the
|
|
208
|
-
dialog using the X
|
|
214
|
+
dialog using the X in the top right corner.
|
|
209
215
|
|
|
210
216
|
:param title: The title of the dialog.
|
|
211
217
|
:param msg: The message to display in the dialog. This can be formatted using HTML elements.
|
|
@@ -258,7 +264,7 @@ class CallbackUtil:
|
|
|
258
264
|
def form_dialog(self,
|
|
259
265
|
title: str,
|
|
260
266
|
msg: str,
|
|
261
|
-
fields:
|
|
267
|
+
fields: Iterable[AbstractVeloxFieldDefinition],
|
|
262
268
|
values: FieldMap = None,
|
|
263
269
|
column_positions: dict[str, tuple[int, int]] = None,
|
|
264
270
|
*,
|
|
@@ -296,7 +302,7 @@ class CallbackUtil:
|
|
|
296
302
|
def record_form_dialog(self,
|
|
297
303
|
title: str,
|
|
298
304
|
msg: str,
|
|
299
|
-
fields:
|
|
305
|
+
fields: Iterable[FieldIdentifier | FieldFilterCriteria] | DataTypeLayoutIdentifier,
|
|
300
306
|
record: SapioRecord,
|
|
301
307
|
column_positions: dict[str, tuple[int, int]] | None = None,
|
|
302
308
|
editable=None,
|
|
@@ -369,7 +375,7 @@ class CallbackUtil:
|
|
|
369
375
|
def set_record_form_dialog(self,
|
|
370
376
|
title: str,
|
|
371
377
|
msg: str,
|
|
372
|
-
fields:
|
|
378
|
+
fields: Iterable[FieldIdentifier | FieldFilterCriteria] | DataTypeLayoutIdentifier,
|
|
373
379
|
record: SapioRecord,
|
|
374
380
|
column_positions: dict[str, tuple[int, int]] | None = None,
|
|
375
381
|
*,
|
|
@@ -412,7 +418,7 @@ class CallbackUtil:
|
|
|
412
418
|
def create_record_form_dialog(self,
|
|
413
419
|
title: str,
|
|
414
420
|
msg: str,
|
|
415
|
-
fields:
|
|
421
|
+
fields: Iterable[FieldIdentifier | FieldFilterCriteria] | DataTypeLayoutIdentifier,
|
|
416
422
|
wrapper_type: type[WrappedType] | str,
|
|
417
423
|
column_positions: dict[str, tuple[int, int]] | None = None,
|
|
418
424
|
*,
|
|
@@ -618,14 +624,14 @@ class CallbackUtil:
|
|
|
618
624
|
def table_dialog(self,
|
|
619
625
|
title: str,
|
|
620
626
|
msg: str,
|
|
621
|
-
fields:
|
|
622
|
-
values:
|
|
627
|
+
fields: Iterable[AbstractVeloxFieldDefinition],
|
|
628
|
+
values: Iterable[FieldMap],
|
|
623
629
|
*,
|
|
624
630
|
data_type: DataTypeIdentifier = "Default",
|
|
625
631
|
display_name: str | None = None,
|
|
626
632
|
plural_display_name: str | None = None,
|
|
627
633
|
group_by: FieldIdentifier | None = None,
|
|
628
|
-
image_data:
|
|
634
|
+
image_data: Iterable[bytes] | None = None) -> list[FieldMap]:
|
|
629
635
|
"""
|
|
630
636
|
Create a table dialog where the user may input data into the fields of the table. Requires that the caller
|
|
631
637
|
provide the definitions of every field in the table.
|
|
@@ -660,7 +666,7 @@ class CallbackUtil:
|
|
|
660
666
|
temp_dt.record_image_assignable = bool(image_data)
|
|
661
667
|
|
|
662
668
|
# Send the request to the user.
|
|
663
|
-
request = TableEntryDialogRequest(title, msg, temp_dt, values,
|
|
669
|
+
request = TableEntryDialogRequest(title, msg, temp_dt, list(values),
|
|
664
670
|
record_image_data_list=image_data, group_by_field=group_by,
|
|
665
671
|
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
666
672
|
response: list[FieldMap] = self.__handle_dialog_request(request, self.callback.show_table_entry_dialog)
|
|
@@ -669,14 +675,14 @@ class CallbackUtil:
|
|
|
669
675
|
def record_table_dialog(self,
|
|
670
676
|
title: str,
|
|
671
677
|
msg: str,
|
|
672
|
-
fields:
|
|
673
|
-
records:
|
|
678
|
+
fields: Iterable[FieldIdentifier | FieldFilterCriteria] | DataTypeLayoutIdentifier,
|
|
679
|
+
records: Iterable[SapioRecord],
|
|
674
680
|
editable=None,
|
|
675
681
|
*,
|
|
676
682
|
default_modifier: FieldModifier | None = None,
|
|
677
683
|
field_modifiers: dict[FieldIdentifier, FieldModifier] | None = None,
|
|
678
684
|
group_by: FieldIdentifier | None = None,
|
|
679
|
-
image_data:
|
|
685
|
+
image_data: Iterable[bytes] | None = None) -> list[FieldMap]:
|
|
680
686
|
"""
|
|
681
687
|
Create a table dialog where the user may input data into the fields of the table. The table is constructed from
|
|
682
688
|
a given list of records of a singular type.
|
|
@@ -750,13 +756,13 @@ class CallbackUtil:
|
|
|
750
756
|
def set_record_table_dialog(self,
|
|
751
757
|
title: str,
|
|
752
758
|
msg: str,
|
|
753
|
-
fields:
|
|
754
|
-
records:
|
|
759
|
+
fields: Iterable[FieldValue] | DataTypeLayoutIdentifier,
|
|
760
|
+
records: Iterable[SapioRecord],
|
|
755
761
|
*,
|
|
756
762
|
default_modifier: FieldModifier | None = None,
|
|
757
763
|
field_modifiers: dict[FieldIdentifier, FieldModifier] | None = None,
|
|
758
764
|
group_by: FieldIdentifier | None = None,
|
|
759
|
-
image_data:
|
|
765
|
+
image_data: Iterable[bytes] | None = None):
|
|
760
766
|
"""
|
|
761
767
|
Create a table dialog where the user may input data into the fields of the table. The table is constructed from
|
|
762
768
|
a given list of records of a singular type. After the user submits this dialog, the values that the user
|
|
@@ -798,14 +804,14 @@ class CallbackUtil:
|
|
|
798
804
|
def create_record_table_dialog(self,
|
|
799
805
|
title: str,
|
|
800
806
|
msg: str,
|
|
801
|
-
fields:
|
|
807
|
+
fields: Iterable[FieldValue] | DataTypeLayoutIdentifier,
|
|
802
808
|
wrapper_type: type[WrappedType] | str,
|
|
803
809
|
count: int | tuple[int, int],
|
|
804
810
|
*,
|
|
805
811
|
default_modifier: FieldModifier | None = None,
|
|
806
812
|
field_modifiers: dict[FieldIdentifier, FieldModifier] | None = None,
|
|
807
813
|
group_by: FieldIdentifier | None = None,
|
|
808
|
-
image_data:
|
|
814
|
+
image_data: Iterable[bytes] | None = None,
|
|
809
815
|
require_input: bool = False,
|
|
810
816
|
repeat_message: str | None = "Please provide a value to continue.") \
|
|
811
817
|
-> list[WrappedType] | list[PyRecordModel]:
|
|
@@ -860,14 +866,14 @@ class CallbackUtil:
|
|
|
860
866
|
def record_adaptive_dialog(self,
|
|
861
867
|
title: str,
|
|
862
868
|
msg: str,
|
|
863
|
-
fields:
|
|
864
|
-
records:
|
|
869
|
+
fields: Iterable[FieldIdentifier | FieldFilterCriteria] | DataTypeLayoutIdentifier,
|
|
870
|
+
records: Collection[SapioRecord],
|
|
865
871
|
*,
|
|
866
872
|
default_modifier: FieldModifier | None = None,
|
|
867
873
|
field_modifiers: dict[FieldIdentifier, FieldModifier] | None = None,
|
|
868
874
|
column_positions: dict[str, tuple[int, int]] | None = None,
|
|
869
875
|
group_by: FieldIdentifier | None = None,
|
|
870
|
-
image_data:
|
|
876
|
+
image_data: Iterable[bytes] | None = None) -> list[FieldMap]:
|
|
871
877
|
"""
|
|
872
878
|
Create a dialog where the user may input data into the specified fields. The dialog is constructed from
|
|
873
879
|
a given list of records of a singular type.
|
|
@@ -911,7 +917,7 @@ class CallbackUtil:
|
|
|
911
917
|
if not count:
|
|
912
918
|
raise SapioException("No records provided.")
|
|
913
919
|
if count == 1:
|
|
914
|
-
return [self.record_form_dialog(title, msg, fields, records[0], column_positions,
|
|
920
|
+
return [self.record_form_dialog(title, msg, fields, list(records)[0], column_positions,
|
|
915
921
|
default_modifier=default_modifier, field_modifiers=field_modifiers)]
|
|
916
922
|
return self.record_table_dialog(title, msg, fields, records,
|
|
917
923
|
default_modifier=default_modifier, field_modifiers=field_modifiers,
|
|
@@ -920,14 +926,14 @@ class CallbackUtil:
|
|
|
920
926
|
def set_record_adaptive_dialog(self,
|
|
921
927
|
title: str,
|
|
922
928
|
msg: str,
|
|
923
|
-
fields:
|
|
924
|
-
records:
|
|
929
|
+
fields: Iterable[FieldIdentifier | FieldFilterCriteria] | DataTypeLayoutIdentifier,
|
|
930
|
+
records: Collection[SapioRecord],
|
|
925
931
|
*,
|
|
926
932
|
default_modifier: FieldModifier | None = None,
|
|
927
933
|
field_modifiers: dict[FieldIdentifier, FieldModifier] | None = None,
|
|
928
934
|
column_positions: dict[str, tuple[int, int]] | None = None,
|
|
929
935
|
group_by: FieldIdentifier | None = None,
|
|
930
|
-
image_data:
|
|
936
|
+
image_data: Iterable[bytes] | None = None) -> None:
|
|
931
937
|
"""
|
|
932
938
|
Create a dialog where the user may input data into the fields of the dialog. The dialog is constructed from
|
|
933
939
|
a given list of records of a singular type. After the user submits this dialog, the values that the user
|
|
@@ -969,7 +975,7 @@ class CallbackUtil:
|
|
|
969
975
|
if not count:
|
|
970
976
|
raise SapioException("No records provided.")
|
|
971
977
|
if count == 1:
|
|
972
|
-
self.set_record_form_dialog(title, msg, fields, records[0], column_positions,
|
|
978
|
+
self.set_record_form_dialog(title, msg, fields, list(records)[0], column_positions,
|
|
973
979
|
default_modifier=default_modifier, field_modifiers=field_modifiers)
|
|
974
980
|
else:
|
|
975
981
|
self.set_record_table_dialog(title, msg, fields, records,
|
|
@@ -979,7 +985,7 @@ class CallbackUtil:
|
|
|
979
985
|
def create_record_adaptive_dialog(self,
|
|
980
986
|
title: str,
|
|
981
987
|
msg: str,
|
|
982
|
-
fields:
|
|
988
|
+
fields: Iterable[FieldValue] | DataTypeLayoutIdentifier,
|
|
983
989
|
wrapper_type: type[WrappedType] | str,
|
|
984
990
|
count: int | tuple[int, int],
|
|
985
991
|
*,
|
|
@@ -987,7 +993,7 @@ class CallbackUtil:
|
|
|
987
993
|
field_modifiers: dict[FieldIdentifier, FieldModifier] | None = None,
|
|
988
994
|
column_positions: dict[str, tuple[int, int]] | None = None,
|
|
989
995
|
group_by: FieldIdentifier | None = None,
|
|
990
|
-
image_data:
|
|
996
|
+
image_data: Iterable[bytes] | None = None,
|
|
991
997
|
require_input: bool = False,
|
|
992
998
|
repeat_message: str | None = "Please provide a value to continue.") \
|
|
993
999
|
-> list[WrappedType]:
|
|
@@ -1050,8 +1056,8 @@ class CallbackUtil:
|
|
|
1050
1056
|
def multi_type_table_dialog(self,
|
|
1051
1057
|
title: str,
|
|
1052
1058
|
msg: str,
|
|
1053
|
-
fields:
|
|
1054
|
-
row_contents:
|
|
1059
|
+
fields: Iterable[tuple[DataTypeIdentifier, FieldIdentifier] | AbstractVeloxFieldDefinition],
|
|
1060
|
+
row_contents: Iterable[Iterable[SapioRecord | FieldMap]],
|
|
1055
1061
|
*,
|
|
1056
1062
|
default_modifier: FieldModifier | None = None,
|
|
1057
1063
|
field_modifiers: dict[FieldIdentifier, FieldModifier] | None = None,
|
|
@@ -1226,7 +1232,7 @@ class CallbackUtil:
|
|
|
1226
1232
|
layout: DataTypeLayoutIdentifier = None,
|
|
1227
1233
|
minimized: bool = False,
|
|
1228
1234
|
access_level: FormAccessLevel | None = None,
|
|
1229
|
-
plugin_path_list:
|
|
1235
|
+
plugin_path_list: Iterable[str] | None = None) -> None:
|
|
1230
1236
|
"""
|
|
1231
1237
|
Create an IDV dialog for the given record. This IDV may use an existing layout already defined in the system,
|
|
1232
1238
|
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.
|
|
@@ -1263,15 +1269,15 @@ class CallbackUtil:
|
|
|
1263
1269
|
# CR-47326: Allow the selection dialog functions to preselect rows/records in the table.
|
|
1264
1270
|
def selection_dialog(self,
|
|
1265
1271
|
msg: str,
|
|
1266
|
-
fields:
|
|
1267
|
-
values:
|
|
1272
|
+
fields: Iterable[AbstractVeloxFieldDefinition],
|
|
1273
|
+
values: Iterable[FieldMap],
|
|
1268
1274
|
multi_select: bool = True,
|
|
1269
|
-
preselected_rows:
|
|
1275
|
+
preselected_rows: Iterable[FieldMap | RecordIdentifier] | None = None,
|
|
1270
1276
|
*,
|
|
1271
1277
|
data_type: DataTypeIdentifier = "Default",
|
|
1272
1278
|
display_name: str | None = None,
|
|
1273
1279
|
plural_display_name: str | None = None,
|
|
1274
|
-
image_data:
|
|
1280
|
+
image_data: Iterable[bytes] | None = None,
|
|
1275
1281
|
require_selection: bool = False,
|
|
1276
1282
|
repeat_message: str | None = "Please provide a selection to continue.") -> list[FieldMap]:
|
|
1277
1283
|
"""
|
|
@@ -1329,6 +1335,7 @@ class CallbackUtil:
|
|
|
1329
1335
|
|
|
1330
1336
|
# Add a RecordId definition to the fields if one is not already present. This is necessary for the
|
|
1331
1337
|
# pre-selected records parameter to function.
|
|
1338
|
+
fields = list(fields)
|
|
1332
1339
|
if "RecordId" not in [x.data_field_name for x in fields]:
|
|
1333
1340
|
builder = FieldBuilder(data_type)
|
|
1334
1341
|
fields.append(builder.long_field("RecordId", abstract_info=AnyFieldInfo(visible=False)))
|
|
@@ -1338,7 +1345,7 @@ class CallbackUtil:
|
|
|
1338
1345
|
temp_dt.record_image_assignable = bool(image_data)
|
|
1339
1346
|
|
|
1340
1347
|
# Send the request to the user.
|
|
1341
|
-
request = TempTableSelectionRequest(temp_dt, msg, values, image_data, preselected_rows, multi_select)
|
|
1348
|
+
request = TempTableSelectionRequest(temp_dt, msg, list(values), image_data, preselected_rows, multi_select)
|
|
1342
1349
|
# If require_selection is true, repeat the request if the user didn't make a selection.
|
|
1343
1350
|
while True:
|
|
1344
1351
|
response: list[FieldMap] = self.__handle_dialog_request(request,
|
|
@@ -1351,12 +1358,12 @@ class CallbackUtil:
|
|
|
1351
1358
|
|
|
1352
1359
|
def record_selection_dialog(self,
|
|
1353
1360
|
msg: str,
|
|
1354
|
-
fields:
|
|
1355
|
-
records:
|
|
1361
|
+
fields: Iterable[FieldIdentifier | FieldFilterCriteria] | DataTypeLayoutIdentifier,
|
|
1362
|
+
records: Iterable[SapioRecord],
|
|
1356
1363
|
multi_select: bool = True,
|
|
1357
|
-
preselected_records:
|
|
1364
|
+
preselected_records: Iterable[RecordIdentifier] | None = None,
|
|
1358
1365
|
*,
|
|
1359
|
-
image_data:
|
|
1366
|
+
image_data: Iterable[bytes] | None = None,
|
|
1360
1367
|
require_selection: bool = False,
|
|
1361
1368
|
repeat_message: str | None = "Please provide a selection to continue.") \
|
|
1362
1369
|
-> list[SapioRecord]:
|
|
@@ -1438,12 +1445,12 @@ class CallbackUtil:
|
|
|
1438
1445
|
msg: str,
|
|
1439
1446
|
multi_select: bool = True,
|
|
1440
1447
|
only_key_fields: bool = False,
|
|
1441
|
-
search_types:
|
|
1448
|
+
search_types: Iterable[SearchType] | None = None,
|
|
1442
1449
|
scan_criteria: ScanToSelectCriteria | None = None,
|
|
1443
1450
|
custom_search: CustomReport | CustomReportCriteria | str | None = None,
|
|
1444
|
-
preselected_records:
|
|
1445
|
-
record_blacklist:
|
|
1446
|
-
record_whitelist:
|
|
1451
|
+
preselected_records: Iterable[RecordIdentifier] | None = None,
|
|
1452
|
+
record_blacklist: Iterable[RecordIdentifier] | None = None,
|
|
1453
|
+
record_whitelist: Iterable[RecordIdentifier] | None = None,
|
|
1447
1454
|
allow_creation: bool = False,
|
|
1448
1455
|
default_creation_number: int = 1,
|
|
1449
1456
|
*,
|
|
@@ -1533,7 +1540,7 @@ class CallbackUtil:
|
|
|
1533
1540
|
title: str,
|
|
1534
1541
|
msg: str,
|
|
1535
1542
|
show_comment: bool = True,
|
|
1536
|
-
additional_fields:
|
|
1543
|
+
additional_fields: Iterable[AbstractVeloxFieldDefinition] | None = None,
|
|
1537
1544
|
*,
|
|
1538
1545
|
require_authentication: bool = False) -> ESigningResponsePojo:
|
|
1539
1546
|
"""
|
|
@@ -1572,7 +1579,7 @@ class CallbackUtil:
|
|
|
1572
1579
|
popup_type=PopupType.Error)
|
|
1573
1580
|
return response
|
|
1574
1581
|
|
|
1575
|
-
def request_file(self, title: str, exts:
|
|
1582
|
+
def request_file(self, title: str, exts: Iterable[str] | None = None,
|
|
1576
1583
|
show_image_editor: bool = False, show_camera_button: bool = False) -> tuple[str, bytes]:
|
|
1577
1584
|
"""
|
|
1578
1585
|
Request a single file from the user.
|
|
@@ -1605,7 +1612,7 @@ class CallbackUtil:
|
|
|
1605
1612
|
self.__verify_file(file_path, sink.data, exts)
|
|
1606
1613
|
return file_path, sink.data
|
|
1607
1614
|
|
|
1608
|
-
def request_files(self, title: str, exts:
|
|
1615
|
+
def request_files(self, title: str, exts: Iterable[str] | None = None,
|
|
1609
1616
|
show_image_editor: bool = False, show_camera_button: bool = False) -> dict[str, bytes]:
|
|
1610
1617
|
"""
|
|
1611
1618
|
Request multiple files from the user.
|
|
@@ -1637,7 +1644,7 @@ class CallbackUtil:
|
|
|
1637
1644
|
return ret_dict
|
|
1638
1645
|
|
|
1639
1646
|
@staticmethod
|
|
1640
|
-
def __verify_file(file_path: str, file_bytes: bytes, allowed_extensions:
|
|
1647
|
+
def __verify_file(file_path: str, file_bytes: bytes, allowed_extensions: Iterable[str]) -> None:
|
|
1641
1648
|
"""
|
|
1642
1649
|
Verify that the provided file was read (i.e. the file path and file bytes aren't None or empty) and that it
|
|
1643
1650
|
has the correct file extension. Raises a user error exception if something about the file is incorrect.
|
|
@@ -1648,7 +1655,7 @@ class CallbackUtil:
|
|
|
1648
1655
|
"""
|
|
1649
1656
|
if file_path is None or len(file_path) == 0 or file_bytes is None or len(file_bytes) == 0:
|
|
1650
1657
|
raise SapioUserErrorException("Empty file provided or file unable to be read.")
|
|
1651
|
-
if
|
|
1658
|
+
if allowed_extensions:
|
|
1652
1659
|
matches: bool = False
|
|
1653
1660
|
for ext in allowed_extensions:
|
|
1654
1661
|
if file_path.endswith("." + ext.lstrip(".")):
|
|
@@ -1665,8 +1672,8 @@ class CallbackUtil:
|
|
|
1665
1672
|
:param file_name: The name of the file.
|
|
1666
1673
|
:param file_data: The data of the file, provided as either a string or as a bytes array.
|
|
1667
1674
|
"""
|
|
1668
|
-
|
|
1669
|
-
|
|
1675
|
+
with io.BytesIO(file_data.encode() if isinstance(file_data, str) else file_data) as data:
|
|
1676
|
+
self.callback.send_file(file_name, False, data)
|
|
1670
1677
|
|
|
1671
1678
|
def write_zip_file(self, zip_name: str, files: dict[str, str | bytes]) -> None:
|
|
1672
1679
|
"""
|
|
@@ -1675,12 +1682,12 @@ class CallbackUtil:
|
|
|
1675
1682
|
:param zip_name: The name of the zip file.
|
|
1676
1683
|
:param files: A dictionary of the files to add to the zip file.
|
|
1677
1684
|
"""
|
|
1678
|
-
|
|
1679
|
-
|
|
1685
|
+
with io.BytesIO(FileUtil.zip_files(files)) as data:
|
|
1686
|
+
self.callback.send_file(zip_name, False, data)
|
|
1680
1687
|
|
|
1681
1688
|
@staticmethod
|
|
1682
1689
|
def __temp_dt_from_field_defs(data_type: DataTypeIdentifier, display_name: str | None,
|
|
1683
|
-
plural_display_name: str | None, fields:
|
|
1690
|
+
plural_display_name: str | None, fields: Iterable[AbstractVeloxFieldDefinition],
|
|
1684
1691
|
column_positions: dict[str, tuple[int, int]] | None) -> TemporaryDataType:
|
|
1685
1692
|
"""
|
|
1686
1693
|
Construct a Temporary Data Type definition from a provided list of field definitions for use in a callback.
|
|
@@ -1714,7 +1721,7 @@ class CallbackUtil:
|
|
|
1714
1721
|
builder.add_field(field_def, column, span)
|
|
1715
1722
|
return builder.get_temporary_data_type()
|
|
1716
1723
|
|
|
1717
|
-
def __temp_dt_from_field_names(self, data_type: str, fields:
|
|
1724
|
+
def __temp_dt_from_field_names(self, data_type: str, fields: Iterable[FieldIdentifier | FieldFilterCriteria],
|
|
1718
1725
|
column_positions: dict[str, tuple[int, int]] | None,
|
|
1719
1726
|
default_modifier: FieldModifier, field_modifiers: dict[str, FieldModifier]) \
|
|
1720
1727
|
-> TemporaryDataType:
|
|
@@ -1731,6 +1738,7 @@ class CallbackUtil:
|
|
|
1731
1738
|
|
|
1732
1739
|
# Determine if any FieldFilterCriteria were provided. If so, remove them from the fields list so that it
|
|
1733
1740
|
# contains only field identifiers.
|
|
1741
|
+
fields = list(fields)
|
|
1734
1742
|
filter_criteria: list[FieldFilterCriteria] = [x for x in fields if isinstance(x, FieldFilterCriteria)]
|
|
1735
1743
|
for criteria in filter_criteria:
|
|
1736
1744
|
fields.remove(criteria)
|
|
@@ -1983,15 +1991,15 @@ class FieldFilterCriteria:
|
|
|
1983
1991
|
key_field: bool | None
|
|
1984
1992
|
identifier: bool | None
|
|
1985
1993
|
system_field: bool | None
|
|
1986
|
-
field_types:
|
|
1987
|
-
not_field_types:
|
|
1994
|
+
field_types: Container[FieldType] | None
|
|
1995
|
+
not_field_types: Container[FieldType] | None
|
|
1988
1996
|
matches_tag: str | None
|
|
1989
1997
|
contains_tag: str | None
|
|
1990
1998
|
regex_tag: str | re.Pattern[str] | None
|
|
1991
1999
|
|
|
1992
2000
|
def __init__(self, *, required: bool | None = None, editable: bool | None = None, key_field: bool | None = None,
|
|
1993
2001
|
identifier: bool | None = None, system_field: bool | None = None,
|
|
1994
|
-
field_types:
|
|
2002
|
+
field_types: Container[FieldType] | None = None, not_field_types: Container[FieldType] | None = None,
|
|
1995
2003
|
matches_tag: str | None = None, contains_tag: str | None = None,
|
|
1996
2004
|
regex_tag: str | re.Pattern[str] | None = None):
|
|
1997
2005
|
"""
|
|
@@ -443,6 +443,8 @@ 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
|
|
446
448
|
|
|
447
449
|
if not list_mode and static_values is None:
|
|
448
450
|
raise SapioException("A list mode must be chosen for selection list fields.")
|
|
@@ -6,6 +6,7 @@ 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
|
|
9
10
|
from sapiopylib.rest.utils.autopaging import SapioPyAutoPager, PagerResultCriteriaType, _default_report_page_size, \
|
|
10
11
|
_default_record_page_size
|
|
11
12
|
from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
|
|
@@ -153,7 +154,7 @@ class _RecordReportPagerBase(SapioPyAutoPager[CustomReportCriteria, WrappedType
|
|
|
153
154
|
if id_index == -1:
|
|
154
155
|
raise SapioException(f"This report does not contain a Record ID column for the given record model type "
|
|
155
156
|
f"{self._data_type}.")
|
|
156
|
-
ids:
|
|
157
|
+
ids: set[int] = {row[id_index] for row in report.result_table}
|
|
157
158
|
for row in self._rec_handler.query_models_by_id(self._query_type, ids, page_size=report.page_size):
|
|
158
159
|
queue.put(row)
|
|
159
160
|
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),
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from weakref import WeakValueDictionary
|
|
4
|
+
|
|
5
|
+
from sapiopylib.rest.ELNService import ElnManager
|
|
6
|
+
from sapiopylib.rest.User import SapioUser
|
|
7
|
+
from sapiopylib.rest.pojo.datatype.FieldDefinition import AbstractVeloxFieldDefinition
|
|
8
|
+
from sapiopylib.rest.pojo.eln.ElnExperiment import ElnTemplate, TemplateExperimentQuery
|
|
9
|
+
from sapiopylib.rest.pojo.eln.SapioELNEnums import ElnBaseDataType
|
|
10
|
+
from sapiopylib.rest.pojo.eln.field_set import ElnFieldSetInfo
|
|
11
|
+
from sapiopylib.rest.pojo.eln.protocol_template import ProtocolTemplateInfo, ProtocolTemplateQuery
|
|
12
|
+
|
|
13
|
+
from sapiopycommons.general.aliases import UserIdentifier, AliasUtil
|
|
14
|
+
from sapiopycommons.general.exceptions import SapioException
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# FR-47530: Created a class that caches experiment template and predefined field information.
|
|
18
|
+
class ExperimentCacheManager:
|
|
19
|
+
"""
|
|
20
|
+
A class to manage the caching of experiment-related information.
|
|
21
|
+
"""
|
|
22
|
+
user: SapioUser
|
|
23
|
+
eln_man: ElnManager
|
|
24
|
+
|
|
25
|
+
_templates: list[ElnTemplate]
|
|
26
|
+
"""A list of experiment templates. Only cached when first accessed."""
|
|
27
|
+
_protocols: list[ProtocolTemplateInfo]
|
|
28
|
+
"""A list of protocol templates. Only cached when first accessed."""
|
|
29
|
+
_field_sets: dict[str, ElnFieldSetInfo]
|
|
30
|
+
"""A dictionary of field set name to field set. Only cached when first accessed."""
|
|
31
|
+
_field_set_fields: dict[int, list[AbstractVeloxFieldDefinition]]
|
|
32
|
+
"""A dictionary of field set ID to field definitions. Only cached when first accessed."""
|
|
33
|
+
_predefined_fields: dict[str, dict[str, AbstractVeloxFieldDefinition]]
|
|
34
|
+
"""A dictionary of ELN data type name to predefined field definitions. Only cached when first accessed."""
|
|
35
|
+
|
|
36
|
+
__instances: WeakValueDictionary[SapioUser, ExperimentCacheManager] = WeakValueDictionary()
|
|
37
|
+
__initialized: bool
|
|
38
|
+
|
|
39
|
+
def __new__(cls, context: UserIdentifier):
|
|
40
|
+
"""
|
|
41
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
42
|
+
"""
|
|
43
|
+
user = AliasUtil.to_sapio_user(context)
|
|
44
|
+
obj = cls.__instances.get(user)
|
|
45
|
+
if not obj:
|
|
46
|
+
obj = object.__new__(cls)
|
|
47
|
+
obj.__initialized = False
|
|
48
|
+
cls.__instances[user] = obj
|
|
49
|
+
return obj
|
|
50
|
+
|
|
51
|
+
def __init__(self, context: UserIdentifier):
|
|
52
|
+
"""
|
|
53
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
54
|
+
"""
|
|
55
|
+
if self.__initialized:
|
|
56
|
+
return
|
|
57
|
+
self.__initialized = True
|
|
58
|
+
|
|
59
|
+
self.user = AliasUtil.to_sapio_user(context)
|
|
60
|
+
self.eln_man = ElnManager(self.user)
|
|
61
|
+
|
|
62
|
+
self._field_set_fields = {}
|
|
63
|
+
self._predefined_fields = {}
|
|
64
|
+
|
|
65
|
+
def get_experiment_template(self, name: str, active: bool = True, version: int | None = None,
|
|
66
|
+
first_match: bool = False) -> ElnTemplate:
|
|
67
|
+
"""
|
|
68
|
+
Get the experiment template with the given information.
|
|
69
|
+
|
|
70
|
+
:param name: The name of the template.
|
|
71
|
+
:param active: Whether the template is marked as active.
|
|
72
|
+
:param version: The version of the template to get. If None, the latest version will be returned.
|
|
73
|
+
:param first_match: If true, returns the first match found. If false, raises an exception.
|
|
74
|
+
:return: The experiment template with the given information.
|
|
75
|
+
"""
|
|
76
|
+
if not hasattr(self, "_templates"):
|
|
77
|
+
query = TemplateExperimentQuery()
|
|
78
|
+
query.active_templates_only = False
|
|
79
|
+
query.latest_version_only = False
|
|
80
|
+
self._templates = self.eln_man.get_template_experiment_list(query)
|
|
81
|
+
return self._find_template(self._templates, name, active, version, first_match)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def get_protocol_template(self, name: str, active: bool = True, version: int | None = None,
|
|
85
|
+
first_match: bool = False) -> ProtocolTemplateInfo:
|
|
86
|
+
"""
|
|
87
|
+
Get the protocol template with the given information. Will throw an exception if multiple templates match
|
|
88
|
+
the given information.
|
|
89
|
+
|
|
90
|
+
:param name: The name of the template.
|
|
91
|
+
:param active: Whether the template is marked as active.
|
|
92
|
+
:param version: The version of the template to get. If None, the latest version will be returned.
|
|
93
|
+
:param first_match: If true, returns the first match found. If false, raises an exception.
|
|
94
|
+
:return: The protocol template with the given information.
|
|
95
|
+
"""
|
|
96
|
+
if not hasattr(self, "_protocols"):
|
|
97
|
+
query = ProtocolTemplateQuery()
|
|
98
|
+
query.active_templates_only = False
|
|
99
|
+
query.latest_version_only = False
|
|
100
|
+
self._protocols = self.eln_man.get_protocol_template_info_list(query)
|
|
101
|
+
return self._find_template(self._protocols, name, active, version, first_match)
|
|
102
|
+
|
|
103
|
+
@staticmethod
|
|
104
|
+
def _find_template(templates: list[ElnTemplate] | list[ProtocolTemplateInfo], name: str, active: bool,
|
|
105
|
+
version: int, first_match: bool) -> ElnTemplate | ProtocolTemplateInfo:
|
|
106
|
+
"""
|
|
107
|
+
Find the experiment or protocol template with the given information.
|
|
108
|
+
"""
|
|
109
|
+
matches = []
|
|
110
|
+
for template in templates:
|
|
111
|
+
if template.template_name != name:
|
|
112
|
+
continue
|
|
113
|
+
if template.active != active:
|
|
114
|
+
continue
|
|
115
|
+
if version is not None and template.template_version != version:
|
|
116
|
+
continue
|
|
117
|
+
matches.append(template)
|
|
118
|
+
if not matches:
|
|
119
|
+
raise SapioException(f"No template with the name \"{name}\"" +
|
|
120
|
+
("" if version is None else f" and the version {version}") +
|
|
121
|
+
f" found.")
|
|
122
|
+
if not version:
|
|
123
|
+
return max(matches, key=lambda x: x.template_version)
|
|
124
|
+
if len(matches) > 1 and not first_match:
|
|
125
|
+
raise SapioException(f"Multiple templates with the name \"{name}\" found.")
|
|
126
|
+
return matches[0]
|
|
127
|
+
|
|
128
|
+
def get_predefined_field(self, field_name: str, data_type: ElnBaseDataType) -> AbstractVeloxFieldDefinition:
|
|
129
|
+
"""
|
|
130
|
+
Get the predefined field of the given name for the given ELN data type.
|
|
131
|
+
|
|
132
|
+
:param field_name: The name of the field.
|
|
133
|
+
:param data_type: The ELN data type of the field.
|
|
134
|
+
:return: The predefined field of the given name for the given ELN data type.
|
|
135
|
+
"""
|
|
136
|
+
return self.get_predefined_fields(data_type)[field_name]
|
|
137
|
+
|
|
138
|
+
def get_predefined_fields(self, data_type: ElnBaseDataType) -> dict[str, AbstractVeloxFieldDefinition]:
|
|
139
|
+
"""
|
|
140
|
+
Get the predefined fields for the given ELN data type.
|
|
141
|
+
|
|
142
|
+
:param data_type: The ELN data type to get the predefined fields for.
|
|
143
|
+
:return: A dictionary of field name to field definition for the given ELN data type.
|
|
144
|
+
"""
|
|
145
|
+
if data_type.data_type_name not in self._predefined_fields:
|
|
146
|
+
fields: list[AbstractVeloxFieldDefinition] = self.eln_man.get_predefined_fields(data_type)
|
|
147
|
+
self._predefined_fields[data_type.data_type_name] = {x.data_field_name: x for x in fields}
|
|
148
|
+
return self._predefined_fields[data_type.data_type_name]
|
|
149
|
+
|
|
150
|
+
def get_field_set(self, name: str) -> ElnFieldSetInfo:
|
|
151
|
+
"""
|
|
152
|
+
Get the field set with the given name.
|
|
153
|
+
|
|
154
|
+
:param name: The name of the field set.
|
|
155
|
+
:return: The field set with the given name.
|
|
156
|
+
"""
|
|
157
|
+
if not hasattr(self, "_field_sets"):
|
|
158
|
+
self._field_sets = {x.field_set_name: x for x in self.eln_man.get_field_set_info_list()}
|
|
159
|
+
return self._field_sets[name]
|
|
160
|
+
|
|
161
|
+
def get_field_set_fields(self, field_set: ElnFieldSetInfo | int) -> list[AbstractVeloxFieldDefinition]:
|
|
162
|
+
"""
|
|
163
|
+
Get the fields of the given field set.
|
|
164
|
+
|
|
165
|
+
:param field_set: The field set to get the fields from. Can be either an ElnFieldSetInfo object or an integer
|
|
166
|
+
for the set ID.
|
|
167
|
+
:return: The fields of the given field set.
|
|
168
|
+
"""
|
|
169
|
+
field_set: int = field_set if isinstance(field_set, int) else field_set.field_set_id
|
|
170
|
+
if field_set in self._field_set_fields:
|
|
171
|
+
return self._field_set_fields[field_set]
|
|
172
|
+
self._field_set_fields[field_set] = self.eln_man.get_predefined_fields_from_field_set_id(field_set)
|
|
173
|
+
return self._field_set_fields[field_set]
|