sapiopycommons 2024.8.19a305__py3-none-any.whl → 2024.8.20a306__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sapiopycommons might be problematic. Click here for more details.
- sapiopycommons/callbacks/callback_util.py +11 -12
- sapiopycommons/customreport/term_builder.py +5 -2
- sapiopycommons/datatype/attachment_util.py +6 -14
- sapiopycommons/eln/experiment_handler.py +23 -12
- sapiopycommons/eln/experiment_report_util.py +33 -129
- sapiopycommons/files/complex_data_loader.py +4 -3
- sapiopycommons/files/file_bridge.py +14 -13
- sapiopycommons/files/file_bridge_handler.py +7 -6
- sapiopycommons/files/file_data_handler.py +2 -5
- sapiopycommons/files/file_validator.py +7 -7
- sapiopycommons/general/aliases.py +54 -1
- sapiopycommons/general/audit_log.py +19 -23
- sapiopycommons/general/custom_report_util.py +34 -32
- sapiopycommons/general/sapio_links.py +4 -2
- sapiopycommons/multimodal/multimodal_data.py +0 -1
- sapiopycommons/processtracking/endpoints.py +22 -22
- sapiopycommons/recordmodel/record_handler.py +71 -46
- sapiopycommons/rules/eln_rule_handler.py +5 -3
- sapiopycommons/rules/on_save_rule_handler.py +5 -3
- {sapiopycommons-2024.8.19a305.dist-info → sapiopycommons-2024.8.20a306.dist-info}/METADATA +1 -1
- {sapiopycommons-2024.8.19a305.dist-info → sapiopycommons-2024.8.20a306.dist-info}/RECORD +23 -23
- {sapiopycommons-2024.8.19a305.dist-info → sapiopycommons-2024.8.20a306.dist-info}/WHEEL +0 -0
- {sapiopycommons-2024.8.19a305.dist-info → sapiopycommons-2024.8.20a306.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import io
|
|
4
|
-
from typing import Any
|
|
5
4
|
from weakref import WeakValueDictionary
|
|
6
5
|
|
|
7
6
|
from sapiopylib.rest.ClientCallbackService import ClientCallback
|
|
@@ -18,7 +17,6 @@ from sapiopylib.rest.pojo.webhook.ClientCallbackRequest import OptionDialogReque
|
|
|
18
17
|
DataRecordDialogRequest, InputSelectionRequest, FilePromptRequest, MultiFilePromptRequest, \
|
|
19
18
|
TempTableSelectionRequest, DisplayPopupRequest, PopupType
|
|
20
19
|
from sapiopylib.rest.pojo.webhook.ClientCallbackResult import ESigningResponsePojo
|
|
21
|
-
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
22
20
|
from sapiopylib.rest.pojo.webhook.WebhookEnums import FormAccessLevel, ScanToSelectCriteria, SearchType
|
|
23
21
|
from sapiopylib.rest.utils.DataTypeCacheManager import DataTypeCacheManager
|
|
24
22
|
from sapiopylib.rest.utils.FormBuilder import FormBuilder
|
|
@@ -26,7 +24,8 @@ from sapiopylib.rest.utils.recorddatasinks import InMemoryRecordDataSink
|
|
|
26
24
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
|
|
27
25
|
|
|
28
26
|
from sapiopycommons.files.file_util import FileUtil
|
|
29
|
-
from sapiopycommons.general.aliases import FieldMap, SapioRecord, AliasUtil, RecordIdentifier
|
|
27
|
+
from sapiopycommons.general.aliases import FieldMap, SapioRecord, AliasUtil, RecordIdentifier, FieldValue, \
|
|
28
|
+
UserIdentifier
|
|
30
29
|
from sapiopycommons.general.custom_report_util import CustomReportUtil
|
|
31
30
|
from sapiopycommons.general.exceptions import SapioUserCancelledException, SapioException, SapioUserErrorException
|
|
32
31
|
from sapiopycommons.recordmodel.record_handler import RecordHandler
|
|
@@ -42,11 +41,11 @@ class CallbackUtil:
|
|
|
42
41
|
__instances: WeakValueDictionary[SapioUser, CallbackUtil] = WeakValueDictionary()
|
|
43
42
|
__initialized: bool
|
|
44
43
|
|
|
45
|
-
def __new__(cls, context:
|
|
44
|
+
def __new__(cls, context: UserIdentifier):
|
|
46
45
|
"""
|
|
47
46
|
:param context: The current webhook context or a user object to send requests from.
|
|
48
47
|
"""
|
|
49
|
-
user =
|
|
48
|
+
user = AliasUtil.to_sapio_user(context)
|
|
50
49
|
obj = cls.__instances.get(user)
|
|
51
50
|
if not obj:
|
|
52
51
|
obj = object.__new__(cls)
|
|
@@ -54,7 +53,7 @@ class CallbackUtil:
|
|
|
54
53
|
cls.__instances[user] = obj
|
|
55
54
|
return obj
|
|
56
55
|
|
|
57
|
-
def __init__(self, context:
|
|
56
|
+
def __init__(self, context: UserIdentifier):
|
|
58
57
|
"""
|
|
59
58
|
:param context: The current webhook context or a user object to send requests from.
|
|
60
59
|
"""
|
|
@@ -62,7 +61,7 @@ class CallbackUtil:
|
|
|
62
61
|
return
|
|
63
62
|
self.__initialized = True
|
|
64
63
|
|
|
65
|
-
self.user =
|
|
64
|
+
self.user = AliasUtil.to_sapio_user(context)
|
|
66
65
|
self.callback = DataMgmtServer.get_client_callback(self.user)
|
|
67
66
|
self.dt_cache = DataTypeCacheManager(self.user)
|
|
68
67
|
self.width_pixels = None
|
|
@@ -281,7 +280,7 @@ class CallbackUtil:
|
|
|
281
280
|
modifier = FieldModifier(visible=True, editable=editable)
|
|
282
281
|
|
|
283
282
|
# Build the form using only those fields that are desired.
|
|
284
|
-
values: dict[str,
|
|
283
|
+
values: dict[str, FieldValue] = {}
|
|
285
284
|
builder = FormBuilder(data_type, type_def.display_name, type_def.plural_display_name)
|
|
286
285
|
for field_name in fields:
|
|
287
286
|
field_def = field_defs.get(field_name)
|
|
@@ -303,7 +302,7 @@ class CallbackUtil:
|
|
|
303
302
|
raise SapioUserCancelledException()
|
|
304
303
|
return response
|
|
305
304
|
|
|
306
|
-
def input_dialog(self, title: str, msg: str, field: AbstractVeloxFieldDefinition) ->
|
|
305
|
+
def input_dialog(self, title: str, msg: str, field: AbstractVeloxFieldDefinition) -> FieldValue:
|
|
307
306
|
"""
|
|
308
307
|
Create an input dialog where the user must input data for a singular field.
|
|
309
308
|
|
|
@@ -314,7 +313,7 @@ class CallbackUtil:
|
|
|
314
313
|
"""
|
|
315
314
|
request = InputDialogCriteria(title, msg, field,
|
|
316
315
|
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
317
|
-
response:
|
|
316
|
+
response: FieldValue | None = self.callback.show_input_dialog(request)
|
|
318
317
|
if response is None:
|
|
319
318
|
raise SapioUserCancelledException()
|
|
320
319
|
return response
|
|
@@ -606,10 +605,10 @@ class CallbackUtil:
|
|
|
606
605
|
field_names.append(name)
|
|
607
606
|
|
|
608
607
|
# Get the values for each row.
|
|
609
|
-
values: list[dict[str,
|
|
608
|
+
values: list[dict[str, FieldValue]] = []
|
|
610
609
|
for row in row_contents:
|
|
611
610
|
# The final values for this row:
|
|
612
|
-
row_values: dict[str,
|
|
611
|
+
row_values: dict[str, FieldValue] = {}
|
|
613
612
|
|
|
614
613
|
# Map the records for this row by their data type. If a field map is provided, its data type is Default.
|
|
615
614
|
row_records: dict[str, SapioRecord | FieldMap] = {}
|
|
@@ -18,7 +18,7 @@ AND = CompositeTermOperation.AND_OPERATOR
|
|
|
18
18
|
OR = CompositeTermOperation.OR_OPERATOR
|
|
19
19
|
|
|
20
20
|
# Forms that field term values can take.
|
|
21
|
-
TermValue = str | int | float | bool | Iterable
|
|
21
|
+
TermValue = str | int | float | bool | Iterable | None
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class TermBuilder:
|
|
@@ -281,9 +281,12 @@ class TermBuilder:
|
|
|
281
281
|
"""
|
|
282
282
|
# If the given value is already a string, then nothing needs to be done with it.
|
|
283
283
|
if not isinstance(value, str):
|
|
284
|
+
# If the given value is None, then use an empty string for the search instead.
|
|
285
|
+
if value is None:
|
|
286
|
+
value = ""
|
|
284
287
|
# If the given value is an iterable object, then the return value is the contents of that iterable
|
|
285
288
|
# in a comma separated list surrounded by curly braces.
|
|
286
|
-
|
|
289
|
+
elif isinstance(value, Iterable):
|
|
287
290
|
# When converting a list of values to a string, values in the list which are already strings should be
|
|
288
291
|
# put in quotation marks so that strings that contain commas do not get split up. All other value
|
|
289
292
|
# types can be simply converted to a string, though.
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import io
|
|
2
2
|
|
|
3
3
|
from sapiopylib.rest.DataMgmtService import DataMgmtServer
|
|
4
|
-
from sapiopylib.rest.User import SapioUser
|
|
5
|
-
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
6
4
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
|
|
7
5
|
|
|
8
|
-
from sapiopycommons.general.aliases import AliasUtil, SapioRecord
|
|
6
|
+
from sapiopycommons.general.aliases import AliasUtil, SapioRecord, UserIdentifier
|
|
9
7
|
from sapiopycommons.general.exceptions import SapioException
|
|
10
8
|
from sapiopycommons.recordmodel.record_handler import RecordHandler
|
|
11
9
|
|
|
@@ -13,7 +11,7 @@ from sapiopycommons.recordmodel.record_handler import RecordHandler
|
|
|
13
11
|
# FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
|
|
14
12
|
class AttachmentUtil:
|
|
15
13
|
@staticmethod
|
|
16
|
-
def get_attachment_bytes(context:
|
|
14
|
+
def get_attachment_bytes(context: UserIdentifier, attachment: SapioRecord) -> bytes:
|
|
17
15
|
"""
|
|
18
16
|
Get the data bytes for the given attachment record. Makes a webservice call to retrieve the data.
|
|
19
17
|
|
|
@@ -22,10 +20,7 @@ class AttachmentUtil:
|
|
|
22
20
|
:return: The bytes for the attachment's file data.
|
|
23
21
|
"""
|
|
24
22
|
attachment = AliasUtil.to_data_record(attachment)
|
|
25
|
-
|
|
26
|
-
dr_man = context.data_record_manager
|
|
27
|
-
else:
|
|
28
|
-
dr_man = DataMgmtServer.get_data_record_manager(context)
|
|
23
|
+
dr_man = DataMgmtServer.get_data_record_manager(AliasUtil.to_sapio_user(context))
|
|
29
24
|
with io.BytesIO() as data_sink:
|
|
30
25
|
def consume_data(chunk: bytes):
|
|
31
26
|
data_sink.write(chunk)
|
|
@@ -36,7 +31,7 @@ class AttachmentUtil:
|
|
|
36
31
|
return file_bytes
|
|
37
32
|
|
|
38
33
|
@staticmethod
|
|
39
|
-
def set_attachment_bytes(context:
|
|
34
|
+
def set_attachment_bytes(context: UserIdentifier, attachment: SapioRecord,
|
|
40
35
|
file_name: str, file_bytes: bytes) -> None:
|
|
41
36
|
"""
|
|
42
37
|
Set the attachment data for a given attachment record. Makes a webservice call to set the data.
|
|
@@ -50,15 +45,12 @@ class AttachmentUtil:
|
|
|
50
45
|
raise SapioException("Provided record cannot have its attachment data set, as it does not exist in the "
|
|
51
46
|
"system yet.")
|
|
52
47
|
attachment = AliasUtil.to_data_record(attachment)
|
|
53
|
-
|
|
54
|
-
dr_man = context.data_record_manager
|
|
55
|
-
else:
|
|
56
|
-
dr_man = DataMgmtServer.get_data_record_manager(context)
|
|
48
|
+
dr_man = DataMgmtServer.get_data_record_manager(AliasUtil.to_sapio_user(context))
|
|
57
49
|
with io.BytesIO(file_bytes) as stream:
|
|
58
50
|
dr_man.set_attachment_data(attachment, file_name, stream)
|
|
59
51
|
|
|
60
52
|
@staticmethod
|
|
61
|
-
def create_attachment(context:
|
|
53
|
+
def create_attachment(context: UserIdentifier, file_name: str, file_bytes: bytes,
|
|
62
54
|
wrapper_type: type[WrappedType]) -> WrappedType:
|
|
63
55
|
"""
|
|
64
56
|
Create an attachment data type and initialize its attachment bytes at the same time.
|
|
@@ -23,7 +23,8 @@ from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelInst
|
|
|
23
23
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
|
|
24
24
|
from sapiopylib.rest.utils.recordmodel.properties import Child
|
|
25
25
|
|
|
26
|
-
from sapiopycommons.general.aliases import AliasUtil, SapioRecord, ExperimentIdentifier,
|
|
26
|
+
from sapiopycommons.general.aliases import AliasUtil, SapioRecord, ExperimentIdentifier, UserIdentifier, \
|
|
27
|
+
DataTypeIdentifier, RecordModel
|
|
27
28
|
from sapiopycommons.general.exceptions import SapioException
|
|
28
29
|
|
|
29
30
|
Step = str | ElnEntryStep
|
|
@@ -87,8 +88,7 @@ class ExperimentHandler:
|
|
|
87
88
|
__instances: WeakValueDictionary[str, ExperimentHandler] = WeakValueDictionary()
|
|
88
89
|
__initialized: bool
|
|
89
90
|
|
|
90
|
-
def __new__(cls, context:
|
|
91
|
-
experiment: ExperimentIdentifier | SapioRecord | None = None):
|
|
91
|
+
def __new__(cls, context: UserIdentifier, experiment: ExperimentIdentifier | SapioRecord | None = None):
|
|
92
92
|
"""
|
|
93
93
|
:param context: The current webhook context or a user object to send requests from.
|
|
94
94
|
:param experiment: If an experiment is provided that is separate from the experiment that is in the context,
|
|
@@ -106,8 +106,7 @@ class ExperimentHandler:
|
|
|
106
106
|
cls.__instances[key] = obj
|
|
107
107
|
return obj
|
|
108
108
|
|
|
109
|
-
def __init__(self, context:
|
|
110
|
-
experiment: ExperimentIdentifier | SapioRecord | None = None):
|
|
109
|
+
def __init__(self, context: UserIdentifier, experiment: ExperimentIdentifier | SapioRecord | None = None):
|
|
111
110
|
"""
|
|
112
111
|
Initialization will throw an exception if there is no ELN Experiment in the provided context and no experiment
|
|
113
112
|
is provided.
|
|
@@ -146,8 +145,7 @@ class ExperimentHandler:
|
|
|
146
145
|
self.__steps.update({entry.entry_name: ElnEntryStep(self.__protocol, entry)})
|
|
147
146
|
|
|
148
147
|
@staticmethod
|
|
149
|
-
def __parse_params(context:
|
|
150
|
-
experiment: ExperimentIdentifier | SapioRecord | None = None) \
|
|
148
|
+
def __parse_params(context: UserIdentifier, experiment: ExperimentIdentifier | SapioRecord | None = None) \
|
|
151
149
|
-> tuple[SapioUser, SapioWebhookContext | None, ElnExperiment]:
|
|
152
150
|
if isinstance(context, SapioWebhookContext):
|
|
153
151
|
user = context.user
|
|
@@ -536,7 +534,7 @@ class ExperimentHandler:
|
|
|
536
534
|
ret_list.append(step)
|
|
537
535
|
return ret_list
|
|
538
536
|
|
|
539
|
-
def get_all_steps(self, data_type:
|
|
537
|
+
def get_all_steps(self, data_type: DataTypeIdentifier | None = None) -> list[ElnEntryStep]:
|
|
540
538
|
"""
|
|
541
539
|
Get a list of every entry in the experiment. Optionally filter the returned entries by a data type.
|
|
542
540
|
|
|
@@ -552,8 +550,7 @@ class ExperimentHandler:
|
|
|
552
550
|
all_steps: list[ElnEntryStep] = self.__protocol.get_sorted_step_list()
|
|
553
551
|
if data_type is None:
|
|
554
552
|
return all_steps
|
|
555
|
-
|
|
556
|
-
data_type: str = data_type.get_wrapper_data_type_name()
|
|
553
|
+
data_type: str = AliasUtil.to_data_type_name(data_type)
|
|
557
554
|
return [x for x in all_steps if data_type in x.get_data_type_names()]
|
|
558
555
|
|
|
559
556
|
def get_step_records(self, step: Step) -> list[DataRecord]:
|
|
@@ -605,6 +602,10 @@ class ExperimentHandler:
|
|
|
605
602
|
The records may be provided as either DataRecords, PyRecordModels, or WrappedRecordModels.
|
|
606
603
|
"""
|
|
607
604
|
step = self.__to_eln_step(step)
|
|
605
|
+
dt: str = AliasUtil.to_singular_data_type_name(records)
|
|
606
|
+
if dt != step.get_data_type_names()[0]:
|
|
607
|
+
raise SapioException(f"Cannot add {dt} records to entry {step.get_name()} of type "
|
|
608
|
+
f"{step.get_data_type_names()[0]}.")
|
|
608
609
|
step.add_records(AliasUtil.to_data_records(records))
|
|
609
610
|
|
|
610
611
|
def remove_step_records(self, step: Step, records: Iterable[SapioRecord]) -> None:
|
|
@@ -623,6 +624,10 @@ class ExperimentHandler:
|
|
|
623
624
|
The records may be provided as either DataRecords, PyRecordModels, or WrappedRecordModels.
|
|
624
625
|
"""
|
|
625
626
|
step = self.__to_eln_step(step)
|
|
627
|
+
dt: str = AliasUtil.to_singular_data_type_name(records)
|
|
628
|
+
if dt != step.get_data_type_names()[0]:
|
|
629
|
+
raise SapioException(f"Cannot remove {dt} records from entry {step.get_name()} of type "
|
|
630
|
+
f"{step.get_data_type_names()[0]}.")
|
|
626
631
|
step.remove_records(AliasUtil.to_data_records(records))
|
|
627
632
|
|
|
628
633
|
def set_step_records(self, step: Step, records: Iterable[SapioRecord]) -> None:
|
|
@@ -646,6 +651,10 @@ class ExperimentHandler:
|
|
|
646
651
|
The records may be provided as either DataRecords, PyRecordModels, or WrappedRecordModels.
|
|
647
652
|
"""
|
|
648
653
|
step = self.__to_eln_step(step)
|
|
654
|
+
dt: str = AliasUtil.to_singular_data_type_name(records)
|
|
655
|
+
if dt != step.get_data_type_names()[0]:
|
|
656
|
+
raise SapioException(f"Cannot set {dt} records for entry {step.get_name()} of type "
|
|
657
|
+
f"{step.get_data_type_names()[0]}.")
|
|
649
658
|
step.set_records(AliasUtil.to_data_records(records))
|
|
650
659
|
|
|
651
660
|
# FR-46496 - Provide alias of set_step_records for use with form entries.
|
|
@@ -733,8 +742,10 @@ class ExperimentHandler:
|
|
|
733
742
|
dt: str = step.get_data_type_names()[0]
|
|
734
743
|
if not ElnBaseDataType.is_eln_type(dt):
|
|
735
744
|
raise SapioException("The provided step is not an ELN data type entry.")
|
|
736
|
-
|
|
737
|
-
|
|
745
|
+
record_dt: str = AliasUtil.to_singular_data_type_name(records)
|
|
746
|
+
if record_dt != dt:
|
|
747
|
+
raise SapioException(f"Cannot remove {dt} records from entry {step.get_name()} of type "
|
|
748
|
+
f"{step.get_data_type_names()[0]}.")
|
|
738
749
|
# If any rows were provided as data records, turn them into record models before deleting them, as otherwise
|
|
739
750
|
# this function would need to make a webservice call to do the deletion.
|
|
740
751
|
data_records: list[DataRecord] = []
|
|
@@ -1,19 +1,10 @@
|
|
|
1
1
|
from sapiopylib.rest.User import SapioUser
|
|
2
|
-
from sapiopylib.rest.pojo.CustomReport import (
|
|
3
|
-
CompositeReportTerm,
|
|
4
|
-
CompositeTermOperation,
|
|
5
|
-
CustomReportCriteria,
|
|
6
|
-
ExplicitJoinDefinition,
|
|
7
|
-
FieldCompareReportTerm,
|
|
8
|
-
RawReportTerm,
|
|
9
|
-
RawTermOperation,
|
|
10
|
-
ReportColumn,
|
|
11
|
-
)
|
|
12
2
|
from sapiopylib.rest.pojo.datatype.FieldDefinition import FieldType
|
|
13
|
-
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
14
3
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
|
|
15
4
|
|
|
16
|
-
from sapiopycommons.
|
|
5
|
+
from sapiopycommons.customreport.custom_report_builder import CustomReportBuilder
|
|
6
|
+
from sapiopycommons.customreport.term_builder import TermBuilder
|
|
7
|
+
from sapiopycommons.general.aliases import SapioRecord, UserIdentifier, AliasUtil
|
|
17
8
|
from sapiopycommons.general.custom_report_util import CustomReportUtil
|
|
18
9
|
from sapiopycommons.recordmodel.record_handler import RecordHandler
|
|
19
10
|
|
|
@@ -25,10 +16,8 @@ _RECORD_ID = "RECORDID"
|
|
|
25
16
|
# that given records were used in or getting all records of a datatype used in given experiments.
|
|
26
17
|
class ExperimentReportUtil:
|
|
27
18
|
@staticmethod
|
|
28
|
-
def map_records_to_experiment_ids(
|
|
29
|
-
|
|
30
|
-
records: list[SapioRecord],
|
|
31
|
-
) -> dict[SapioRecord, list[int]]:
|
|
19
|
+
def map_records_to_experiment_ids(context: UserIdentifier, records: list[SapioRecord]) \
|
|
20
|
+
-> dict[SapioRecord, list[int]]:
|
|
32
21
|
"""
|
|
33
22
|
Return a dictionary mapping each record to a list of ids of experiments that they were used in.
|
|
34
23
|
If a record wasn't used in any experiments then it will be mapped to an empty list.
|
|
@@ -40,38 +29,25 @@ class ExperimentReportUtil:
|
|
|
40
29
|
if not records:
|
|
41
30
|
return {}
|
|
42
31
|
|
|
43
|
-
user: SapioUser =
|
|
44
|
-
|
|
45
|
-
data_type_name = records[0].data_type_name
|
|
32
|
+
user: SapioUser = AliasUtil.to_sapio_user(context)
|
|
33
|
+
data_type_name: str = AliasUtil.to_singular_data_type_name(records)
|
|
46
34
|
|
|
47
35
|
record_ids = [record.record_id for record in records]
|
|
48
|
-
|
|
49
|
-
rows = ExperimentReportUtil.__get_record_experiment_relation_rows(
|
|
50
|
-
user, data_type_name, record_ids=record_ids
|
|
51
|
-
)
|
|
36
|
+
rows = ExperimentReportUtil.__get_record_experiment_relation_rows(user, data_type_name, record_ids=record_ids)
|
|
52
37
|
|
|
53
38
|
id_to_record: dict[int, SapioRecord] = RecordHandler.map_by_id(records)
|
|
54
|
-
|
|
55
|
-
record_to_exps: dict[SapioRecord, set[int]] = {
|
|
56
|
-
record: set() for record in records
|
|
57
|
-
}
|
|
58
|
-
|
|
39
|
+
record_to_exps: dict[SapioRecord, set[int]] = {record: set() for record in records}
|
|
59
40
|
for row in rows:
|
|
60
41
|
record_id: int = row[_RECORD_ID]
|
|
61
42
|
exp_id: int = row[_NOTEBOOK_ID]
|
|
62
|
-
|
|
63
43
|
record = id_to_record[record_id]
|
|
64
|
-
|
|
65
44
|
record_to_exps[record].add(exp_id)
|
|
66
45
|
|
|
67
46
|
return {record: list(exps) for record, exps in record_to_exps.items()}
|
|
68
47
|
|
|
69
48
|
@staticmethod
|
|
70
|
-
def map_experiments_to_records_of_type(
|
|
71
|
-
|
|
72
|
-
exp_ids: list[int],
|
|
73
|
-
wrapper_type: type[WrappedType],
|
|
74
|
-
) -> dict[int, list[WrappedType]]:
|
|
49
|
+
def map_experiments_to_records_of_type(context: UserIdentifier, exp_ids: list[int],
|
|
50
|
+
wrapper_type: type[WrappedType]) -> dict[int, list[WrappedType]]:
|
|
75
51
|
"""
|
|
76
52
|
Return a dictionary mapping each experiment id to a list of records of the given type that were used in each experiment.
|
|
77
53
|
If an experiment didn't use any records of the given type then it will be mapped to an empty list.
|
|
@@ -84,41 +60,27 @@ class ExperimentReportUtil:
|
|
|
84
60
|
if not exp_ids:
|
|
85
61
|
return {}
|
|
86
62
|
|
|
87
|
-
user =
|
|
88
|
-
|
|
63
|
+
user = AliasUtil.to_sapio_user(context)
|
|
89
64
|
record_handler = RecordHandler(user)
|
|
90
|
-
|
|
91
65
|
data_type_name: str = wrapper_type.get_wrapper_data_type_name()
|
|
92
66
|
|
|
93
|
-
rows = ExperimentReportUtil.__get_record_experiment_relation_rows(
|
|
94
|
-
user, data_type_name, exp_ids=exp_ids
|
|
95
|
-
)
|
|
96
|
-
|
|
67
|
+
rows = ExperimentReportUtil.__get_record_experiment_relation_rows(user, data_type_name, exp_ids=exp_ids)
|
|
97
68
|
record_ids: set[int] = {row[_RECORD_ID] for row in rows}
|
|
98
|
-
|
|
99
69
|
records = record_handler.query_models_by_id(wrapper_type, record_ids)
|
|
100
70
|
|
|
101
71
|
id_to_record: dict[int, WrappedType] = RecordHandler.map_by_id(records)
|
|
102
|
-
|
|
103
72
|
exp_to_records: dict[int, set[SapioRecord]] = {exp: set() for exp in exp_ids}
|
|
104
|
-
|
|
105
73
|
for row in rows:
|
|
106
74
|
record_id: int = row[_RECORD_ID]
|
|
107
75
|
exp_id: int = row[_NOTEBOOK_ID]
|
|
108
|
-
|
|
109
76
|
record = id_to_record[record_id]
|
|
110
|
-
|
|
111
77
|
exp_to_records[exp_id].add(record)
|
|
112
78
|
|
|
113
79
|
return {exp: list(records) for exp, records in exp_to_records.items()}
|
|
114
80
|
|
|
115
81
|
@staticmethod
|
|
116
|
-
def __get_record_experiment_relation_rows(
|
|
117
|
-
|
|
118
|
-
data_type_name: str,
|
|
119
|
-
record_ids: list[int] | None = None,
|
|
120
|
-
exp_ids: list[int] | None = None,
|
|
121
|
-
) -> list[dict[str, int]]:
|
|
82
|
+
def __get_record_experiment_relation_rows(user: SapioUser, data_type_name: str, record_ids: list[int] | None = None,
|
|
83
|
+
exp_ids: list[int] | None = None) -> list[dict[str, int]]:
|
|
122
84
|
"""
|
|
123
85
|
Return a list of dicts mapping \"RECORDID\" to the record id and \"EXPERIMENTID\" to the experiment id.
|
|
124
86
|
At least one of record_ids and exp_ids should be provided.
|
|
@@ -126,89 +88,31 @@ class ExperimentReportUtil:
|
|
|
126
88
|
assert (record_ids or exp_ids)
|
|
127
89
|
|
|
128
90
|
if record_ids:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
ids_str = "{" + ", ".join(rec_ids) + "}"
|
|
132
|
-
|
|
133
|
-
records_term = RawReportTerm(
|
|
134
|
-
data_type_name, "RECORDID", RawTermOperation.EQUAL_TO_OPERATOR, ids_str
|
|
135
|
-
)
|
|
136
|
-
|
|
91
|
+
records_term = TermBuilder.is_term(data_type_name, "RECORDID", record_ids)
|
|
137
92
|
else:
|
|
138
93
|
# Get all records of the given type
|
|
139
|
-
records_term =
|
|
140
|
-
data_type_name,
|
|
141
|
-
"RECORDID",
|
|
142
|
-
RawTermOperation.GREATER_THAN_OR_EQUAL_OPERATOR,
|
|
143
|
-
"0",
|
|
144
|
-
)
|
|
94
|
+
records_term = TermBuilder.all_records_term(data_type_name)
|
|
145
95
|
|
|
146
96
|
if exp_ids:
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
ids_str = "{" + ", ".join(exp_ids) + "}"
|
|
150
|
-
|
|
151
|
-
exp_term = RawReportTerm(
|
|
152
|
-
"NOTEBOOKEXPERIMENT",
|
|
153
|
-
"EXPERIMENTID",
|
|
154
|
-
RawTermOperation.EQUAL_TO_OPERATOR,
|
|
155
|
-
ids_str,
|
|
156
|
-
)
|
|
157
|
-
|
|
97
|
+
exp_term = TermBuilder.is_term("NOTEBOOKEXPERIMENT", "EXPERIMENTID", exp_ids)
|
|
158
98
|
else:
|
|
159
99
|
# Get all experiments
|
|
160
|
-
exp_term =
|
|
161
|
-
"NOTEBOOKEXPERIMENT",
|
|
162
|
-
"EXPERIMENTID",
|
|
163
|
-
RawTermOperation.GREATER_THAN_OR_EQUAL_OPERATOR,
|
|
164
|
-
"0",
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
root_term = CompositeReportTerm(
|
|
168
|
-
records_term, CompositeTermOperation.AND_OPERATOR, exp_term
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
# The columns the resulting dataframe will have
|
|
172
|
-
column_list = [
|
|
173
|
-
ReportColumn(data_type_name, "RECORDID", FieldType.LONG),
|
|
174
|
-
ReportColumn("NOTEBOOKEXPERIMENT", "EXPERIMENTID", FieldType.LONG),
|
|
175
|
-
]
|
|
100
|
+
exp_term = TermBuilder.gte_term("NOTEBOOKEXPERIMENT", "EXPERIMENTID", "0")
|
|
176
101
|
|
|
177
|
-
|
|
178
|
-
records_entry_join = FieldCompareReportTerm(
|
|
179
|
-
data_type_name,
|
|
180
|
-
"RECORDID",
|
|
181
|
-
RawTermOperation.EQUAL_TO_OPERATOR,
|
|
182
|
-
"EXPERIMENTENTRYRECORD",
|
|
183
|
-
"RECORDID",
|
|
184
|
-
)
|
|
102
|
+
root_term = TermBuilder.and_terms(records_term, exp_term)
|
|
185
103
|
|
|
104
|
+
# Join records on the experiment entry records that correspond to them.
|
|
105
|
+
records_entry_join = TermBuilder.compare_is_term("EXPERIMENTENTRYRECORD", "RECORDID", data_type_name, "RECORDID")
|
|
186
106
|
# Join entry records on the experiment entries they are in.
|
|
187
|
-
experiment_entry_enb_entry_join =
|
|
188
|
-
"EXPERIMENTENTRYRECORD",
|
|
189
|
-
"ENTRYID",
|
|
190
|
-
RawTermOperation.EQUAL_TO_OPERATOR,
|
|
191
|
-
"ENBENTRY",
|
|
192
|
-
"ENTRYID",
|
|
193
|
-
)
|
|
194
|
-
|
|
107
|
+
experiment_entry_enb_entry_join = TermBuilder.compare_is_term("ENBENTRY", "ENTRYID", "EXPERIMENTENTRYRECORD", "ENTRYID")
|
|
195
108
|
# Join entries on the experiments they are in.
|
|
196
|
-
enb_entry_experiment_join =
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
root_term,
|
|
207
|
-
join_list=[
|
|
208
|
-
ExplicitJoinDefinition("EXPERIMENTENTRYRECORD", records_entry_join),
|
|
209
|
-
ExplicitJoinDefinition("ENBENTRY", experiment_entry_enb_entry_join),
|
|
210
|
-
ExplicitJoinDefinition("NOTEBOOKEXPERIMENT", enb_entry_experiment_join),
|
|
211
|
-
],
|
|
212
|
-
)
|
|
213
|
-
|
|
214
|
-
return CustomReportUtil.run_custom_report(user, report_criteria)
|
|
109
|
+
enb_entry_experiment_join = TermBuilder.compare_is_term("NOTEBOOKEXPERIMENT", "EXPERIMENTID", "ENBENTRY", "EXPERIMENTID")
|
|
110
|
+
|
|
111
|
+
report_builder = CustomReportBuilder(data_type_name)
|
|
112
|
+
report_builder.set_root_term(root_term)
|
|
113
|
+
report_builder.add_column("RECORDID", FieldType.LONG, data_type=data_type_name)
|
|
114
|
+
report_builder.add_column("EXPERIMENTID", FieldType.LONG, data_type="NOTEBOOKEXPERIMENT")
|
|
115
|
+
report_builder.add_join(records_entry_join)
|
|
116
|
+
report_builder.add_join(experiment_entry_enb_entry_join)
|
|
117
|
+
report_builder.add_join(enb_entry_experiment_join)
|
|
118
|
+
return CustomReportUtil.run_custom_report(user, report_builder.build_report_criteria())
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import io
|
|
2
2
|
|
|
3
3
|
from sapiopylib.rest.User import SapioUser
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
from sapiopycommons.general.aliases import UserIdentifier, AliasUtil
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class CDL:
|
|
8
9
|
@staticmethod
|
|
9
|
-
def load_cdl(context:
|
|
10
|
+
def load_cdl(context: UserIdentifier, config_name: str, file_name: str, file_data: bytes | str) \
|
|
10
11
|
-> list[int]:
|
|
11
12
|
"""
|
|
12
13
|
Create data records from a file using one of the complex data loader (CDL) configurations in the system.
|
|
@@ -22,7 +23,7 @@ class CDL:
|
|
|
22
23
|
"configName": config_name,
|
|
23
24
|
"fileName": file_name
|
|
24
25
|
}
|
|
25
|
-
user: SapioUser =
|
|
26
|
+
user: SapioUser = AliasUtil.to_sapio_user(context)
|
|
26
27
|
with io.BytesIO(file_data.encode() if isinstance(file_data, str) else file_data) as data_stream:
|
|
27
28
|
response = user.post_data_stream(sub_path, params=params, data_stream=data_stream)
|
|
28
29
|
user.raise_for_status(response)
|
|
@@ -4,13 +4,14 @@ import urllib.parse
|
|
|
4
4
|
|
|
5
5
|
from requests import Response
|
|
6
6
|
from sapiopylib.rest.User import SapioUser
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
from sapiopycommons.general.aliases import UserIdentifier, AliasUtil
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
# FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
|
|
11
12
|
class FileBridge:
|
|
12
13
|
@staticmethod
|
|
13
|
-
def read_file(context:
|
|
14
|
+
def read_file(context: UserIdentifier, bridge_name: str, file_path: str,
|
|
14
15
|
base64_decode: bool = True) -> bytes:
|
|
15
16
|
"""
|
|
16
17
|
Read a file from FileBridge.
|
|
@@ -27,7 +28,7 @@ class FileBridge:
|
|
|
27
28
|
params = {
|
|
28
29
|
'Filepath': f"bridge://{bridge_name}/{file_path}"
|
|
29
30
|
}
|
|
30
|
-
user: SapioUser =
|
|
31
|
+
user: SapioUser = AliasUtil.to_sapio_user(context)
|
|
31
32
|
response = user.get(sub_path, params)
|
|
32
33
|
user.raise_for_status(response)
|
|
33
34
|
|
|
@@ -37,7 +38,7 @@ class FileBridge:
|
|
|
37
38
|
return ret_val
|
|
38
39
|
|
|
39
40
|
@staticmethod
|
|
40
|
-
def write_file(context:
|
|
41
|
+
def write_file(context: UserIdentifier, bridge_name: str, file_path: str,
|
|
41
42
|
file_data: bytes | str) -> None:
|
|
42
43
|
"""
|
|
43
44
|
Write a file to FileBridge.
|
|
@@ -53,13 +54,13 @@ class FileBridge:
|
|
|
53
54
|
params = {
|
|
54
55
|
'Filepath': f"bridge://{bridge_name}/{file_path}"
|
|
55
56
|
}
|
|
56
|
-
user: SapioUser =
|
|
57
|
+
user: SapioUser = AliasUtil.to_sapio_user(context)
|
|
57
58
|
with io.BytesIO(file_data.encode() if isinstance(file_data, str) else file_data) as data_stream:
|
|
58
59
|
response = user.post_data_stream(sub_path, params=params, data_stream=data_stream)
|
|
59
60
|
user.raise_for_status(response)
|
|
60
61
|
|
|
61
62
|
@staticmethod
|
|
62
|
-
def list_directory(context:
|
|
63
|
+
def list_directory(context: UserIdentifier, bridge_name: str,
|
|
63
64
|
file_path: str | None = "") -> list[str]:
|
|
64
65
|
"""
|
|
65
66
|
List the contents of a FileBridge directory.
|
|
@@ -74,7 +75,7 @@ class FileBridge:
|
|
|
74
75
|
params = {
|
|
75
76
|
'Filepath': f"bridge://{bridge_name}/{file_path}"
|
|
76
77
|
}
|
|
77
|
-
user: SapioUser =
|
|
78
|
+
user: SapioUser = AliasUtil.to_sapio_user(context)
|
|
78
79
|
response: Response = user.get(sub_path, params=params)
|
|
79
80
|
user.raise_for_status(response)
|
|
80
81
|
|
|
@@ -83,7 +84,7 @@ class FileBridge:
|
|
|
83
84
|
return [urllib.parse.unquote(value)[path_length:] for value in response_body]
|
|
84
85
|
|
|
85
86
|
@staticmethod
|
|
86
|
-
def create_directory(context:
|
|
87
|
+
def create_directory(context: UserIdentifier, bridge_name: str, file_path: str) -> None:
|
|
87
88
|
"""
|
|
88
89
|
Create a new directory in FileBridge.
|
|
89
90
|
|
|
@@ -97,12 +98,12 @@ class FileBridge:
|
|
|
97
98
|
params = {
|
|
98
99
|
'Filepath': f"bridge://{bridge_name}/{file_path}"
|
|
99
100
|
}
|
|
100
|
-
user: SapioUser =
|
|
101
|
+
user: SapioUser = AliasUtil.to_sapio_user(context)
|
|
101
102
|
response = user.post(sub_path, params=params)
|
|
102
103
|
user.raise_for_status(response)
|
|
103
104
|
|
|
104
105
|
@staticmethod
|
|
105
|
-
def delete_file(context:
|
|
106
|
+
def delete_file(context: UserIdentifier, bridge_name: str, file_path: str) -> None:
|
|
106
107
|
"""
|
|
107
108
|
Delete an existing file in FileBridge.
|
|
108
109
|
|
|
@@ -115,12 +116,12 @@ class FileBridge:
|
|
|
115
116
|
params = {
|
|
116
117
|
'Filepath': f"bridge://{bridge_name}/{file_path}"
|
|
117
118
|
}
|
|
118
|
-
user: SapioUser =
|
|
119
|
+
user: SapioUser = AliasUtil.to_sapio_user(context)
|
|
119
120
|
response = user.delete(sub_path, params=params)
|
|
120
121
|
user.raise_for_status(response)
|
|
121
122
|
|
|
122
123
|
@staticmethod
|
|
123
|
-
def delete_directory(context:
|
|
124
|
+
def delete_directory(context: UserIdentifier, bridge_name: str, file_path: str) -> None:
|
|
124
125
|
"""
|
|
125
126
|
Delete an existing directory in FileBridge.
|
|
126
127
|
|
|
@@ -133,6 +134,6 @@ class FileBridge:
|
|
|
133
134
|
params = {
|
|
134
135
|
'Filepath': f"bridge://{bridge_name}/{file_path}"
|
|
135
136
|
}
|
|
136
|
-
user: SapioUser =
|
|
137
|
+
user: SapioUser = AliasUtil.to_sapio_user(context)
|
|
137
138
|
response = user.delete(sub_path, params=params)
|
|
138
139
|
user.raise_for_status(response)
|