sapiopycommons 2024.3.19a157__py3-none-any.whl → 2025.1.17a402__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/__init__.py +0 -0
- sapiopycommons/callbacks/callback_util.py +2041 -0
- sapiopycommons/callbacks/field_builder.py +545 -0
- sapiopycommons/chem/IndigoMolecules.py +46 -1
- sapiopycommons/chem/Molecules.py +100 -21
- sapiopycommons/customreport/__init__.py +0 -0
- sapiopycommons/customreport/column_builder.py +60 -0
- sapiopycommons/customreport/custom_report_builder.py +137 -0
- sapiopycommons/customreport/term_builder.py +315 -0
- sapiopycommons/datatype/attachment_util.py +14 -15
- sapiopycommons/datatype/data_fields.py +61 -0
- sapiopycommons/datatype/pseudo_data_types.py +440 -0
- sapiopycommons/eln/experiment_handler.py +355 -91
- sapiopycommons/eln/experiment_report_util.py +649 -0
- sapiopycommons/eln/plate_designer.py +152 -0
- sapiopycommons/files/complex_data_loader.py +31 -0
- sapiopycommons/files/file_bridge.py +149 -25
- sapiopycommons/files/file_bridge_handler.py +555 -0
- sapiopycommons/files/file_data_handler.py +633 -0
- sapiopycommons/files/file_util.py +263 -163
- sapiopycommons/files/file_validator.py +569 -0
- sapiopycommons/files/file_writer.py +377 -0
- sapiopycommons/flowcyto/flow_cyto.py +77 -0
- sapiopycommons/flowcyto/flowcyto_data.py +75 -0
- sapiopycommons/general/accession_service.py +375 -0
- sapiopycommons/general/aliases.py +250 -15
- sapiopycommons/general/audit_log.py +185 -0
- sapiopycommons/general/custom_report_util.py +251 -31
- sapiopycommons/general/directive_util.py +86 -0
- sapiopycommons/general/exceptions.py +69 -7
- sapiopycommons/general/popup_util.py +59 -7
- sapiopycommons/general/sapio_links.py +50 -0
- sapiopycommons/general/storage_util.py +148 -0
- sapiopycommons/general/time_util.py +91 -7
- sapiopycommons/multimodal/multimodal.py +146 -0
- sapiopycommons/multimodal/multimodal_data.py +490 -0
- sapiopycommons/processtracking/__init__.py +0 -0
- sapiopycommons/processtracking/custom_workflow_handler.py +406 -0
- sapiopycommons/processtracking/endpoints.py +192 -0
- sapiopycommons/recordmodel/record_handler.py +621 -148
- sapiopycommons/rules/eln_rule_handler.py +87 -8
- sapiopycommons/rules/on_save_rule_handler.py +87 -12
- sapiopycommons/sftpconnect/__init__.py +0 -0
- sapiopycommons/sftpconnect/sftp_builder.py +70 -0
- sapiopycommons/webhook/webhook_context.py +39 -0
- sapiopycommons/webhook/webhook_handlers.py +614 -71
- sapiopycommons/webhook/webservice_handlers.py +317 -0
- {sapiopycommons-2024.3.19a157.dist-info → sapiopycommons-2025.1.17a402.dist-info}/METADATA +5 -4
- sapiopycommons-2025.1.17a402.dist-info/RECORD +60 -0
- {sapiopycommons-2024.3.19a157.dist-info → sapiopycommons-2025.1.17a402.dist-info}/WHEEL +1 -1
- sapiopycommons-2024.3.19a157.dist-info/RECORD +0 -28
- {sapiopycommons-2024.3.19a157.dist-info → sapiopycommons-2025.1.17a402.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
from typing import Iterable
|
|
2
|
+
|
|
3
|
+
from sapiopylib.rest.User import SapioUser
|
|
4
|
+
from sapiopylib.rest.pojo.CustomReport import CustomReportCriteria
|
|
5
|
+
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
6
|
+
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
|
|
7
|
+
|
|
8
|
+
from sapiopycommons.customreport.custom_report_builder import CustomReportBuilder
|
|
9
|
+
from sapiopycommons.datatype.data_fields import ProcessQueueItemFields, SystemFields, ProcessWorkflowTrackingFields
|
|
10
|
+
from sapiopycommons.general.aliases import UserIdentifier, AliasUtil, SapioRecord
|
|
11
|
+
from sapiopycommons.general.custom_report_util import CustomReportUtil
|
|
12
|
+
from sapiopycommons.general.exceptions import SapioException
|
|
13
|
+
from sapiopycommons.general.time_util import TimeUtil
|
|
14
|
+
from sapiopycommons.recordmodel.record_handler import RecordHandler
|
|
15
|
+
from sapiopycommons.webhook.webhook_context import ProcessQueueContext
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class QueueItemReportCriteria:
|
|
19
|
+
"""
|
|
20
|
+
Queue item report criteria is used to restrict the results of searches for queue item records.
|
|
21
|
+
"""
|
|
22
|
+
process_names: list[str] | None
|
|
23
|
+
not_process_names: list[str] | None
|
|
24
|
+
step_names: list[str] | None
|
|
25
|
+
not_step_names: list[str] | None
|
|
26
|
+
data_type_names: list[str] | None
|
|
27
|
+
not_data_type_names: list[str] | None
|
|
28
|
+
data_record_ids: list[int] | None
|
|
29
|
+
not_data_record_ids: list[int] | None
|
|
30
|
+
assigned_to: list[str] | None
|
|
31
|
+
not_assigned_to: list[str] | None
|
|
32
|
+
launched_after: int | None
|
|
33
|
+
launched_before: int | None
|
|
34
|
+
scheduled_after: int | None
|
|
35
|
+
scheduled_before: int | None
|
|
36
|
+
shown_in_queue: bool | None
|
|
37
|
+
has_experiment: bool | None
|
|
38
|
+
|
|
39
|
+
def __init__(self, *,
|
|
40
|
+
process_names: list[str] | None = None,
|
|
41
|
+
not_process_names: list[str] | None = None,
|
|
42
|
+
step_names: list[str] | None = None,
|
|
43
|
+
not_step_names: list[str] | None = None,
|
|
44
|
+
data_type_names: list[str] | None = None,
|
|
45
|
+
not_data_type_names: list[str] | None = None,
|
|
46
|
+
data_record_ids: list[int] | None = None,
|
|
47
|
+
not_data_record_ids: list[int] | None = None,
|
|
48
|
+
assigned_to: list[str] | None = None,
|
|
49
|
+
not_assigned_to: list[str] | None = None,
|
|
50
|
+
launched_after: int | None = None,
|
|
51
|
+
launched_before: int | None = None,
|
|
52
|
+
scheduled_after: int | None = None,
|
|
53
|
+
scheduled_before: int | None = None,
|
|
54
|
+
shown_in_queue: bool | None = None,
|
|
55
|
+
has_experiment: bool | None = None):
|
|
56
|
+
"""
|
|
57
|
+
:param process_names: The allowed process name(s).
|
|
58
|
+
:param not_process_names: The disallowed process name(s).
|
|
59
|
+
:param step_names: The allowed step name(s).
|
|
60
|
+
:param not_step_names: The disallowed step name(s).
|
|
61
|
+
:param data_type_names: The allowed data type name(s).
|
|
62
|
+
:param not_data_type_names: The disallowed dta type name(s).
|
|
63
|
+
:param data_record_ids: The allowed record ID(s).
|
|
64
|
+
:param not_data_record_ids: The disallowed record ID(s).
|
|
65
|
+
:param assigned_to: The allowed username(s) of the user(s) that the queue items are assigned to.
|
|
66
|
+
:param not_assigned_to: The disallowed username(s) of the user(s) that the queue items are assigned to.
|
|
67
|
+
:param launched_after: A timestamp after which the queue item was launched.
|
|
68
|
+
:param launched_before: A timestamp before which the queue item was launched.
|
|
69
|
+
:param scheduled_after: A timestamp after which the queue item was scheduled.
|
|
70
|
+
:param scheduled_before: A timestamp before which the queue item was scheduled.
|
|
71
|
+
:param shown_in_queue: Whether the queue item is currently being shown in a queue.
|
|
72
|
+
:param has_experiment: Whether the queue item is linked to an experiment record.
|
|
73
|
+
"""
|
|
74
|
+
self.process_names = process_names
|
|
75
|
+
self.not_process_names = not_process_names
|
|
76
|
+
self.step_names = step_names
|
|
77
|
+
self.not_step_names = not_step_names
|
|
78
|
+
self.data_type_names = data_type_names
|
|
79
|
+
self.not_data_type_names = not_data_type_names
|
|
80
|
+
self.data_record_ids = data_record_ids
|
|
81
|
+
self.not_data_record_ids = not_data_record_ids
|
|
82
|
+
self.assigned_to = assigned_to
|
|
83
|
+
self.not_assigned_to = not_assigned_to
|
|
84
|
+
self.launched_after = launched_after
|
|
85
|
+
self.launched_before = launched_before
|
|
86
|
+
self.scheduled_after = scheduled_after
|
|
87
|
+
self.scheduled_before = scheduled_before
|
|
88
|
+
self.shown_in_queue = shown_in_queue
|
|
89
|
+
self.has_experiment = has_experiment
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class QueueItemHandler:
|
|
93
|
+
"""
|
|
94
|
+
A class used for handling the display of records in custom process queues, which are controlled in the system by
|
|
95
|
+
ProcessQueueItem data types.
|
|
96
|
+
"""
|
|
97
|
+
user: SapioUser
|
|
98
|
+
context: ProcessQueueContext | None
|
|
99
|
+
rec_handler: RecordHandler
|
|
100
|
+
|
|
101
|
+
def __init__(self, context: UserIdentifier):
|
|
102
|
+
"""
|
|
103
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
104
|
+
"""
|
|
105
|
+
self.user = AliasUtil.to_sapio_user(context)
|
|
106
|
+
self.rec_handler = RecordHandler(self.user)
|
|
107
|
+
if isinstance(context, SapioWebhookContext):
|
|
108
|
+
self.context = ProcessQueueContext(context)
|
|
109
|
+
else:
|
|
110
|
+
self.context = None
|
|
111
|
+
|
|
112
|
+
def get_process_queue_items_from_context(self, wrapper: type[WrappedType],
|
|
113
|
+
context: SapioWebhookContext | ProcessQueueContext | None = None) \
|
|
114
|
+
-> list[WrappedType]:
|
|
115
|
+
"""
|
|
116
|
+
When you launch records from a custom process queue, the process queue items related to the selected records
|
|
117
|
+
are provided as record IDs to the process queue context. Using these record IDs, query for the queue item
|
|
118
|
+
records and wrap them as record models.
|
|
119
|
+
|
|
120
|
+
:param wrapper: The record model wrapper for the process queue items.
|
|
121
|
+
:param context: If this handler was not initialized with a context object, or you wish to retrieve
|
|
122
|
+
data from a different context object than the initializing context, then provide the context to retrieve the
|
|
123
|
+
record IDs from.
|
|
124
|
+
:return: The process queue items corresponding to the record IDs from the context wrapped as record models.
|
|
125
|
+
"""
|
|
126
|
+
if context is None and self.context is not None:
|
|
127
|
+
record_ids: list[int] = self.context.process_queue_item_record_ids
|
|
128
|
+
elif context is not None:
|
|
129
|
+
if isinstance(context, SapioWebhookContext):
|
|
130
|
+
record_ids: list[int] = ProcessQueueContext(context).process_queue_item_record_ids
|
|
131
|
+
else:
|
|
132
|
+
record_ids: list[int] = context.process_queue_item_record_ids
|
|
133
|
+
else:
|
|
134
|
+
raise SapioException("A context object must be provided to this function call, as the handler "
|
|
135
|
+
"was not initialized with one.")
|
|
136
|
+
return self.rec_handler.query_models_by_id(wrapper, record_ids)
|
|
137
|
+
|
|
138
|
+
def map_records_to_queue_items(self, records: Iterable[SapioRecord], queue_items: Iterable[SapioRecord]) \
|
|
139
|
+
-> dict[SapioRecord, list[SapioRecord]]:
|
|
140
|
+
"""
|
|
141
|
+
Given a list of records and a list of queue items, create a dictionary mapping the records to the queue items
|
|
142
|
+
that refer to them.
|
|
143
|
+
|
|
144
|
+
:param records: The records to map to the queue items.
|
|
145
|
+
:param queue_items: The queue items to map to the records.
|
|
146
|
+
:return: A dictionary of record to the queue items that refer to them. Input queue items that don't refer to
|
|
147
|
+
any provided records will not be in this dictionary.
|
|
148
|
+
"""
|
|
149
|
+
ret_val: dict[SapioRecord, list[SapioRecord]] = {}
|
|
150
|
+
id_to_queue_items: dict[int, list[SapioRecord]] = self.rec_handler.map_by_field(queue_items,
|
|
151
|
+
ProcessQueueItemFields.DATA_RECORD_ID__FIELD)
|
|
152
|
+
id_to_record: dict[int, SapioRecord] = self.rec_handler.map_by_id(records)
|
|
153
|
+
for record_id, record in id_to_record.items():
|
|
154
|
+
ret_val[record] = id_to_queue_items[record_id]
|
|
155
|
+
return ret_val
|
|
156
|
+
|
|
157
|
+
def map_queue_items_to_records(self, queue_items: Iterable[SapioRecord], records: Iterable[SapioRecord]) \
|
|
158
|
+
-> dict[SapioRecord, SapioRecord]:
|
|
159
|
+
"""
|
|
160
|
+
Given a list of queue items and a list of records, create a dictionary mapping the queue items to the record
|
|
161
|
+
that they refer to.
|
|
162
|
+
|
|
163
|
+
:param queue_items: The queue items to map to the records.
|
|
164
|
+
:param records: The records to map to the queue items.
|
|
165
|
+
:return: A dictionary of queue items to the records that the refer to. Input record that aren't referred to by
|
|
166
|
+
any provided queue items will not be in this dictionary.
|
|
167
|
+
"""
|
|
168
|
+
ret_val: dict[SapioRecord, SapioRecord] = {}
|
|
169
|
+
id_to_queue_items: dict[int, list[SapioRecord]] = self.rec_handler.map_by_field(queue_items,
|
|
170
|
+
ProcessQueueItemFields.DATA_RECORD_ID__FIELD)
|
|
171
|
+
id_to_record: dict[int, SapioRecord] = self.rec_handler.map_by_id(records)
|
|
172
|
+
for record_id, queue_items in id_to_queue_items.items():
|
|
173
|
+
record: SapioRecord = id_to_record[record_id]
|
|
174
|
+
for queue_item in queue_items:
|
|
175
|
+
ret_val[queue_item] = record
|
|
176
|
+
return ret_val
|
|
177
|
+
|
|
178
|
+
def get_queue_items_from_report(self, wrapper: type[WrappedType], criteria: QueueItemReportCriteria) \
|
|
179
|
+
-> list[WrappedType]:
|
|
180
|
+
"""
|
|
181
|
+
Run a custom report that retrieves every queue item in the system for the given search criteria.
|
|
182
|
+
|
|
183
|
+
:param wrapper: The record model wrapper for the process queue items.
|
|
184
|
+
:param criteria: The search criteria to query for queue items with.
|
|
185
|
+
:return: A list of every queue item in the system that matches the search criteria.
|
|
186
|
+
"""
|
|
187
|
+
report = self.build_queue_item_report(criteria)
|
|
188
|
+
return self.rec_handler.query_models_by_report(wrapper, report)
|
|
189
|
+
|
|
190
|
+
def get_records_from_item_report(self, wrapper: type[WrappedType],
|
|
191
|
+
criteria: QueueItemReportCriteria = QueueItemReportCriteria()) -> list[WrappedType]:
|
|
192
|
+
"""
|
|
193
|
+
Run a custom report that retrieves for queue items that match the given search criteria, then query for the
|
|
194
|
+
data records that those queue items refer to.
|
|
195
|
+
|
|
196
|
+
:param wrapper: The record model wrapper for the records being queried for.
|
|
197
|
+
:param criteria: Additional search criteria to filter the results. This function forces the data_type_names
|
|
198
|
+
parameter of the criteria to match the data type of the given record model wrapper.
|
|
199
|
+
:return: A list of all records related to the queue items in the system that match the search criteria.
|
|
200
|
+
"""
|
|
201
|
+
# Don't try to query for process queue items that don't match the data type of this wrapper.
|
|
202
|
+
criteria.data_type_names = [wrapper.get_wrapper_data_type_name()]
|
|
203
|
+
criteria.not_data_type_names = None
|
|
204
|
+
report = self.build_queue_item_report(criteria)
|
|
205
|
+
record_ids: list[int] = [x[ProcessQueueItemFields.DATA_RECORD_ID__FIELD.field_name]
|
|
206
|
+
for x in CustomReportUtil.run_custom_report(self.user, report)]
|
|
207
|
+
return self.rec_handler.query_models_by_id(wrapper, record_ids)
|
|
208
|
+
|
|
209
|
+
def get_queue_items_for_records(self, records: Iterable[SapioRecord], wrapper: type[WrappedType],
|
|
210
|
+
criteria: QueueItemReportCriteria = QueueItemReportCriteria()) \
|
|
211
|
+
-> dict[SapioRecord, list[WrappedType]]:
|
|
212
|
+
"""
|
|
213
|
+
Given a list of records, query the system for every process queue item that refers to those records and matches
|
|
214
|
+
the provided search criteria.
|
|
215
|
+
|
|
216
|
+
:param records: The queued records to query for the process queue items of.
|
|
217
|
+
:param wrapper: The record model wrapper for the returned process queue item records.
|
|
218
|
+
:param criteria: Additional search criteria to filter the results. This function forces the data_record_ids and
|
|
219
|
+
data_type_names parameters on the criteria to match the given records.
|
|
220
|
+
:return: A dictionary mapping the input records to a list of the process queue items that refer to them. If a
|
|
221
|
+
record does not have any queue items that refer to it that match the given search criteria, then it will
|
|
222
|
+
map to an empty list.
|
|
223
|
+
"""
|
|
224
|
+
# Query for only those process queue items that have a record ID from the provided records.
|
|
225
|
+
criteria.data_record_ids = AliasUtil.to_record_ids(records)
|
|
226
|
+
criteria.not_data_record_ids = None
|
|
227
|
+
criteria.data_type_names = AliasUtil.to_data_type_names(records)
|
|
228
|
+
criteria.not_data_type_names = None
|
|
229
|
+
items: list[WrappedType] = self.get_queue_items_from_report(wrapper, criteria)
|
|
230
|
+
return self.map_records_to_queue_items(records, items)
|
|
231
|
+
|
|
232
|
+
def get_records_for_queue_items(self, queue_items: Iterable[SapioRecord], wrapper: type[WrappedType]) \
|
|
233
|
+
-> dict[SapioRecord, WrappedType]:
|
|
234
|
+
"""
|
|
235
|
+
Given a list of process queue items, query the system for the records that those queue items refer to.
|
|
236
|
+
|
|
237
|
+
:param queue_items: The process queue items to query for the referenced records of.
|
|
238
|
+
:param wrapper: The record model wrapper for the records being queried.
|
|
239
|
+
:return: A dictionary mapping the input process queue items to the record tht they refer to.
|
|
240
|
+
"""
|
|
241
|
+
record_ids: set[int] = {x.get_field_value(ProcessQueueItemFields.DATA_RECORD_ID__FIELD) for x in queue_items}
|
|
242
|
+
records: list[WrappedType] = self.rec_handler.query_models_by_id(wrapper, record_ids)
|
|
243
|
+
return self.map_queue_items_to_records(queue_items, records)
|
|
244
|
+
|
|
245
|
+
def queue_records_for_process(self, records: Iterable[SapioRecord], process: str, step: str,
|
|
246
|
+
wrapper: type[WrappedType]) -> dict[SapioRecord, WrappedType]:
|
|
247
|
+
"""
|
|
248
|
+
Given a list of records, create process queue item records for them at the provided process and step names.
|
|
249
|
+
You must store and commit using the record model manager in order for these changes to take effect.
|
|
250
|
+
|
|
251
|
+
:param records: The records to create process queue items for.
|
|
252
|
+
:param wrapper: The record model wrapper for the process queue items being created.
|
|
253
|
+
:param process: The name of the process to queue for.
|
|
254
|
+
:param step: The name of the step in the above process to queue for. This is the "Workflow Name" field of the
|
|
255
|
+
Process Workflow record corresponding to the step you want to assign these records to. For steps that
|
|
256
|
+
launch an experiment, this is the name of the template that will be launched. For the other types of custom
|
|
257
|
+
process steps, this is the "Workflow Name" as defined in the process manager config.
|
|
258
|
+
:return: A dictionary mapping each input record to the newly created process queue item for that record.
|
|
259
|
+
"""
|
|
260
|
+
ret_val: dict[SapioRecord, WrappedType] = {}
|
|
261
|
+
for record in records:
|
|
262
|
+
item = self.rec_handler.add_model(wrapper)
|
|
263
|
+
item.set_field_values({
|
|
264
|
+
ProcessQueueItemFields.PROCESS_HEADER_NAME__FIELD.field_name: process,
|
|
265
|
+
ProcessQueueItemFields.WORKFLOW_HEADER_NAME__FIELD.field_name: step,
|
|
266
|
+
ProcessQueueItemFields.SHOW_IN_QUEUE__FIELD.field_name: True,
|
|
267
|
+
ProcessQueueItemFields.SCHEDULED_DATE__FIELD.field_name: TimeUtil.now_in_millis(),
|
|
268
|
+
ProcessQueueItemFields.DATA_RECORD_ID__FIELD.field_name: AliasUtil.to_record_id(record),
|
|
269
|
+
ProcessQueueItemFields.DATA_TYPE_NAME__FIELD.field_name: AliasUtil.to_data_type_name(record)
|
|
270
|
+
})
|
|
271
|
+
ret_val[record] = item
|
|
272
|
+
return ret_val
|
|
273
|
+
|
|
274
|
+
def dequeue_records_for_process(self, records: Iterable[SapioRecord], wrapper: type[WrappedType],
|
|
275
|
+
criteria: QueueItemReportCriteria = QueueItemReportCriteria()) \
|
|
276
|
+
-> dict[SapioRecord, list[WrappedType]]:
|
|
277
|
+
"""
|
|
278
|
+
Given a list of records, locate the process queue items that refer to them and that match the given search
|
|
279
|
+
criteria and remove them from the queue by setting the ShowInQueue field on the process queue items to false.
|
|
280
|
+
You must store and commit using the record model manager in order for these changes to take effect.
|
|
281
|
+
|
|
282
|
+
:param records: The records to remove from the queue.
|
|
283
|
+
:param wrapper: The record model wrapper for the process queue items being updated.
|
|
284
|
+
:param criteria: Additional search criteria to filter the results. This function forces the show_in_queue
|
|
285
|
+
parameter on the criteria to True.
|
|
286
|
+
:return: A dictionary mapping each input record to the queue item records that refer to that record and were
|
|
287
|
+
updated. If a record does not have any queue items that refer to it that match the given search criteria,
|
|
288
|
+
then it will map to an empty list.
|
|
289
|
+
"""
|
|
290
|
+
# Only locate queue items that are currently visible in the queue.
|
|
291
|
+
criteria.shown_in_queue = True
|
|
292
|
+
dequeue: dict[SapioRecord, list[WrappedType]] = self.get_queue_items_for_records(records, wrapper, criteria)
|
|
293
|
+
for record, items in dequeue.items():
|
|
294
|
+
for item in items:
|
|
295
|
+
item.set_field_value(ProcessQueueItemFields.SHOW_IN_QUEUE__FIELD.field_name, False)
|
|
296
|
+
return dequeue
|
|
297
|
+
|
|
298
|
+
@staticmethod
|
|
299
|
+
def assigned_queue_items(queue_items: Iterable[SapioRecord], assign_to: list[str]) -> None:
|
|
300
|
+
"""
|
|
301
|
+
Given a collection of queue items, assign them to a list of usernames or group names. Only those users in the
|
|
302
|
+
listed groups or those users with the matching username will be able to see these related records in the process
|
|
303
|
+
queue.
|
|
304
|
+
You must store and commit using the record model manager in order for these changes to take effect.
|
|
305
|
+
|
|
306
|
+
:param queue_items: The queue items to assign.
|
|
307
|
+
:param assign_to: A list of usernames and/or group names to assign these items to.
|
|
308
|
+
"""
|
|
309
|
+
for item in queue_items:
|
|
310
|
+
item.set_field_value(ProcessQueueItemFields.ASSIGNED_TO__FIELD, ",".join(assign_to))
|
|
311
|
+
|
|
312
|
+
def get_queue_item_workflow_trackers(self, items: Iterable[SapioRecord], wrapper: type[WrappedType]) \
|
|
313
|
+
-> dict[SapioRecord, list[WrappedType]]:
|
|
314
|
+
"""
|
|
315
|
+
When a queue item is launched into a process step, a ProcessWorkflowTracking record is created by the system
|
|
316
|
+
with a side link to the process queue item. This record records information about how long the record ws in
|
|
317
|
+
the queue, among other details.
|
|
318
|
+
|
|
319
|
+
Retrieve the workflow tracker records for the input queue items.
|
|
320
|
+
|
|
321
|
+
:param items: The queue items to load the workflow trackers of.
|
|
322
|
+
:param wrapper: The record model wrapper for the ProcessWorkflowTracking records.
|
|
323
|
+
:return: A dictionary mapping each queue item to the workflow trackers that side link to that item. If an input
|
|
324
|
+
queue item doesn't have any workflow trackers, then it will map to an empty list.
|
|
325
|
+
"""
|
|
326
|
+
field: str = ProcessWorkflowTrackingFields.PROCESS_QUEUE_ITEM__FIELD.field_name
|
|
327
|
+
self.rec_handler.rel_man.load_reverse_side_links_of_type(list(items), wrapper, field)
|
|
328
|
+
return self.rec_handler.map_to_reverse_side_links(items, field, wrapper)
|
|
329
|
+
|
|
330
|
+
@staticmethod
|
|
331
|
+
def build_queue_item_report(criteria: QueueItemReportCriteria) -> CustomReportCriteria:
|
|
332
|
+
"""
|
|
333
|
+
Construct a custom report using the provided QueueItemReportCriteria.
|
|
334
|
+
|
|
335
|
+
:param criteria: The criteria to construct a custom report from.
|
|
336
|
+
:return: A custom report that can be used to search for queue items that match the given criteria.
|
|
337
|
+
"""
|
|
338
|
+
dt: str = ProcessQueueItemFields.DATA_TYPE_NAME
|
|
339
|
+
report_builder = CustomReportBuilder(dt)
|
|
340
|
+
tb = report_builder.get_term_builder()
|
|
341
|
+
report_builder.add_column(SystemFields.RECORD_ID__FIELD)
|
|
342
|
+
report_builder.add_column(ProcessQueueItemFields.DATA_RECORD_ID__FIELD)
|
|
343
|
+
|
|
344
|
+
root = tb.all_records_term()
|
|
345
|
+
|
|
346
|
+
if criteria.process_names is not None:
|
|
347
|
+
term = tb.is_term(ProcessQueueItemFields.PROCESS_HEADER_NAME__FIELD, criteria.process_names)
|
|
348
|
+
root = tb.and_terms(root, term)
|
|
349
|
+
if criteria.not_process_names is not None:
|
|
350
|
+
term = tb.not_term(ProcessQueueItemFields.PROCESS_HEADER_NAME__FIELD, criteria.not_process_names)
|
|
351
|
+
root = tb.and_terms(root, term)
|
|
352
|
+
|
|
353
|
+
if criteria.step_names is not None:
|
|
354
|
+
term = tb.is_term(ProcessQueueItemFields.WORKFLOW_HEADER_NAME__FIELD, criteria.step_names)
|
|
355
|
+
root = tb.and_terms(root, term)
|
|
356
|
+
if criteria.not_step_names is not None:
|
|
357
|
+
term = tb.not_term(ProcessQueueItemFields.WORKFLOW_HEADER_NAME__FIELD, criteria.not_step_names)
|
|
358
|
+
root = tb.and_terms(root, term)
|
|
359
|
+
|
|
360
|
+
if criteria.data_type_names is not None:
|
|
361
|
+
term = tb.is_term(ProcessQueueItemFields.DATA_TYPE_NAME__FIELD, criteria.data_type_names)
|
|
362
|
+
root = tb.and_terms(root, term)
|
|
363
|
+
if criteria.not_data_type_names is not None:
|
|
364
|
+
term = tb.not_term(ProcessQueueItemFields.DATA_TYPE_NAME__FIELD, criteria.not_data_type_names)
|
|
365
|
+
root = tb.and_terms(root, term)
|
|
366
|
+
|
|
367
|
+
if criteria.data_record_ids is not None:
|
|
368
|
+
term = tb.is_term(ProcessQueueItemFields.DATA_RECORD_ID__FIELD, criteria.data_record_ids)
|
|
369
|
+
root = tb.and_terms(root, term)
|
|
370
|
+
if criteria.not_data_record_ids is not None:
|
|
371
|
+
term = tb.not_term(ProcessQueueItemFields.DATA_RECORD_ID__FIELD, criteria.not_data_record_ids)
|
|
372
|
+
root = tb.and_terms(root, term)
|
|
373
|
+
|
|
374
|
+
if criteria.assigned_to is not None:
|
|
375
|
+
term = tb.is_term(ProcessQueueItemFields.ASSIGNED_TO__FIELD, criteria.assigned_to)
|
|
376
|
+
root = tb.and_terms(root, term)
|
|
377
|
+
if criteria.not_assigned_to is not None:
|
|
378
|
+
term = tb.not_term(ProcessQueueItemFields.ASSIGNED_TO__FIELD, criteria.not_assigned_to)
|
|
379
|
+
root = tb.and_terms(root, term)
|
|
380
|
+
|
|
381
|
+
if criteria.launched_after is not None:
|
|
382
|
+
term = tb.gte_term(ProcessQueueItemFields.LAUNCHED_DATE__FIELD, criteria.launched_after)
|
|
383
|
+
root = tb.and_terms(root, term)
|
|
384
|
+
if criteria.launched_before is not None:
|
|
385
|
+
term = tb.lte_term(ProcessQueueItemFields.LAUNCHED_DATE__FIELD, criteria.launched_before)
|
|
386
|
+
root = tb.and_terms(root, term)
|
|
387
|
+
|
|
388
|
+
if criteria.scheduled_after is not None:
|
|
389
|
+
term = tb.gte_term(ProcessQueueItemFields.LAUNCHED_DATE__FIELD, criteria.scheduled_after)
|
|
390
|
+
root = tb.and_terms(root, term)
|
|
391
|
+
if criteria.scheduled_before is not None:
|
|
392
|
+
term = tb.lte_term(ProcessQueueItemFields.LAUNCHED_DATE__FIELD, criteria.scheduled_before)
|
|
393
|
+
root = tb.and_terms(root, term)
|
|
394
|
+
|
|
395
|
+
if criteria.shown_in_queue is not None:
|
|
396
|
+
term = tb.is_term(ProcessQueueItemFields.SHOW_IN_QUEUE__FIELD, criteria.shown_in_queue)
|
|
397
|
+
root = tb.and_terms(root, term)
|
|
398
|
+
if criteria.has_experiment is not None:
|
|
399
|
+
if criteria.has_experiment:
|
|
400
|
+
term = tb.not_term(ProcessQueueItemFields.EXPERIMENT__FIELD, None)
|
|
401
|
+
else:
|
|
402
|
+
term = tb.is_term(ProcessQueueItemFields.EXPERIMENT__FIELD, None)
|
|
403
|
+
root = tb.and_terms(root, term)
|
|
404
|
+
|
|
405
|
+
report_builder.set_root_term(root)
|
|
406
|
+
return report_builder.build_report_criteria()
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
from sapiopylib.rest.User import SapioUser
|
|
2
|
+
|
|
3
|
+
from sapiopycommons.general.aliases import RecordIdentifier, AliasUtil, ExperimentIdentifier, DataTypeIdentifier, \
|
|
4
|
+
UserIdentifier
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ProcessTracking:
|
|
8
|
+
@staticmethod
|
|
9
|
+
def assign_to_process(context: UserIdentifier, data_type: DataTypeIdentifier, records: list[RecordIdentifier],
|
|
10
|
+
process_name: str, step_number: int | None = None, branch_id: int | None = None,
|
|
11
|
+
request: RecordIdentifier | None = None) -> None:
|
|
12
|
+
"""
|
|
13
|
+
Assign the given tracked records to a new process at the specified step, settings its status to "Ready for -"
|
|
14
|
+
at that step.
|
|
15
|
+
Synonymous with what occurs during request creation or when using the assign to process button.
|
|
16
|
+
|
|
17
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
18
|
+
:param data_type: The data type of the tracked records.
|
|
19
|
+
:param records: A list of the tracked records.
|
|
20
|
+
:param process_name: The name of the process that the tracked records should start at.
|
|
21
|
+
:param step_number: The step number that the tracked records should start at. If not provided, assumes step
|
|
22
|
+
number 1.
|
|
23
|
+
:param branch_id: The branch ID of the above step. Only necessary to provide if multiple steps in the process
|
|
24
|
+
share the same step number.
|
|
25
|
+
:param request: The request that the tracked records are a part of, to be used for the assigned process record's
|
|
26
|
+
request record ID field. If none is provided, creates a new request with default fields.
|
|
27
|
+
"""
|
|
28
|
+
sub_path = '/ext/process-tracking/assign-to-process'
|
|
29
|
+
payload = {
|
|
30
|
+
"data-type-name": AliasUtil.to_data_type_name(data_type),
|
|
31
|
+
"record-ids": AliasUtil.to_record_ids(records),
|
|
32
|
+
"process-name": process_name,
|
|
33
|
+
"step-number": step_number,
|
|
34
|
+
"branch-id": branch_id,
|
|
35
|
+
"request-record-id": AliasUtil.to_record_ids([request])[0] if request is not None else None
|
|
36
|
+
}
|
|
37
|
+
user: SapioUser = AliasUtil.to_sapio_user(context)
|
|
38
|
+
response = user.post(sub_path, payload=payload)
|
|
39
|
+
user.raise_for_status(response)
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def begin_protocol(context: UserIdentifier, data_type: DataTypeIdentifier, records: list[RecordIdentifier],
|
|
43
|
+
experiment: ExperimentIdentifier) -> None:
|
|
44
|
+
"""
|
|
45
|
+
Begin the assigned processes of the given tracked records as the given experiment. This sets the status of the
|
|
46
|
+
tracked records from "Ready for -" to "In Process -" for their current step.
|
|
47
|
+
Synonymous with what occurs when starting a process step in the system.
|
|
48
|
+
|
|
49
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
50
|
+
:param data_type: The data type of the tracked records.
|
|
51
|
+
:param records: A list of the tracked records.
|
|
52
|
+
:param experiment: The experiment that the tracked records are beginning the process for. This must be the next
|
|
53
|
+
step in the current process of the tracked records.
|
|
54
|
+
"""
|
|
55
|
+
sub_path = '/ext/process-tracking/begin-protocol'
|
|
56
|
+
payload = {
|
|
57
|
+
"data-type-name": AliasUtil.to_data_type_name(data_type),
|
|
58
|
+
"record-ids": AliasUtil.to_record_ids(records),
|
|
59
|
+
"experiment-id": AliasUtil.to_notebook_id(experiment),
|
|
60
|
+
}
|
|
61
|
+
user: SapioUser = AliasUtil.to_sapio_user(context)
|
|
62
|
+
response = user.post(sub_path, payload=payload)
|
|
63
|
+
user.raise_for_status(response)
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def complete_protocol(context: UserIdentifier, data_type: DataTypeIdentifier, records: list[RecordIdentifier],
|
|
67
|
+
experiment: ExperimentIdentifier) -> None:
|
|
68
|
+
"""
|
|
69
|
+
Complete the current step that the given tracked records are at given the experiment.
|
|
70
|
+
Moves the status to "Ready for -" for the next step in the process, or "Completed -" if this was the last
|
|
71
|
+
step.
|
|
72
|
+
Moves the status to "Failed -" if the failed flag for the tracked record is true.
|
|
73
|
+
Moves the assigned process down to the descendant sample(s) if both samples are provided in the records list.
|
|
74
|
+
Synonymous with what occurs when completing an experiment in the system.
|
|
75
|
+
|
|
76
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
77
|
+
:param data_type: The data type of the tracked records.
|
|
78
|
+
:param records: A list of the tracked records.
|
|
79
|
+
:param experiment: The experiment that the tracked records are currently in and completing.
|
|
80
|
+
"""
|
|
81
|
+
sub_path = '/ext/process-tracking/complete-protocol'
|
|
82
|
+
payload = {
|
|
83
|
+
"data-type-name": AliasUtil.to_data_type_name(data_type),
|
|
84
|
+
"record-ids": AliasUtil.to_record_ids(records),
|
|
85
|
+
"experiment-id": AliasUtil.to_notebook_id(experiment),
|
|
86
|
+
}
|
|
87
|
+
user: SapioUser = AliasUtil.to_sapio_user(context)
|
|
88
|
+
response = user.post(sub_path, payload=payload)
|
|
89
|
+
user.raise_for_status(response)
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
92
|
+
def fail(context: UserIdentifier, data_type: DataTypeIdentifier, records: list[RecordIdentifier],
|
|
93
|
+
experiment: ExperimentIdentifier) -> None:
|
|
94
|
+
"""
|
|
95
|
+
Fail the assigned processes of the given tracked records, changing their statuses to "Failed -". The tracked
|
|
96
|
+
records must be "In Process -" for the given step.
|
|
97
|
+
Synonymous with what occurs when failing a sample due to a QC failure in a process in the system.
|
|
98
|
+
|
|
99
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
100
|
+
:param data_type: The data type of the tracked records.
|
|
101
|
+
:param records: A list of the tracked records.
|
|
102
|
+
:param experiment: The experiment that the tracked records are currently in.
|
|
103
|
+
"""
|
|
104
|
+
sub_path = '/ext/process-tracking/fail'
|
|
105
|
+
payload = {
|
|
106
|
+
"data-type-name": AliasUtil.to_data_type_name(data_type),
|
|
107
|
+
"record-ids": AliasUtil.to_record_ids(records),
|
|
108
|
+
"experiment-id": AliasUtil.to_notebook_id(experiment),
|
|
109
|
+
}
|
|
110
|
+
user: SapioUser = AliasUtil.to_sapio_user(context)
|
|
111
|
+
response = user.post(sub_path, payload=payload)
|
|
112
|
+
user.raise_for_status(response)
|
|
113
|
+
|
|
114
|
+
@staticmethod
|
|
115
|
+
def promote_to_next_by_experiment(context: UserIdentifier, data_type: DataTypeIdentifier,
|
|
116
|
+
records: list[RecordIdentifier], experiment: ExperimentIdentifier) -> None:
|
|
117
|
+
"""
|
|
118
|
+
Promote the status of the given tracked records to the next status in their process using an experiment.
|
|
119
|
+
If the tracked records currently have a status of "Ready for -", then providing an experiment of the template
|
|
120
|
+
they are ready for will move their status to "In Process -" for the experiment.
|
|
121
|
+
If the tracked records currently have a status of "In Process -", then providing their current experiment
|
|
122
|
+
will move their status to "Ready for -" for the next step in the process, or "Completed -" if this was the
|
|
123
|
+
last step.
|
|
124
|
+
|
|
125
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
126
|
+
:param data_type: The data type of the tracked records.
|
|
127
|
+
:param records: A list of the tracked records.
|
|
128
|
+
:param experiment: The experiment that the tracked records are currently in.
|
|
129
|
+
"""
|
|
130
|
+
sub_path = '/ext/process-tracking/promote-status-to-next'
|
|
131
|
+
payload = {
|
|
132
|
+
"data-type-name": AliasUtil.to_data_type_name(data_type),
|
|
133
|
+
"record-ids": AliasUtil.to_record_ids(records),
|
|
134
|
+
"experiment-id": AliasUtil.to_notebook_id(experiment),
|
|
135
|
+
}
|
|
136
|
+
user: SapioUser = AliasUtil.to_sapio_user(context)
|
|
137
|
+
response = user.post(sub_path, payload=payload)
|
|
138
|
+
user.raise_for_status(response)
|
|
139
|
+
|
|
140
|
+
@staticmethod
|
|
141
|
+
def promote_to_next_by_step(context: UserIdentifier, data_type: DataTypeIdentifier,
|
|
142
|
+
records: list[RecordIdentifier], process_name: str, step_number: int,
|
|
143
|
+
branch_id: int | None = None) -> None:
|
|
144
|
+
"""
|
|
145
|
+
Promote the status of the given tracked records to the next status in their process using step info.
|
|
146
|
+
If the tracked records currently have a status of "Ready for -", then providing the step info for their current
|
|
147
|
+
step will move their status to "In Process -" for that step.
|
|
148
|
+
If the tracked records currently have a status of "In Process -", then providing the step info for their current
|
|
149
|
+
step will move their status to "Ready for -" for the next step in the process, or "Completed -" if this was the
|
|
150
|
+
last step.
|
|
151
|
+
|
|
152
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
153
|
+
:param data_type: The data type of the tracked records.
|
|
154
|
+
:param records: A list of the tracked records.
|
|
155
|
+
:param process_name: The name of the process that the tracked records are currently in.
|
|
156
|
+
:param step_number: The step number that the tracked records are currently in.
|
|
157
|
+
:param branch_id: The branch ID of the above step. Only necessary to provide if multiple steps in the process
|
|
158
|
+
share the same step number.
|
|
159
|
+
"""
|
|
160
|
+
sub_path = '/ext/process-tracking/promote-status-to-next'
|
|
161
|
+
payload = {
|
|
162
|
+
"data-type-name": AliasUtil.to_data_type_name(data_type),
|
|
163
|
+
"record-ids": AliasUtil.to_record_ids(records),
|
|
164
|
+
"current-process-status": {
|
|
165
|
+
"process-name": process_name,
|
|
166
|
+
"step-number": step_number,
|
|
167
|
+
"branch-id": branch_id
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
user: SapioUser = AliasUtil.to_sapio_user(context)
|
|
171
|
+
response = user.post(sub_path, payload=payload)
|
|
172
|
+
user.raise_for_status(response)
|
|
173
|
+
|
|
174
|
+
@staticmethod
|
|
175
|
+
def reprocess(context: UserIdentifier, records: list[RecordIdentifier]) -> None:
|
|
176
|
+
"""
|
|
177
|
+
Reprocess tracked records to a previous step in their process. Reprocessing is controlled by ReturnPoint records
|
|
178
|
+
which are children of the AssignedProcess on the tracked records. Creates a new AssignedProcess record for the
|
|
179
|
+
effected tracked records. Any existing AssignedProcess records are untouched.
|
|
180
|
+
Synonymous with what occurs when reprocessing records to a previous step due to QC failures in a process in the
|
|
181
|
+
system.
|
|
182
|
+
|
|
183
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
184
|
+
:param records: A list of ReturnPoint records to reprocess to.
|
|
185
|
+
"""
|
|
186
|
+
sub_path = '/ext/process-tracking/reprocess'
|
|
187
|
+
payload = {
|
|
188
|
+
"record-ids": AliasUtil.to_record_ids(records)
|
|
189
|
+
}
|
|
190
|
+
user: SapioUser = AliasUtil.to_sapio_user(context)
|
|
191
|
+
response = user.post(sub_path, payload=payload)
|
|
192
|
+
user.raise_for_status(response)
|