sapiopycommons 2025.2.6a421__py3-none-any.whl → 2025.2.7a424__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.

Files changed (27) hide show
  1. sapiopycommons/callbacks/callback_util.py +363 -1217
  2. sapiopycommons/chem/Molecules.py +2 -0
  3. sapiopycommons/datatype/data_fields.py +1 -1
  4. sapiopycommons/eln/experiment_handler.py +1 -2
  5. sapiopycommons/eln/experiment_report_util.py +7 -7
  6. sapiopycommons/files/file_bridge.py +0 -76
  7. sapiopycommons/files/file_bridge_handler.py +110 -325
  8. sapiopycommons/files/file_data_handler.py +2 -2
  9. sapiopycommons/files/file_validator.py +5 -6
  10. sapiopycommons/flowcyto/flow_cyto.py +1 -1
  11. sapiopycommons/general/accession_service.py +1 -1
  12. sapiopycommons/general/aliases.py +27 -40
  13. sapiopycommons/general/audit_log.py +2 -2
  14. sapiopycommons/general/custom_report_util.py +1 -24
  15. sapiopycommons/general/exceptions.py +2 -41
  16. sapiopycommons/multimodal/multimodal.py +0 -1
  17. sapiopycommons/processtracking/custom_workflow_handler.py +3 -3
  18. sapiopycommons/recordmodel/record_handler.py +3 -5
  19. sapiopycommons/webhook/webhook_handlers.py +55 -445
  20. {sapiopycommons-2025.2.6a421.dist-info → sapiopycommons-2025.2.7a424.dist-info}/METADATA +1 -1
  21. {sapiopycommons-2025.2.6a421.dist-info → sapiopycommons-2025.2.7a424.dist-info}/RECORD +23 -27
  22. sapiopycommons/customreport/auto_pagers.py +0 -270
  23. sapiopycommons/elain/__init__.py +0 -0
  24. sapiopycommons/elain/tool_of_tools.py +0 -510
  25. sapiopycommons/general/directive_util.py +0 -86
  26. {sapiopycommons-2025.2.6a421.dist-info → sapiopycommons-2025.2.7a424.dist-info}/WHEEL +0 -0
  27. {sapiopycommons-2025.2.6a421.dist-info → sapiopycommons-2025.2.7a424.dist-info}/licenses/LICENSE +0 -0
@@ -1,510 +0,0 @@
1
- import base64
2
- import math
3
- from typing import Final, Mapping, Any
4
-
5
- from pandas import DataFrame
6
- from sapiopylib.rest.DataRecordManagerService import DataRecordManager
7
- from sapiopylib.rest.ELNService import ElnManager
8
- from sapiopylib.rest.User import SapioUser
9
- from sapiopylib.rest.pojo.DataRecord import DataRecord
10
- from sapiopylib.rest.pojo.chartdata.DashboardDefinition import GaugeChartDefinition
11
- from sapiopylib.rest.pojo.chartdata.DashboardEnums import ChartGroupingType, ChartOperationType, ChartType
12
- from sapiopylib.rest.pojo.chartdata.DashboardSeries import GaugeChartSeries
13
- from sapiopylib.rest.pojo.datatype.FieldDefinition import AbstractVeloxFieldDefinition, FieldType
14
- from sapiopylib.rest.pojo.eln.ElnExperiment import ElnExperiment
15
- from sapiopylib.rest.pojo.eln.ExperimentEntry import ExperimentEntry
16
- from sapiopylib.rest.pojo.eln.ExperimentEntryCriteria import ElnEntryCriteria, ElnFormEntryUpdateCriteria, \
17
- ElnDashboardEntryUpdateCriteria, AbstractElnEntryUpdateCriteria
18
- from sapiopylib.rest.pojo.eln.SapioELNEnums import ElnEntryType, ElnBaseDataType
19
- from sapiopylib.rest.pojo.eln.eln_headings import ElnExperimentTabAddCriteria, ElnExperimentTab
20
- from sapiopylib.rest.pojo.eln.field_set import ElnFieldSetInfo
21
- from sapiopylib.rest.utils.ProtocolUtils import ELNStepFactory
22
- from sapiopylib.rest.utils.Protocols import ElnEntryStep, ElnExperimentProtocol
23
-
24
- from sapiopycommons.callbacks.field_builder import FieldBuilder
25
- from sapiopycommons.general.exceptions import SapioException
26
- from sapiopycommons.general.time_util import TimeUtil
27
-
28
- CREDENTIALS_HEADER: Final[str] = "SAPIO_APP_API_KEY"
29
- API_URL_HEADER: Final[str] = "SAPIO_APP_API_URL"
30
- EXP_ID_HEADER: Final[str] = "EXPERIMENT_ID"
31
- TAB_PREFIX_HEADER: Final[str] = "TAB_PREFIX"
32
-
33
-
34
- # FR-47422: Create utility methods to assist the tool of tools.
35
- def create_tot_headers(url: str, username: str, password: str, experiment_id: int, tab_prefix: str) \
36
- -> tuple[str, dict[str, str]]:
37
- """
38
- Create the headers to be passed to a tool of tools endpoint.
39
-
40
- :param url: The webservice URL of the system to make the changes in.
41
- :param username: The username of the user making the changes.
42
- :param password: The password of the user making the changes.
43
- :param experiment_id: The ID of the experiment to make the changes in.
44
- :param tab_prefix: The prefix to use for the tab name that will be created by the tool.
45
- :return: The encoded credentials and the headers to be passed to the endpoint.
46
- """
47
- # Combine the credentials into the format "username:password"
48
- credentials: str = f"{username}:{password}"
49
- # Encode the credentials to bytes, then encode them using base64,
50
- # and finally convert the result back into a string.
51
- encoded_credentials: str = base64.b64encode(credentials.encode('utf-8')).decode('utf-8')
52
- headers: dict[str, str] = {
53
- CREDENTIALS_HEADER: f"Basic {encoded_credentials}",
54
- API_URL_HEADER: url,
55
- EXP_ID_HEADER: str(experiment_id),
56
- TAB_PREFIX_HEADER: tab_prefix
57
- }
58
- return encoded_credentials, headers
59
-
60
-
61
- def create_user_from_tot_headers(headers: Mapping[str, str]) -> SapioUser:
62
- """
63
- Create a SapioUser object from the headers passed to a tool of tools endpoint.
64
-
65
- :param headers: The headers that were passed to the endpoint.
66
- :return: A SapioUser object created from the headers that can be used to communicate with the Sapio server.
67
- """
68
- headers: dict[str, str] = format_tot_headers(headers)
69
- credentials = (base64.b64decode(headers[CREDENTIALS_HEADER.lower()].removeprefix("Basic "))
70
- .decode("utf-8").split(":", 1))
71
- return SapioUser(headers[API_URL_HEADER.lower()], username=credentials[0], password=credentials[1])
72
-
73
-
74
- def format_tot_headers(headers: Mapping[str, str]) -> dict[str, str]:
75
- """
76
- Format the headers passed to a tool of tools endpoint to guarantee that the keys are lowercase.
77
-
78
- :param headers: The headers that were passed to the endpoint.
79
- :return: The headers with all keys converted to lowercase. (Conflicting keys will cause one to overwrite the other,
80
- but there should not be any conflicting keys in the headers passed to a tool of tools endpoint.)
81
- """
82
- return {k.lower(): v for k, v in headers.items()}
83
-
84
-
85
- def create_experiment_details_from_data_frame(user: SapioUser,
86
- exp_id: int,
87
- entry_name: str,
88
- tab: ElnExperimentTab,
89
- df: DataFrame) -> ExperimentEntry | None:
90
- """
91
- Create an experiment detail entry from a DataFrame.
92
-
93
- :param user: The user to send the request from.
94
- :param exp_id: The ID of the experiment to create the entry in.
95
- :param entry_name: The name of the entry.
96
- :param tab: The tab that the entry should be added to.
97
- :param df: The DataFrame to create the entry from.
98
- :return: The created entry object.
99
- """
100
- json_list: list[dict[str, Any]] = []
101
- for _, row in df.iterrows():
102
- json_list.append(row.to_dict())
103
- return create_experiment_details_from_json(user, exp_id, entry_name, tab, json_list)
104
-
105
-
106
- def create_experiment_details_from_json(user: SapioUser,
107
- exp_id: int,
108
- entry_name: str,
109
- tab: ElnExperimentTab,
110
- json_list: list[dict[str, Any]]) -> ExperimentEntry | None:
111
- """
112
- Create an experiment detail entry from a list of JSON dictionaries.
113
-
114
- :param user: The user to send the request from.
115
- :param exp_id: The ID of the experiment to create the entry in.
116
- :param entry_name: The name of the entry.
117
- :param tab: The tab that the entry should be added to.
118
- :param json_list: The list of JSON dictionaries to create the entry from. Each dictionary is expected to have the
119
- same keys.
120
- :return: The created entry object.
121
- """
122
- if not json_list:
123
- return None
124
-
125
- # Determine which fields in the JSON can be used to create field definitions.
126
- fb = FieldBuilder()
127
- fields: list[AbstractVeloxFieldDefinition] = []
128
- fields_by_name: dict[str, AbstractVeloxFieldDefinition] = {}
129
- valid_keys: list[str] = []
130
- display_to_field_name: dict[str, str] = {}
131
- for key, value in json_list[0].items():
132
- field_name: str = key.replace(" ", "_")
133
- display_to_field_name[key] = field_name
134
- if isinstance(value, str):
135
- field = fb.string_field(field_name, display_name=key)
136
- fields.append(field)
137
- fields_by_name[field_name] = field
138
- valid_keys.append(key)
139
- elif isinstance(value, (int, float)):
140
- field = fb.double_field(field_name, display_name=key, precision=3)
141
- fields.append(field)
142
- fields_by_name[field_name] = field
143
- valid_keys.append(key)
144
-
145
- # Extract the valid field values from the JSON.
146
- field_maps: list[dict[str, Any]] = []
147
- for json_dict in json_list:
148
- field_map: dict[str, Any] = {}
149
- for key in valid_keys:
150
- # Watch out for NaN values or other special values.
151
- val: Any = json_dict.get(key)
152
- if (fields_by_name[key].data_field_type == FieldType.DOUBLE
153
- and (not isinstance(val, (int, float))) or (isinstance(val, float) and math.isnan(val))):
154
- val = None
155
- field_map[display_to_field_name[key]] = val
156
- field_maps.append(field_map)
157
-
158
- detail_entry = ElnEntryCriteria(ElnEntryType.Table, entry_name,
159
- ElnBaseDataType.EXPERIMENT_DETAIL.data_type_name,
160
- tab_next_entry_order(user, exp_id, tab),
161
- notebook_experiment_tab_id=tab.tab_id,
162
- field_definition_list=fields)
163
- entry = ElnManager(user).add_experiment_entry(exp_id, detail_entry)
164
- DataRecordManager(user).add_data_records_with_data(entry.data_type_name, field_maps)
165
-
166
-
167
- def tab_next_entry_order(user: SapioUser, exp_id: int, tab: ElnExperimentTab) -> int:
168
- max_order: int = 0
169
- for step in ElnExperimentProtocol(ElnExperiment(exp_id, "", 0), user).get_sorted_step_list():
170
- if step.eln_entry.notebook_experiment_tab_id == tab.tab_id and step.eln_entry.order > max_order:
171
- max_order = step.eln_entry.order
172
- return max_order + 1
173
-
174
-
175
- class ToolOfToolsHelper:
176
- """
177
- A class with helper methods utilized by the Tool of Tools for the creation and updating of experiment tabs that
178
- track a tool's progress and results.
179
- """
180
- # Contextual info.
181
- user: SapioUser
182
- tab_prefix: str
183
- exp_id: int
184
- _protocol: ElnExperimentProtocol
185
-
186
- # Tool info.
187
- name: str
188
- description: str
189
- results_data_type: str | None
190
-
191
- # Managers.
192
- eln_man: ElnManager
193
- dr_man: DataRecordManager
194
-
195
- # Stuff created by this helper.
196
- _initialized: bool
197
- """Whether a tab for this tool has been initialized."""
198
- tab: ElnExperimentTab
199
- """The tab that contains the tool's entries."""
200
- description_entry: ElnEntryStep | None
201
- """The text entry that displays the description of the tool."""
202
- description_record: DataRecord | None
203
- """The record that stores the description of the tool."""
204
- progress_entry: ElnEntryStep | None
205
- """A hidden entry for tracking the progress of the tool."""
206
- progress_record: DataRecord | None
207
- """The record that stores the progress of the tool."""
208
- progress_gauge_entry: ElnEntryStep | None
209
- """A chart entry that displays the progress of the tool using the hidden progress entry."""
210
- results_entry: ElnEntryStep | None
211
- """An entry for displaying the results of the tool. If None, the tool does not produce result records."""
212
-
213
- def __init__(self, headers: Mapping[str, str], name: str, description: str,
214
- results_data_type: str | None = None):
215
- """
216
- :param headers: The headers that were passed to the endpoint.
217
- :param name: The name of the tool.
218
- :param description: A description of the tool.
219
- :param results_data_type: The data type name for the results of the tool. If None, the tool does not produce
220
- result records.
221
- """
222
- headers: dict[str, str] = format_tot_headers(headers)
223
- self.user = create_user_from_tot_headers(headers)
224
- self.exp_id = int(headers[EXP_ID_HEADER.lower()])
225
- self.tab_prefix = headers[TAB_PREFIX_HEADER.lower()]
226
- # The experiment name and record ID aren't necessary to know.
227
- self._protocol = ElnExperimentProtocol(ElnExperiment(self.exp_id, "", 0), self.user)
228
-
229
- self.name = name
230
- self.description = description
231
- self.results_data_type = results_data_type
232
-
233
- self.eln_man = ElnManager(self.user)
234
- self.dr_man = DataRecordManager(self.user)
235
-
236
- self._initialized = False
237
-
238
- def initialize_tab(self) -> ElnExperimentTab:
239
- if self._initialized:
240
- return self.tab
241
- self._initialized = True
242
-
243
- # Determine if a previous call to this endpoint already created a tab for these results. If so, grab the entries
244
- # from that tab.
245
- tab_name: str = f"{self.tab_prefix.strip()} {self.name.strip()}"
246
- tabs: list[ElnExperimentTab] = self.eln_man.get_tabs_for_experiment(self.exp_id)
247
- for tab in tabs:
248
- if tab.tab_name != tab_name:
249
- continue
250
-
251
- for entry in self._protocol.get_sorted_step_list():
252
- if entry.eln_entry.notebook_experiment_tab_id != tab.tab_id:
253
- continue
254
-
255
- dt: str = entry.get_data_type_names()[0] if entry.get_data_type_names() else None
256
- if (entry.eln_entry.entry_type == ElnEntryType.Form
257
- and ElnBaseDataType.get_base_type(dt) == ElnBaseDataType.EXPERIMENT_DETAIL
258
- and not hasattr(self, "progress_entry")):
259
- self.progress_entry = entry
260
- self.progress_record = entry.get_records()[0]
261
- elif (entry.eln_entry.entry_type == ElnEntryType.Dashboard
262
- and not hasattr(self, "progress_gauge_entry")):
263
- self.progress_gauge_entry = entry
264
- elif (entry.eln_entry.entry_type == ElnEntryType.Text
265
- and not hasattr(self, "description_entry")):
266
- self.description_entry = entry
267
- self.description_record = entry.get_records()[0]
268
- elif (entry.eln_entry.entry_type == ElnEntryType.Table
269
- and dt == self.results_data_type
270
- and not hasattr(self, "results_entry")):
271
- self.results_entry = entry
272
-
273
- if not hasattr(self, "progress_entry"):
274
- self.progress_entry = None
275
- self.progress_record = None
276
- if not hasattr(self, "progress_gauge_entry"):
277
- self.progress_gauge_entry = None
278
- if not hasattr(self, "description_entry"):
279
- self.description_entry = None
280
- self.description_record = None
281
- if not hasattr(self, "results_entry"):
282
- self.results_entry = None
283
-
284
- self.tab = tab
285
- return tab
286
-
287
- # Otherwise, create the tab for the tool progress and results.
288
- tab_crit = ElnExperimentTabAddCriteria(tab_name, [])
289
- tab: ElnExperimentTab = self.eln_man.add_tab_for_experiment(self.exp_id, tab_crit)
290
- self.tab = tab
291
-
292
- # Create a hidden entry for tracking the progress of the tool.
293
- field_sets: list[ElnFieldSetInfo] = self.eln_man.get_field_set_info_list()
294
- progress_field_set: list[ElnFieldSetInfo] = [x for x in field_sets if
295
- x.field_set_name == "Tool of Tools Progress"]
296
- if not progress_field_set:
297
- raise SapioException("Unable to locate the field set for the Tool of Tools progress.")
298
- progress_entry_crit = ElnEntryCriteria(ElnEntryType.Form, f"ELaiN: {self.name} Progress",
299
- ElnBaseDataType.EXPERIMENT_DETAIL.data_type_name, 1,
300
- notebook_experiment_tab_id=tab.tab_id,
301
- enb_field_set_id=progress_field_set[0].field_set_id)
302
- progress_entry = ElnEntryStep(self._protocol,
303
- self.eln_man.add_experiment_entry(self.exp_id, progress_entry_crit))
304
- self.progress_entry = progress_entry
305
- self.progress_record = progress_entry.get_records()[0]
306
-
307
- # Hide the progress entry.
308
- form_update_crit = ElnFormEntryUpdateCriteria()
309
- form_update_crit.is_hidden = True
310
- self.eln_man.update_experiment_entry(self.exp_id, self.progress_entry.get_id(), form_update_crit)
311
-
312
- # Create the text entry that displays the description of the tool. Include the timestamp of when the
313
- # tool started and format the description so that the text isn't too small to read.
314
- # TODO: Get the UTC offset in seconds from the header once that's being sent.
315
- now: str = TimeUtil.now_in_format("%Y-%m-%d %H:%M:%S UTC", "UTC")
316
- description: str = f"""<p><span style="color: rgb(35, 111, 161); font-size: 12pt; font-weight:500;">{now}</span>
317
- <br><span style="font-size: 15pt;">{self.description}</span></p>"""
318
- text_entry: ElnEntryStep = ELNStepFactory.create_text_entry(self._protocol, description)
319
- self.description_entry = text_entry
320
- self.description_record = text_entry.get_records()[0]
321
-
322
- # Shrink the text entry by one column.
323
- text_update_crit = _ElnTextEntryUpdateCriteria()
324
- text_update_crit.column_span = 2
325
- self.eln_man.update_experiment_entry(self.exp_id, self.description_entry.get_id(), text_update_crit)
326
-
327
- # Create a gauge entry to display the progress.
328
- gauge_entry: ElnEntryStep = self._create_gauge_chart(self._protocol, progress_entry,
329
- f"{self.name} Progress", "Progress", "StatusMsg")
330
- self.progress_gauge_entry = gauge_entry
331
-
332
- # Make sure the gauge entry isn't too big and stick it to the right of the text entry.
333
- dash_update_crit = ElnDashboardEntryUpdateCriteria()
334
- dash_update_crit.entry_height = 250
335
- dash_update_crit.column_span = 2
336
- dash_update_crit.column_order = 2
337
- self.eln_man.update_experiment_entry(self.exp_id, self.progress_gauge_entry.get_id(), dash_update_crit)
338
-
339
- # TODO: Bulk updates aren't working?
340
- # self.eln_man.update_experiment_entries(self.exp_id, {
341
- # self.progress_entry.get_id(): form_update_crit,
342
- # self.progress_gauge_entry.get_id(): dash_update_crit,
343
- # self.description_entry.get_id(): text_update_crit
344
- # })
345
-
346
- # Create a results entry if this tool produces result records.
347
- if self.results_data_type:
348
- results_entry = ELNStepFactory.create_table_step(self._protocol, f"{self.name} Results",
349
- self.results_data_type)
350
- self.results_entry = results_entry
351
- else:
352
- self.results_entry = None
353
-
354
- return tab
355
-
356
- def add_to_description(self, description: str) -> None:
357
- """
358
- Add to the description entry of the tool.
359
-
360
- :param description: The text to add to the description.
361
- """
362
- if not self._initialized:
363
- raise SapioException("The tab for this tool has not been initialized.")
364
- field: str = ElnBaseDataType.get_text_entry_data_field_name()
365
- update: str = self.description_record.get_field_value(field)
366
- update += f"""<p style="padding-top: 10px;"><span style="font-size: 15pt;">{description}</span></p>"""
367
- self.description_record.set_field_value(field, update)
368
- self.dr_man.commit_data_records([self.description_record])
369
-
370
- def update_progress(self, progress: float, status_msg: str | None = None) -> None:
371
- """
372
- Updates the progress of the tool.
373
-
374
- :param progress: A value between 0 and 100 representing the progress of the tool.
375
- :param status_msg: A status message to display to the user alongside the progress gauge.
376
- """
377
- if not self._initialized:
378
- raise SapioException("The tab for this tool has not been initialized.")
379
- self.progress_record.set_field_value("Progress", progress)
380
- self.progress_record.set_field_value("StatusMsg", status_msg)
381
- self.dr_man.commit_data_records([self.progress_record])
382
-
383
- def add_results_bar_chart(self, x_axis: str, y_axis: str) -> ExperimentEntry:
384
- """
385
- Create a bar chart entry for the results of the tool.
386
-
387
- :param x_axis: The data field to use for the x-axis of the chart.
388
- :param y_axis: The data field to use for the y-axis of the chart.
389
- :return: The newly created chart entry.
390
- """
391
- if not self._initialized:
392
- raise SapioException("The tab for this tool has not been initialized.")
393
- if not self.results_entry:
394
- raise SapioException("This tool does not produce result records.")
395
- return ELNStepFactory.create_bar_chart_step(self._protocol, self.results_entry, f"{self.name} Results Chart",
396
- x_axis, y_axis)[0].eln_entry
397
-
398
- def add_attachment_entry(self, file_name: str, file_data: str | bytes, entry_name: str,
399
- tab: ElnExperimentTab | None = None) -> ExperimentEntry:
400
- """
401
- Add a new attachment entry to the experiment with the provided attachment data.
402
-
403
- :param file_name: The name of the attachment.
404
- :param file_data: The data of the attachment. This can be a string or bytes.
405
- :param entry_name: Name of the attachment entry to create in the experiment.
406
- :param tab: The tab where the attachment will be added. If not provided, the tab initialized by this helper
407
- will be used.
408
- :return: The created entry object.
409
- """
410
- # Check if the tab has been initialized or a tab has been provided.
411
- if not self._initialized and tab is None:
412
- raise SapioException("The tab for this tool has not been initialized. Either initialize a tab for this "
413
- "tool or provide the tab to this function to add the attachment entry to.")
414
- tab_id: int = self.tab.tab_id if tab is None else tab.tab_id
415
-
416
- # Encode the file contents in base64.
417
- if isinstance(file_data, str):
418
- file_data: bytes = file_data.encode("utf-8")
419
- base64_encoded: str = base64.b64encode(file_data).decode("utf-8")
420
-
421
- # Crete an attachment entry with the provided data.
422
- attachment_entry = self.eln_man.add_experiment_entry(
423
- self.exp_id,
424
- ElnEntryCriteria(ElnEntryType.Attachment, entry_name, "Attachment", order=2,
425
- notebook_experiment_tab_id=tab_id, attachment_file_name=file_name,
426
- attachment_data_base64=base64_encoded)
427
- )
428
-
429
- # Return the entry object for further use.
430
- return attachment_entry
431
-
432
- def add_attachment_entry_from_file_system(self, file_path: str, entry_name: str,
433
- tab: ElnExperimentTab | None = None) -> ExperimentEntry:
434
- """
435
- Add a new attachment entry to the experiment with the provided file path to a file in the file system.
436
-
437
- :param file_path: The path to a file in the system to attach to the experiment.
438
- :param entry_name: Name of the attachment entry to create in the experiment.
439
- :param tab: The tab where the attachment will be added. If not provided, the tab initialized by this helper
440
- will be used.
441
- :return: The created entry object.
442
- """
443
- # Check if the tab has been initialized or a tab has been provided.
444
- # This is redundant with the same check in the add_attachment_entry function, but it's duplicated here as to
445
- # not read the provided file and then find out we can't do anything with it anyway.
446
- if not self._initialized and tab is None:
447
- raise SapioException("The tab for this tool has not been initialized. Either initialize a tab for this "
448
- "tool or provide the tab to this function to add the attachment entry to.")
449
-
450
- with open(file_path, 'rb') as f:
451
- file_contents: bytes = f.read()
452
- return self.add_attachment_entry(file_path, file_contents, entry_name, tab)
453
-
454
- # TODO: Remove this once pylib has a gauge chart function in ElnStepFactory.
455
- @staticmethod
456
- def _create_gauge_chart(protocol: ElnExperimentProtocol, data_source_step: ElnEntryStep, step_name: str,
457
- field_name: str, status_field: str, group_by_field_name: str = "DataRecordName") \
458
- -> ElnEntryStep:
459
- """
460
- Create a gauge chart step in the experiment protocol.
461
- """
462
- if not data_source_step.get_data_type_names():
463
- raise ValueError("The data source step did not declare a data type name.")
464
- data_type_name: str = data_source_step.get_data_type_names()[0]
465
- series = GaugeChartSeries(data_type_name, field_name)
466
- series.operation_type = ChartOperationType.VALUE
467
- chart = _GaugeChartDefinition()
468
- chart.main_data_type_name = data_type_name
469
- chart.status_field = status_field
470
- chart.minimum_value = 0.
471
- chart.maximum_value = 100.
472
- chart.series_list = [series]
473
- chart.grouping_type = ChartGroupingType.GROUP_BY_FIELD
474
- chart.grouping_type_data_type_name = data_type_name
475
- chart.grouping_type_data_field_name = group_by_field_name
476
- dashboard, step = ELNStepFactory._create_dashboard_step_from_chart(chart, data_source_step, protocol, step_name)
477
- protocol.invalidate()
478
- return step
479
-
480
-
481
- # TODO: This is only here because the get_chart_type function in pylib is wrong. Remove this once pylib is fixed.
482
- # Also using this to set the new status field setting.
483
- class _GaugeChartDefinition(GaugeChartDefinition):
484
- status_field: str
485
-
486
- def get_chart_type(self) -> ChartType:
487
- return ChartType.GAUGE_CHART
488
-
489
- def to_json(self) -> dict[str, Any]:
490
- result = super().to_json()
491
- result["statusValueField"] = {
492
- "dataTypeName": self.main_data_type_name,
493
- "dataFieldName": self.status_field
494
- }
495
- return result
496
-
497
-
498
- # TODO: Remove once the ElnTextEntryUpdateCriteria is fixed.
499
- class _ElnTextEntryUpdateCriteria(AbstractElnEntryUpdateCriteria):
500
- """
501
- Text Entry Update Data Payload
502
- Create this payload object and set the attributes you want to update before sending the request.
503
- """
504
-
505
- def __init__(self):
506
- super().__init__(ElnEntryType.Text)
507
-
508
- def to_json(self) -> dict[str, Any]:
509
- ret = super().to_json()
510
- return ret
@@ -1,86 +0,0 @@
1
- from typing import Iterable, cast
2
-
3
- from sapiopylib.rest.User import SapioUser
4
- from sapiopylib.rest.pojo.CustomReport import CustomReportCriteria, CustomReport
5
- from sapiopylib.rest.pojo.webhook.WebhookDirective import HomePageDirective, FormDirective, TableDirective, \
6
- CustomReportDirective, ElnExperimentDirective, ExperimentEntryDirective
7
-
8
- from sapiopycommons.general.aliases import SapioRecord, AliasUtil, ExperimentIdentifier, ExperimentEntryIdentifier, \
9
- UserIdentifier
10
- from sapiopycommons.general.custom_report_util import CustomReportUtil
11
-
12
-
13
- # FR-47392: Create a DirectiveUtil class to simplify the creation of directives.
14
- class DirectiveUtil:
15
- """
16
- DirectiveUtil is a class for creating webhook directives. The utility functions reduce the provided variables
17
- down to the exact type that the directives require, removing the need for the caller to handle the conversion.
18
- """
19
- user: SapioUser
20
-
21
- def __init__(self, context: UserIdentifier):
22
- """
23
- :param context: The current webhook context or a user object to send requests from.
24
- """
25
- self.user = AliasUtil.to_sapio_user(context)
26
-
27
- @staticmethod
28
- def homepage() -> HomePageDirective:
29
- """
30
- :return: A directive that sends the user back to their home page.
31
- """
32
- return HomePageDirective()
33
-
34
- @staticmethod
35
- def record_form(record: SapioRecord) -> FormDirective:
36
- """
37
- :param record: A record in the system.
38
- :return: A directive that sends the user to a specific data record form.
39
- """
40
- return FormDirective(AliasUtil.to_data_record(record))
41
-
42
- @staticmethod
43
- def record_table(records: Iterable[SapioRecord]) -> TableDirective:
44
- """
45
- :param records: A list of records in the system.
46
- :return: A directive that sends the user to a table of data records.
47
- """
48
- return TableDirective(AliasUtil.to_data_records(records))
49
-
50
- @staticmethod
51
- def record_adaptive(records: Iterable[SapioRecord]) -> TableDirective | FormDirective:
52
- """
53
- :param records: A list of records in the system.
54
- :return: A directive that sends the user to a table of data records if there are multiple records,
55
- or a directive that sends the user to a specific data record form if there is only one record.
56
- """
57
- records: list[SapioRecord] = list(records)
58
- if len(records) == 1:
59
- return DirectiveUtil.record_form(records[0])
60
- return DirectiveUtil.record_table(records)
61
-
62
- def custom_report(self, report: CustomReport | CustomReportCriteria | str) -> CustomReportDirective:
63
- """
64
- :param report: A custom report, the criteria for a custom report, or the name of a system report.
65
- :return: A directive that sends the user to the results of the provided custom report.
66
- """
67
- if isinstance(report, str):
68
- report: CustomReport = CustomReportUtil.get_system_report_criteria(self.user, report)
69
- return CustomReportDirective(cast(CustomReport, report))
70
-
71
- @staticmethod
72
- def eln_experiment(experiment: ExperimentIdentifier) -> ElnExperimentDirective:
73
- """
74
- :param experiment: An identifier for an experiment.
75
- :return: A directive that sends the user to the ELN experiment.
76
- """
77
- return ElnExperimentDirective(AliasUtil.to_notebook_id(experiment))
78
-
79
- @staticmethod
80
- def eln_entry(experiment: ExperimentIdentifier, entry: ExperimentEntryIdentifier) -> ExperimentEntryDirective:
81
- """
82
- :param experiment: An identifier for an experiment.
83
- :param entry: An identifier for an entry in the experiment.
84
- :return: A directive that sends the user to the provided experiment entry within its ELN experiment.
85
- """
86
- return ExperimentEntryDirective(AliasUtil.to_notebook_id(experiment), AliasUtil.to_entry_id(entry))