sapiopycommons 2024.10.25a345__py3-none-any.whl → 2024.10.29a346__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/chem/IndigoMolecules.py +0 -1
- sapiopycommons/chem/Molecules.py +19 -77
- sapiopycommons/customreport/custom_report_builder.py +9 -4
- sapiopycommons/datatype/data_fields.py +61 -0
- sapiopycommons/eln/experiment_handler.py +13 -3
- sapiopycommons/eln/experiment_report_util.py +438 -30
- sapiopycommons/general/aliases.py +16 -9
- sapiopycommons/multimodal/multimodal_data.py +3 -6
- sapiopycommons/processtracking/custom_workflow_handler.py +406 -0
- sapiopycommons/webhook/webhook_context.py +39 -0
- {sapiopycommons-2024.10.25a345.dist-info → sapiopycommons-2024.10.29a346.dist-info}/METADATA +1 -1
- {sapiopycommons-2024.10.25a345.dist-info → sapiopycommons-2024.10.29a346.dist-info}/RECORD +14 -13
- sapiopycommons/flowcyto/flow_cyto.py +0 -77
- sapiopycommons/flowcyto/flowcyto_data.py +0 -75
- {sapiopycommons-2024.10.25a345.dist-info → sapiopycommons-2024.10.29a346.dist-info}/WHEEL +0 -0
- {sapiopycommons-2024.10.25a345.dist-info → sapiopycommons-2024.10.29a346.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from sapiopylib.rest.ELNService import ElnManager
|
|
2
2
|
from sapiopylib.rest.User import SapioUser
|
|
3
|
+
from sapiopylib.rest.pojo.CustomReport import CustomReportCriteria, AbstractReportTerm, RawReportTerm
|
|
3
4
|
from sapiopylib.rest.pojo.datatype.FieldDefinition import FieldType
|
|
4
5
|
from sapiopylib.rest.pojo.eln.ElnExperiment import ElnExperiment, ElnExperimentQueryCriteria
|
|
6
|
+
from sapiopylib.rest.pojo.eln.SapioELNEnums import ElnExperimentStatus, ElnBaseDataType
|
|
5
7
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
|
|
6
8
|
|
|
7
9
|
from sapiopycommons.customreport.custom_report_builder import CustomReportBuilder
|
|
@@ -11,9 +13,131 @@ from sapiopycommons.datatype.pseudo_data_types import EnbEntryOptionsPseudoDef,
|
|
|
11
13
|
from sapiopycommons.general.aliases import SapioRecord, UserIdentifier, AliasUtil, FieldValue, \
|
|
12
14
|
ExperimentEntryIdentifier, ExperimentIdentifier
|
|
13
15
|
from sapiopycommons.general.custom_report_util import CustomReportUtil
|
|
16
|
+
from sapiopycommons.general.exceptions import SapioException
|
|
14
17
|
from sapiopycommons.recordmodel.record_handler import RecordHandler
|
|
15
18
|
|
|
16
19
|
|
|
20
|
+
# FR-47234: Create an ExperimentReportCriteria class for finer experiment searching
|
|
21
|
+
# and create/update ExperimentReportUtil functions to use it.
|
|
22
|
+
class ExperimentReportCriteria:
|
|
23
|
+
"""
|
|
24
|
+
Experiment report criteria is used to restrict the results of searches for experiments.
|
|
25
|
+
"""
|
|
26
|
+
notebook_ids: list[int] | None
|
|
27
|
+
not_notebook_ids: list[int] | None
|
|
28
|
+
names: list[str] | None
|
|
29
|
+
not_names: list[str] | None
|
|
30
|
+
created_by: list[str] | None
|
|
31
|
+
not_created_by: list[str] | None
|
|
32
|
+
last_modified_by: list[str] | None
|
|
33
|
+
not_last_modified_by: list[str] | None
|
|
34
|
+
owners: list[str] | None
|
|
35
|
+
not_owners: list[str] | None
|
|
36
|
+
statuses: list[str] | None
|
|
37
|
+
not_statuses: list[str] | None
|
|
38
|
+
templates: str | None
|
|
39
|
+
not_templates: str | None
|
|
40
|
+
is_from_template: bool | None
|
|
41
|
+
is_from_protocol_template: bool | None
|
|
42
|
+
is_modifiable: bool | None
|
|
43
|
+
is_active: bool | None
|
|
44
|
+
created_before: int | None
|
|
45
|
+
created_after: int | None
|
|
46
|
+
due_before: int | None
|
|
47
|
+
due_after: int | None
|
|
48
|
+
last_modified_before: int | None
|
|
49
|
+
last_modified_after: int | None
|
|
50
|
+
|
|
51
|
+
def __init__(self, *,
|
|
52
|
+
notebook_ids: list[ExperimentIdentifier] | None = None,
|
|
53
|
+
not_notebook_ids: list[ExperimentIdentifier] | None = None,
|
|
54
|
+
names: list[str] | None = None,
|
|
55
|
+
not_names: list[str] | None = None,
|
|
56
|
+
created_by: list[str] | None = None,
|
|
57
|
+
not_created_by: list[str] | None = None,
|
|
58
|
+
last_modified_by: list[str] | None = None,
|
|
59
|
+
not_last_modified_by: list[str] | None = None,
|
|
60
|
+
owners: list[str] | None = None,
|
|
61
|
+
not_owners: list[str] | None = None,
|
|
62
|
+
statuses: list[ElnExperimentStatus | str] | None = None,
|
|
63
|
+
not_statuses: list[ElnExperimentStatus | str] | None = None,
|
|
64
|
+
templates: list[str] | None = None,
|
|
65
|
+
not_templates: list[str] | None = None,
|
|
66
|
+
is_from_template: bool | None = None,
|
|
67
|
+
is_from_protocol_template: bool | None = None,
|
|
68
|
+
is_modifiable: bool | None = None,
|
|
69
|
+
is_active: bool | None = None,
|
|
70
|
+
created_after: int | None = None,
|
|
71
|
+
created_before: int | None = None,
|
|
72
|
+
due_after: int | None = None,
|
|
73
|
+
due_before: int | None = None,
|
|
74
|
+
last_modified_after: int | None = None,
|
|
75
|
+
last_modified_before: int | None = None):
|
|
76
|
+
"""
|
|
77
|
+
Restrict searches using the following criteria.
|
|
78
|
+
|
|
79
|
+
:param notebook_ids: The allowed notebook ID(s) of the experiment.
|
|
80
|
+
:param not_notebook_ids: The disallowed notebook ID(s) of the experiment.
|
|
81
|
+
:param names: The allowed name(s) of the experiment.
|
|
82
|
+
:param not_names: The disallowed name(s) of the experiment.
|
|
83
|
+
:param created_by: The allowed username(s) of the user who created the experiment.
|
|
84
|
+
:param not_created_by: The disallowed username(s) of the user who created the experiment.
|
|
85
|
+
:param last_modified_by: The allowed username(s) of the user who last modified the experiment.
|
|
86
|
+
:param not_last_modified_by: The disallowed username(s) of the user who last modified the experiment.
|
|
87
|
+
:param owners: The allowed username(s) of the user who owns the experiment.
|
|
88
|
+
:param not_owners: The disallowed username(s) of the user who owns the experiment.
|
|
89
|
+
:param statuses: The allowed status(es) of the experiment.
|
|
90
|
+
:param not_statuses: The disallowed status(es) of the experiment.
|
|
91
|
+
:param templates: The allowed template name(s) that the experiment was created from.
|
|
92
|
+
:param not_templates: The disallowed template name(s) that the experiment was created from.
|
|
93
|
+
:param is_from_template: Whether the experiment was created from a template.
|
|
94
|
+
:param is_from_protocol_template: Whether the experiment was created from a protocol template.
|
|
95
|
+
:param is_modifiable: Whether the experiment is modifiable.
|
|
96
|
+
:param is_active: Whether the experiment is from an active template.
|
|
97
|
+
:param created_after: A timestamp after which the experiment was created.
|
|
98
|
+
:param created_before: A timestamp before which the experiment was created.
|
|
99
|
+
:param due_after: A timestamp after which the experiment's approval is due.
|
|
100
|
+
:param due_before: A timestamp before which the experiment's approval is due.
|
|
101
|
+
:param last_modified_after: A timestamp after which the experiment last modified.
|
|
102
|
+
:param last_modified_before: A timestamp before which the experiment last modified.
|
|
103
|
+
"""
|
|
104
|
+
self.notebook_ids = notebook_ids
|
|
105
|
+
self.not_notebook_ids = not_notebook_ids
|
|
106
|
+
self.names = names
|
|
107
|
+
self.not_names = not_names
|
|
108
|
+
self.created_by = created_by
|
|
109
|
+
self.not_created_by = not_created_by
|
|
110
|
+
self.last_modified_by = last_modified_by
|
|
111
|
+
self.not_last_modified_by = not_last_modified_by
|
|
112
|
+
self.owners = owners
|
|
113
|
+
self.not_owners = not_owners
|
|
114
|
+
self.statuses = statuses
|
|
115
|
+
self.not_statuses = not_statuses
|
|
116
|
+
self.templates = templates
|
|
117
|
+
self.not_templates = not_templates
|
|
118
|
+
|
|
119
|
+
self.is_from_template = is_from_template
|
|
120
|
+
self.is_from_protocol_template = is_from_protocol_template
|
|
121
|
+
self.is_modifiable = is_modifiable
|
|
122
|
+
self.is_active = is_active
|
|
123
|
+
|
|
124
|
+
self.created_before = created_before
|
|
125
|
+
self.created_after = created_after
|
|
126
|
+
self.due_before = due_before
|
|
127
|
+
self.due_after = due_after
|
|
128
|
+
self.last_modified_before = last_modified_before
|
|
129
|
+
self.last_modified_after = last_modified_after
|
|
130
|
+
|
|
131
|
+
if self.notebook_ids is not None:
|
|
132
|
+
self.notebook_ids = AliasUtil.to_notebook_ids(self.notebook_ids)
|
|
133
|
+
if self.not_notebook_ids is not None:
|
|
134
|
+
self.not_notebook_ids = AliasUtil.to_notebook_ids(self.not_notebook_ids)
|
|
135
|
+
if self.statuses is not None:
|
|
136
|
+
self.statuses = [x.description if isinstance(x, ElnExperimentStatus) else x for x in self.statuses]
|
|
137
|
+
if self.not_statuses is not None:
|
|
138
|
+
self.not_statuses = [x.description if isinstance(x, ElnExperimentStatus) else x for x in self.not_statuses]
|
|
139
|
+
|
|
140
|
+
|
|
17
141
|
# FR-46908 - Provide a utility class that holds experiment related custom reports e.g. getting all the experiments
|
|
18
142
|
# that given records were used in or getting all records of a datatype used in given experiments.
|
|
19
143
|
class ExperimentReportUtil:
|
|
@@ -34,7 +158,7 @@ class ExperimentReportUtil:
|
|
|
34
158
|
user: SapioUser = AliasUtil.to_sapio_user(context)
|
|
35
159
|
data_type_name: str = AliasUtil.to_singular_data_type_name(records)
|
|
36
160
|
|
|
37
|
-
record_ids =
|
|
161
|
+
record_ids: list[int] = AliasUtil.to_record_ids(records)
|
|
38
162
|
rows = ExperimentReportUtil.__get_record_experiment_relation_rows(user, data_type_name, record_ids=record_ids)
|
|
39
163
|
|
|
40
164
|
id_to_record: dict[int, SapioRecord] = RecordHandler.map_by_id(records)
|
|
@@ -47,6 +171,45 @@ class ExperimentReportUtil:
|
|
|
47
171
|
|
|
48
172
|
return {record: list(exps) for record, exps in record_to_exps.items()}
|
|
49
173
|
|
|
174
|
+
@staticmethod
|
|
175
|
+
def map_eln_records_to_experiment_ids(context: UserIdentifier, records: list[SapioRecord]) \
|
|
176
|
+
-> dict[SapioRecord, int]:
|
|
177
|
+
"""
|
|
178
|
+
Return a dictionary mapping each ELN record to the ID of the experiments that each were used in.
|
|
179
|
+
|
|
180
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
181
|
+
:param records: A list of ELN data type records. They are permitted to be of mixed data type names.
|
|
182
|
+
:return: A dictionary mapping each record to the ID of the experiment it was used in.
|
|
183
|
+
"""
|
|
184
|
+
if not records:
|
|
185
|
+
return {}
|
|
186
|
+
|
|
187
|
+
data_types: set[str] = AliasUtil.to_data_type_names(records, True, False)
|
|
188
|
+
for data_type in data_types:
|
|
189
|
+
if not ElnBaseDataType.is_eln_type(data_type):
|
|
190
|
+
raise SapioException(f"Data type {data_type} is not an ELN data type.")
|
|
191
|
+
|
|
192
|
+
dt_to_record: dict[str, list[SapioRecord]] = {}
|
|
193
|
+
for record in records:
|
|
194
|
+
dt_to_record.setdefault(AliasUtil.to_data_type_name(record, False), []).append(record)
|
|
195
|
+
|
|
196
|
+
report_builder = CustomReportBuilder(EnbEntryPseudoDef.DATA_TYPE_NAME)
|
|
197
|
+
report_builder.add_column(EnbEntryPseudoDef.DATA_TYPE_NAME__FIELD_NAME)
|
|
198
|
+
report_builder.add_column(EnbEntryPseudoDef.EXPERIMENT_ID__FIELD_NAME)
|
|
199
|
+
report_builder.set_root_term(TermBuilder.is_term(EnbEntryPseudoDef.DATA_TYPE_NAME,
|
|
200
|
+
EnbEntryPseudoDef.DATA_TYPE_NAME__FIELD_NAME,
|
|
201
|
+
data_types))
|
|
202
|
+
criteria = report_builder.build_report_criteria()
|
|
203
|
+
|
|
204
|
+
ret_val: dict[SapioRecord, int] = {}
|
|
205
|
+
rows: list[dict[str, FieldValue]] = CustomReportUtil.run_custom_report(context, criteria)
|
|
206
|
+
for row in rows:
|
|
207
|
+
dt: str = row[EnbEntryPseudoDef.DATA_TYPE_NAME__FIELD_NAME.field_name]
|
|
208
|
+
exp_id: int = row[EnbEntryPseudoDef.EXPERIMENT_ID__FIELD_NAME.field_name]
|
|
209
|
+
for record in dt_to_record[dt]:
|
|
210
|
+
ret_val[record] = exp_id
|
|
211
|
+
return ret_val
|
|
212
|
+
|
|
50
213
|
@staticmethod
|
|
51
214
|
def map_experiments_to_records_of_type(context: UserIdentifier, exp_ids: list[ExperimentIdentifier],
|
|
52
215
|
wrapper_type: type[WrappedType]) -> dict[int, list[WrappedType]]:
|
|
@@ -148,52 +311,299 @@ class ExperimentReportUtil:
|
|
|
148
311
|
return options
|
|
149
312
|
|
|
150
313
|
@staticmethod
|
|
151
|
-
def
|
|
314
|
+
def get_template_names_for_experiments(context: UserIdentifier, experiments: list[ExperimentIdentifier]) \
|
|
315
|
+
-> dict[int, str]:
|
|
316
|
+
"""
|
|
317
|
+
Run a custom report to retrieve the template names for all the provided experiments.
|
|
318
|
+
|
|
319
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
320
|
+
:param experiments: The experiment identifiers to retrieve the template names for.
|
|
321
|
+
:return: A dictionary mapping the notebook experiment ID to the template name that the experiment was created from.
|
|
322
|
+
"""
|
|
323
|
+
exp_ids: list[int] = AliasUtil.to_notebook_ids(experiments)
|
|
324
|
+
|
|
325
|
+
report_builder = CustomReportBuilder(NotebookExperimentPseudoDef.DATA_TYPE_NAME)
|
|
326
|
+
root = TermBuilder.is_term(NotebookExperimentPseudoDef.DATA_TYPE_NAME,
|
|
327
|
+
NotebookExperimentPseudoDef.EXPERIMENT_ID__FIELD_NAME,
|
|
328
|
+
exp_ids)
|
|
329
|
+
report_builder.add_join(TermBuilder.compare_is_term("ELNExperiment",
|
|
330
|
+
"RecordId",
|
|
331
|
+
NotebookExperimentPseudoDef.DATA_TYPE_NAME,
|
|
332
|
+
NotebookExperimentPseudoDef.EXPERIMENT_RECORD_ID__FIELD_NAME))
|
|
333
|
+
report_builder.set_root_term(root)
|
|
334
|
+
report_builder.add_column(NotebookExperimentPseudoDef.EXPERIMENT_ID__FIELD_NAME)
|
|
335
|
+
report_builder.add_column("TemplateExperimentName", FieldType.STRING, data_type="ELNExperiment")
|
|
336
|
+
report = report_builder.build_report_criteria()
|
|
337
|
+
|
|
338
|
+
ret_val: dict[int, str] = {}
|
|
339
|
+
results: list[dict[str, FieldValue]] = CustomReportUtil.run_custom_report(context, report)
|
|
340
|
+
for row in results:
|
|
341
|
+
exp_id: int = row[NotebookExperimentPseudoDef.EXPERIMENT_ID__FIELD_NAME.field_name]
|
|
342
|
+
name: str = row["TemplateExperimentName"]
|
|
343
|
+
ret_val[exp_id] = name
|
|
344
|
+
return ret_val
|
|
345
|
+
|
|
346
|
+
@staticmethod
|
|
347
|
+
def get_experiments_for_criteria(context: UserIdentifier, criteria: ExperimentReportCriteria) -> list[ElnExperiment]:
|
|
348
|
+
"""
|
|
349
|
+
Run a custom report that retrieves every experiment in the system for the given search criteria.
|
|
350
|
+
|
|
351
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
352
|
+
:param criteria: The search criteria to query for experiments with.
|
|
353
|
+
:return: A list of every experiment in the system that matches the search criteria.
|
|
354
|
+
"""
|
|
355
|
+
report: CustomReportCriteria = ExperimentReportUtil.build_experiment_id_report(criteria)
|
|
356
|
+
return ExperimentReportUtil.get_experiments_for_report(context, report)
|
|
357
|
+
|
|
358
|
+
@staticmethod
|
|
359
|
+
def get_experiments_by_name(context: UserIdentifier, name: str,
|
|
360
|
+
criteria: ExperimentReportCriteria = ExperimentReportCriteria()) -> list[ElnExperiment]:
|
|
152
361
|
"""
|
|
153
362
|
Run a custom report that retrieves every experiment in the system with a given name.
|
|
154
363
|
|
|
155
364
|
:param context: The current webhook context or a user object to send requests from.
|
|
156
365
|
:param name: The name of the experiment to query for.
|
|
157
|
-
:
|
|
366
|
+
:param criteria: Additional search criteria to filter the results.
|
|
367
|
+
:return: A list of every experiment in the system with a name that matches the input
|
|
368
|
+
that matches the given criteria.
|
|
158
369
|
"""
|
|
159
|
-
return ExperimentReportUtil.get_experiments_by_names(context, [name])[name]
|
|
370
|
+
return ExperimentReportUtil.get_experiments_by_names(context, [name], criteria)[name]
|
|
160
371
|
|
|
161
372
|
@staticmethod
|
|
162
|
-
def get_experiments_by_names(context: UserIdentifier, names: list[str]
|
|
373
|
+
def get_experiments_by_names(context: UserIdentifier, names: list[str],
|
|
374
|
+
criteria: ExperimentReportCriteria = ExperimentReportCriteria()) -> dict[str, list[ElnExperiment]]:
|
|
163
375
|
"""
|
|
164
376
|
Run a custom report that retrieves every experiment in the system with a name from a list of names.
|
|
165
377
|
|
|
166
378
|
:param context: The current webhook context or a user object to send requests from.
|
|
167
379
|
:param names: The names of the experiment to query for.
|
|
168
|
-
:
|
|
380
|
+
:param criteria: Additional search criteria to filter the results.
|
|
381
|
+
:return: A dictionary mapping the experiment name to a list of every experiment in the system with that name
|
|
382
|
+
that matches the given criteria.
|
|
169
383
|
"""
|
|
170
|
-
|
|
384
|
+
criteria.names = names
|
|
385
|
+
experiments: list[ElnExperiment] = ExperimentReportUtil.get_experiments_for_criteria(context, criteria)
|
|
171
386
|
|
|
172
|
-
|
|
387
|
+
# Ensure that each name appears in the dictionary, even if it has no experiments.
|
|
388
|
+
ret_val: dict[str, list[ElnExperiment]] = {x: [] for x in names}
|
|
389
|
+
for experiment in experiments:
|
|
390
|
+
ret_val.get(experiment.notebook_experiment_name).append(experiment)
|
|
391
|
+
return ret_val
|
|
392
|
+
|
|
393
|
+
@staticmethod
|
|
394
|
+
def get_experiments_by_owner(context: UserIdentifier, owner: str,
|
|
395
|
+
criteria: ExperimentReportCriteria = ExperimentReportCriteria()) -> list[ElnExperiment]:
|
|
396
|
+
"""
|
|
397
|
+
Run a custom report that retrieves every experiment in the system with a given owner.
|
|
398
|
+
|
|
399
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
400
|
+
:param owner: The username of the owner of the experiments to query for.
|
|
401
|
+
:param criteria: Additional search criteria to filter the results.
|
|
402
|
+
:return: A list of every experiment in the system with the owner that matches the input
|
|
403
|
+
that matches the given criteria.
|
|
404
|
+
"""
|
|
405
|
+
return ExperimentReportUtil.get_experiments_by_owners(context, [owner], criteria)[owner]
|
|
406
|
+
|
|
407
|
+
@staticmethod
|
|
408
|
+
def get_experiments_by_owners(context: UserIdentifier, owners: list[str],
|
|
409
|
+
criteria: ExperimentReportCriteria = ExperimentReportCriteria) \
|
|
410
|
+
-> dict[str, list[ElnExperiment]]:
|
|
411
|
+
"""
|
|
412
|
+
Run a custom report that retrieves every experiment in the system with a given list of owners.
|
|
413
|
+
|
|
414
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
415
|
+
:param owners: The usernames of the owner of the experiments to query for.
|
|
416
|
+
:param criteria: Additional search criteria to filter the results.
|
|
417
|
+
:return: A dictionary mapping the owner username to a list of every experiment in the system from that owner
|
|
418
|
+
that matches the given criteria.
|
|
419
|
+
"""
|
|
420
|
+
criteria.owners = owners
|
|
421
|
+
experiments: list[ElnExperiment] = ExperimentReportUtil.get_experiments_for_criteria(context, criteria)
|
|
422
|
+
|
|
423
|
+
# Ensure that each name appears in the dictionary, even if it has no experiments.
|
|
424
|
+
ret_val: dict[str, list[ElnExperiment]] = {x: [] for x in owners}
|
|
425
|
+
for experiment in experiments:
|
|
426
|
+
ret_val.get(experiment.owner).append(experiment)
|
|
427
|
+
return ret_val
|
|
428
|
+
|
|
429
|
+
@staticmethod
|
|
430
|
+
def get_experiments_by_creator(context: UserIdentifier, created_by: str, *,
|
|
431
|
+
criteria: ExperimentReportCriteria = ExperimentReportCriteria()) \
|
|
432
|
+
-> list[ElnExperiment]:
|
|
433
|
+
"""
|
|
434
|
+
Run a custom report that retrieves every experiment in the system with a given creator.
|
|
435
|
+
|
|
436
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
437
|
+
:param created_by: The username of the creator of the experiments to query for.
|
|
438
|
+
:param criteria: Additional search criteria to filter the results.
|
|
439
|
+
:return: A list of every experiment in the system with the creator that matches the input
|
|
440
|
+
that matches the given criteria.
|
|
441
|
+
"""
|
|
442
|
+
return ExperimentReportUtil.get_experiments_by_creators(context, [created_by], criteria=criteria)[created_by]
|
|
443
|
+
|
|
444
|
+
@staticmethod
|
|
445
|
+
def get_experiments_by_creators(context: UserIdentifier, created_by: list[str], *,
|
|
446
|
+
criteria: ExperimentReportCriteria = ExperimentReportCriteria()) \
|
|
447
|
+
-> dict[str, list[ElnExperiment]]:
|
|
448
|
+
"""
|
|
449
|
+
Run a custom report that retrieves every experiment in the system with a given list of creators.
|
|
450
|
+
|
|
451
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
452
|
+
:param created_by: The usernames of the creator of the experiments to query for.
|
|
453
|
+
:param criteria: Additional search criteria to filter the results.
|
|
454
|
+
:return: A dictionary mapping the owner username to a list of every experiment in the system from that creator
|
|
455
|
+
that matches the given criteria.
|
|
456
|
+
"""
|
|
457
|
+
criteria.created_by = created_by
|
|
458
|
+
experiments: list[ElnExperiment] = ExperimentReportUtil.get_experiments_for_criteria(context, criteria)
|
|
459
|
+
|
|
460
|
+
# Ensure that each name appears in the dictionary, even if it has no experiments.
|
|
461
|
+
ret_val: dict[str, list[ElnExperiment]] = {x: [] for x in created_by}
|
|
462
|
+
for experiment in experiments:
|
|
463
|
+
ret_val.get(experiment.created_by).append(experiment)
|
|
464
|
+
return ret_val
|
|
465
|
+
|
|
466
|
+
@staticmethod
|
|
467
|
+
def build_experiment_id_report(criteria: ExperimentReportCriteria = ExperimentReportCriteria()) \
|
|
468
|
+
-> CustomReportCriteria:
|
|
469
|
+
"""
|
|
470
|
+
Construct a custom report using the provided ExperimentReportCriteria.
|
|
471
|
+
|
|
472
|
+
:param criteria: The criteria to construct a custom report from.
|
|
473
|
+
:return: A custom report that can be used to search for experiment IDs that match the given criteria.
|
|
474
|
+
"""
|
|
475
|
+
dt: str = NotebookExperimentPseudoDef.DATA_TYPE_NAME
|
|
476
|
+
report_builder = CustomReportBuilder(dt)
|
|
173
477
|
report_builder.add_column(NotebookExperimentPseudoDef.EXPERIMENT_ID__FIELD_NAME)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
478
|
+
|
|
479
|
+
root: AbstractReportTerm | None = None
|
|
480
|
+
if criteria.notebook_ids is not None:
|
|
481
|
+
root = TermBuilder.is_term(dt, NotebookExperimentPseudoDef.EXPERIMENT_ID__FIELD_NAME, criteria.notebook_ids)
|
|
482
|
+
if criteria.not_notebook_ids is not None:
|
|
483
|
+
term = TermBuilder.not_term(dt, NotebookExperimentPseudoDef.EXPERIMENT_ID__FIELD_NAME, criteria.not_notebook_ids)
|
|
484
|
+
if root is not None:
|
|
485
|
+
root = TermBuilder.and_terms(root, term)
|
|
486
|
+
if root is None:
|
|
487
|
+
root = ExperimentReportUtil.all_notebook_experiments_term()
|
|
488
|
+
|
|
489
|
+
if criteria.names is not None:
|
|
490
|
+
term = TermBuilder.is_term(dt, NotebookExperimentPseudoDef.EXPERIMENT_NAME__FIELD_NAME, criteria.names)
|
|
491
|
+
root = TermBuilder.and_terms(root, term)
|
|
492
|
+
if criteria.not_names is not None:
|
|
493
|
+
term = TermBuilder.not_term(dt, NotebookExperimentPseudoDef.EXPERIMENT_NAME__FIELD_NAME, criteria.not_names)
|
|
494
|
+
root = TermBuilder.and_terms(root, term)
|
|
495
|
+
|
|
496
|
+
if criteria.created_by is not None:
|
|
497
|
+
term = TermBuilder.is_term(dt, NotebookExperimentPseudoDef.CREATED_BY__FIELD_NAME, criteria.created_by)
|
|
498
|
+
root = TermBuilder.and_terms(root, term)
|
|
499
|
+
if criteria.not_created_by is not None:
|
|
500
|
+
term = TermBuilder.not_term(dt, NotebookExperimentPseudoDef.CREATED_BY__FIELD_NAME, criteria.not_created_by)
|
|
501
|
+
root = TermBuilder.and_terms(root, term)
|
|
502
|
+
|
|
503
|
+
if criteria.last_modified_by is not None:
|
|
504
|
+
term = TermBuilder.is_term(dt, NotebookExperimentPseudoDef.LAST_MODIFIED_BY__FIELD_NAME, criteria.last_modified_by)
|
|
505
|
+
root = TermBuilder.and_terms(root, term)
|
|
506
|
+
if criteria.not_last_modified_by is not None:
|
|
507
|
+
term = TermBuilder.not_term(dt, NotebookExperimentPseudoDef.LAST_MODIFIED_BY__FIELD_NAME, criteria.not_last_modified_by)
|
|
508
|
+
root = TermBuilder.and_terms(root, term)
|
|
509
|
+
|
|
510
|
+
if criteria.owners is not None:
|
|
511
|
+
term = TermBuilder.is_term(dt, NotebookExperimentPseudoDef.EXPERIMENT_OWNER__FIELD_NAME, criteria.owners)
|
|
512
|
+
root = TermBuilder.and_terms(root, term)
|
|
513
|
+
if criteria.not_owners is not None:
|
|
514
|
+
term = TermBuilder.not_term(dt, NotebookExperimentPseudoDef.EXPERIMENT_OWNER__FIELD_NAME, criteria.not_owners)
|
|
515
|
+
root = TermBuilder.and_terms(root, term)
|
|
516
|
+
|
|
517
|
+
if criteria.statuses is not None:
|
|
518
|
+
term = TermBuilder.is_term(dt, NotebookExperimentPseudoDef.STATUS__FIELD_NAME, criteria.statuses)
|
|
519
|
+
root = TermBuilder.and_terms(root, term)
|
|
520
|
+
if criteria.not_statuses is not None:
|
|
521
|
+
term = TermBuilder.not_term(dt, NotebookExperimentPseudoDef.STATUS__FIELD_NAME, criteria.not_statuses)
|
|
522
|
+
root = TermBuilder.and_terms(root, term)
|
|
523
|
+
|
|
524
|
+
# For the template name term, we need to join on the experiment record.
|
|
525
|
+
if criteria.templates is not None or criteria.not_templates is not None:
|
|
526
|
+
join = TermBuilder.compare_is_term("ELNExperiment",
|
|
527
|
+
"RecordId",
|
|
528
|
+
NotebookExperimentPseudoDef.DATA_TYPE_NAME,
|
|
529
|
+
NotebookExperimentPseudoDef.EXPERIMENT_RECORD_ID__FIELD_NAME)
|
|
530
|
+
report_builder.add_join(join)
|
|
531
|
+
if criteria.templates is not None:
|
|
532
|
+
term = TermBuilder.is_term("ELNExperiment", "TemplateExperimentName", criteria.templates)
|
|
533
|
+
root = TermBuilder.and_terms(root, term)
|
|
534
|
+
if criteria.not_templates is not None:
|
|
535
|
+
term = TermBuilder.not_term("ELNExperiment", "TemplateExperimentName", criteria.not_templates)
|
|
536
|
+
root = TermBuilder.and_terms(root, term)
|
|
537
|
+
|
|
538
|
+
if criteria.is_from_template is not None:
|
|
539
|
+
term = TermBuilder.is_term(dt, NotebookExperimentPseudoDef.IS_TEMPLATE__FIELD_NAME, criteria.is_from_template)
|
|
540
|
+
root = TermBuilder.and_terms(root, term)
|
|
541
|
+
if criteria.is_from_protocol_template is not None:
|
|
542
|
+
term = TermBuilder.is_term(dt, NotebookExperimentPseudoDef.IS_PROTOCOL_TEMPLATE__FIELD_NAME, criteria.is_from_protocol_template)
|
|
543
|
+
root = TermBuilder.and_terms(root, term)
|
|
544
|
+
if criteria.is_modifiable is not None:
|
|
545
|
+
term = TermBuilder.is_term(dt, NotebookExperimentPseudoDef.IS_MODIFIABLE__FIELD_NAME, criteria.is_modifiable)
|
|
546
|
+
root = TermBuilder.and_terms(root, term)
|
|
547
|
+
if criteria.is_active is not None:
|
|
548
|
+
term = TermBuilder.is_term(dt, NotebookExperimentPseudoDef.IS_ACTIVE__FIELD_NAME, criteria.is_active)
|
|
549
|
+
root = TermBuilder.and_terms(root, term)
|
|
550
|
+
|
|
551
|
+
if criteria.created_after is not None:
|
|
552
|
+
term = TermBuilder.gte_term(dt, NotebookExperimentPseudoDef.DATE_CREATED__FIELD_NAME, criteria.created_after)
|
|
553
|
+
root = TermBuilder.and_terms(root, term)
|
|
554
|
+
if criteria.created_before is not None:
|
|
555
|
+
term = TermBuilder.lte_term(dt, NotebookExperimentPseudoDef.DATE_CREATED__FIELD_NAME, criteria.created_before)
|
|
556
|
+
root = TermBuilder.and_terms(root, term)
|
|
557
|
+
|
|
558
|
+
if criteria.last_modified_after is not None:
|
|
559
|
+
term = TermBuilder.gte_term(dt, NotebookExperimentPseudoDef.LAST_MODIFIED_DATE__FIELD_NAME, criteria.last_modified_after)
|
|
560
|
+
root = TermBuilder.and_terms(root, term)
|
|
561
|
+
if criteria.last_modified_before is not None:
|
|
562
|
+
term = TermBuilder.lte_term(dt, NotebookExperimentPseudoDef.LAST_MODIFIED_DATE__FIELD_NAME, criteria.last_modified_before)
|
|
563
|
+
root = TermBuilder.and_terms(root, term)
|
|
564
|
+
|
|
565
|
+
if criteria.due_after is not None:
|
|
566
|
+
term = TermBuilder.gte_term(dt, NotebookExperimentPseudoDef.APPROVAL_DUE_DATE__FIELD_NAME, criteria.due_after)
|
|
567
|
+
root = TermBuilder.and_terms(root, term)
|
|
568
|
+
if criteria.due_before is not None:
|
|
569
|
+
term = TermBuilder.lte_term(dt, NotebookExperimentPseudoDef.APPROVAL_DUE_DATE__FIELD_NAME, criteria.due_before)
|
|
570
|
+
root = TermBuilder.and_terms(root, term)
|
|
571
|
+
|
|
177
572
|
report_builder.set_root_term(root)
|
|
573
|
+
return report_builder.build_report_criteria()
|
|
178
574
|
|
|
179
|
-
|
|
180
|
-
|
|
575
|
+
@staticmethod
|
|
576
|
+
def get_experiments_for_report(context: UserIdentifier, report: CustomReportCriteria) -> list[ElnExperiment]:
|
|
577
|
+
"""
|
|
578
|
+
Retrieve the ELN experiment objects for experiments whose notebook IDs appear in a custom report.
|
|
181
579
|
|
|
580
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
581
|
+
:param report: A custom report that searches for ELN experiments, containing an "Experiment ID" column to
|
|
582
|
+
query for the experiments of.
|
|
583
|
+
:return: The ELN experiments that match the provided report.
|
|
584
|
+
"""
|
|
585
|
+
user = AliasUtil.to_sapio_user(context)
|
|
182
586
|
exp_ids: list[int] = []
|
|
183
|
-
for row in CustomReportUtil.run_custom_report(user,
|
|
587
|
+
for row in CustomReportUtil.run_custom_report(user, report):
|
|
184
588
|
exp_ids.append(row[NotebookExperimentPseudoDef.EXPERIMENT_ID__FIELD_NAME.field_name])
|
|
185
589
|
if not exp_ids:
|
|
186
|
-
return
|
|
590
|
+
return []
|
|
187
591
|
|
|
188
592
|
criteria = ElnExperimentQueryCriteria(notebook_experiment_id_white_list=exp_ids)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
593
|
+
return ElnManager(user).get_eln_experiment_by_criteria(criteria)
|
|
594
|
+
|
|
595
|
+
@staticmethod
|
|
596
|
+
def all_notebook_experiments_term() -> RawReportTerm:
|
|
597
|
+
"""
|
|
598
|
+
:return: A report term searching for all notebook experiments.
|
|
599
|
+
"""
|
|
600
|
+
return TermBuilder.gte_term(NotebookExperimentPseudoDef.DATA_TYPE_NAME,
|
|
601
|
+
NotebookExperimentPseudoDef.EXPERIMENT_ID__FIELD_NAME,
|
|
602
|
+
0)
|
|
193
603
|
|
|
194
604
|
@staticmethod
|
|
195
605
|
def __get_record_experiment_relation_rows(user: SapioUser, data_type_name: str, record_ids: list[int] | None = None,
|
|
196
|
-
exp_ids: list[int] | None = None) -> list[dict[str,
|
|
606
|
+
exp_ids: list[int] | None = None) -> list[dict[str, FieldValue]]:
|
|
197
607
|
"""
|
|
198
608
|
Return a list of dicts mapping \"RECORDID\" to the record id and \"EXPERIMENTID\" to the experiment id.
|
|
199
609
|
At least one of record_ids and exp_ids should be provided.
|
|
@@ -212,17 +622,15 @@ class ExperimentReportUtil:
|
|
|
212
622
|
exp_ids)
|
|
213
623
|
else:
|
|
214
624
|
# Get all experiments
|
|
215
|
-
exp_term =
|
|
216
|
-
NotebookExperimentPseudoDef.EXPERIMENT_ID__FIELD_NAME,
|
|
217
|
-
0)
|
|
625
|
+
exp_term = ExperimentReportUtil.all_notebook_experiments_term()
|
|
218
626
|
|
|
219
627
|
root_term = TermBuilder.and_terms(records_term, exp_term)
|
|
220
628
|
|
|
221
629
|
# Join records on the experiment entry records that correspond to them.
|
|
222
|
-
records_entry_join = TermBuilder.compare_is_term(
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
630
|
+
records_entry_join = TermBuilder.compare_is_term(data_type_name,
|
|
631
|
+
"RecordId",
|
|
632
|
+
ExperimentEntryRecordPseudoDef.DATA_TYPE_NAME,
|
|
633
|
+
ExperimentEntryRecordPseudoDef.RECORD_ID__FIELD_NAME)
|
|
226
634
|
# Join entry records on the experiment entries they are in.
|
|
227
635
|
experiment_entry_enb_entry_join = TermBuilder.compare_is_term(EnbEntryPseudoDef.DATA_TYPE_NAME,
|
|
228
636
|
EnbEntryPseudoDef.ENTRY_ID__FIELD_NAME,
|
|
@@ -239,7 +647,7 @@ class ExperimentReportUtil:
|
|
|
239
647
|
report_builder.add_column("RecordId", FieldType.LONG)
|
|
240
648
|
report_builder.add_column(NotebookExperimentPseudoDef.EXPERIMENT_ID__FIELD_NAME,
|
|
241
649
|
data_type=NotebookExperimentPseudoDef.DATA_TYPE_NAME)
|
|
242
|
-
report_builder.add_join(records_entry_join)
|
|
243
|
-
report_builder.add_join(experiment_entry_enb_entry_join)
|
|
244
|
-
report_builder.add_join(enb_entry_experiment_join)
|
|
650
|
+
report_builder.add_join(records_entry_join, ExperimentEntryRecordPseudoDef.DATA_TYPE_NAME)
|
|
651
|
+
report_builder.add_join(experiment_entry_enb_entry_join, EnbEntryPseudoDef.DATA_TYPE_NAME)
|
|
652
|
+
report_builder.add_join(enb_entry_experiment_join, NotebookExperimentPseudoDef.DATA_TYPE_NAME)
|
|
245
653
|
return CustomReportUtil.run_custom_report(user, report_builder.build_report_criteria())
|
|
@@ -6,6 +6,7 @@ from sapiopylib.rest.pojo.DataRecord import DataRecord
|
|
|
6
6
|
from sapiopylib.rest.pojo.datatype.FieldDefinition import FieldType
|
|
7
7
|
from sapiopylib.rest.pojo.eln.ElnExperiment import ElnExperiment
|
|
8
8
|
from sapiopylib.rest.pojo.eln.ExperimentEntry import ExperimentEntry
|
|
9
|
+
from sapiopylib.rest.pojo.eln.SapioELNEnums import ElnBaseDataType
|
|
9
10
|
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
10
11
|
from sapiopylib.rest.utils.Protocols import ElnExperimentProtocol, ElnEntryStep
|
|
11
12
|
from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
|
|
@@ -89,44 +90,50 @@ class AliasUtil:
|
|
|
89
90
|
return record if isinstance(record, int) else record.record_id
|
|
90
91
|
|
|
91
92
|
@staticmethod
|
|
92
|
-
def to_data_type_name(value: DataTypeIdentifier) -> str:
|
|
93
|
+
def to_data_type_name(value: DataTypeIdentifier, convert_eln_dts: bool = True) -> str:
|
|
93
94
|
"""
|
|
94
95
|
Convert a given value to a data type name.
|
|
95
96
|
|
|
96
97
|
:param value: A value which is a string, record, or record model type.
|
|
98
|
+
:param convert_eln_dts: If true, convert ELN data types to their base data type name.
|
|
97
99
|
:return: A string of the data type name of the input value.
|
|
98
100
|
"""
|
|
99
|
-
if isinstance(value, str):
|
|
100
|
-
return value
|
|
101
101
|
if isinstance(value, SapioRecord):
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
value = value.data_type_name
|
|
103
|
+
elif not isinstance(value, str):
|
|
104
|
+
value = value.get_wrapper_data_type_name()
|
|
105
|
+
if convert_eln_dts and ElnBaseDataType.is_eln_type(value):
|
|
106
|
+
return ElnBaseDataType.get_base_type(value).data_type_name
|
|
107
|
+
return value
|
|
104
108
|
|
|
105
109
|
@staticmethod
|
|
106
|
-
def to_data_type_names(values: Iterable[DataTypeIdentifier], return_set: bool = False
|
|
110
|
+
def to_data_type_names(values: Iterable[DataTypeIdentifier], return_set: bool = False,
|
|
111
|
+
convert_eln_dts: bool = True) -> list[str] | set[str]:
|
|
107
112
|
"""
|
|
108
113
|
Convert a given iterable of values to a list or set of data type names.
|
|
109
114
|
|
|
110
115
|
:param values: An iterable of values which are strings, records, or record model types.
|
|
111
116
|
:param return_set: If true, return a set instead of a list.
|
|
117
|
+
:param convert_eln_dts: If true, convert ELN data types to their base data type name.
|
|
112
118
|
:return: A list or set of strings of the data type name of the input value.
|
|
113
119
|
"""
|
|
114
|
-
values = [AliasUtil.to_data_type_name(x) for x in values]
|
|
120
|
+
values = [AliasUtil.to_data_type_name(x, convert_eln_dts) for x in values]
|
|
115
121
|
return set(values) if return_set else values
|
|
116
122
|
|
|
117
123
|
@staticmethod
|
|
118
|
-
def to_singular_data_type_name(values: Iterable[DataTypeIdentifier]) -> str:
|
|
124
|
+
def to_singular_data_type_name(values: Iterable[DataTypeIdentifier], convert_eln_dts: bool = True) -> str:
|
|
119
125
|
"""
|
|
120
126
|
Convert a given iterable of values to a singular data type name that they share. Throws an exception if more
|
|
121
127
|
than one data type name exists in the provided list of identifiers.
|
|
122
128
|
|
|
123
129
|
:param values: An iterable of values which are strings, records, or record model types.
|
|
130
|
+
:param convert_eln_dts: If true, convert ELN data types to their base data type name.
|
|
124
131
|
:return: The single data type name that the input vales share. Returns an empty string if an empty iterable
|
|
125
132
|
was provided.
|
|
126
133
|
"""
|
|
127
134
|
if not values:
|
|
128
135
|
return ""
|
|
129
|
-
data_types: set[str] = AliasUtil.to_data_type_names(values, True)
|
|
136
|
+
data_types: set[str] = AliasUtil.to_data_type_names(values, True, convert_eln_dts)
|
|
130
137
|
if len(data_types) > 1:
|
|
131
138
|
raise SapioException(f"Provided values contain multiple data types: {data_types}. "
|
|
132
139
|
f"Only expecting a single data type.")
|
|
@@ -38,9 +38,6 @@ class PyMolecule:
|
|
|
38
38
|
normError: str | None
|
|
39
39
|
desaltError: str | None
|
|
40
40
|
desaltedList: list[str] | None
|
|
41
|
-
registrationHash: str | None
|
|
42
|
-
hasOrGroup: bool
|
|
43
|
-
CXSMILESHash: str | None
|
|
44
41
|
|
|
45
42
|
|
|
46
43
|
@dataclass
|
|
@@ -103,9 +100,9 @@ class PyMoleculeLoaderResult:
|
|
|
103
100
|
compoundList: the compounds successfully loaded.
|
|
104
101
|
errorList: an error record is added here for each one we failed to load in Sapio.
|
|
105
102
|
"""
|
|
106
|
-
compoundByStr: dict[str, PyCompound]
|
|
107
|
-
compoundList: list[PyCompound]
|
|
108
|
-
errorList: list[ChemLoadingError]
|
|
103
|
+
compoundByStr: dict[str, PyCompound]
|
|
104
|
+
compoundList: list[PyCompound]
|
|
105
|
+
errorList: list[ChemLoadingError]
|
|
109
106
|
|
|
110
107
|
|
|
111
108
|
@dataclass
|