sapiopycommons 2025.2.3a411__tar.gz → 2025.2.5a412__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of sapiopycommons might be problematic. Click here for more details.

Files changed (81) hide show
  1. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/PKG-INFO +1 -1
  2. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/pyproject.toml +1 -1
  3. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/elain/tool_of_tools.py +94 -18
  4. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/.gitignore +0 -0
  5. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/LICENSE +0 -0
  6. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/README.md +0 -0
  7. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/__init__.py +0 -0
  8. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/callbacks/__init__.py +0 -0
  9. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/callbacks/callback_util.py +0 -0
  10. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/callbacks/field_builder.py +0 -0
  11. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/chem/IndigoMolecules.py +0 -0
  12. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/chem/Molecules.py +0 -0
  13. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/chem/__init__.py +0 -0
  14. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/customreport/__init__.py +0 -0
  15. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/customreport/auto_pagers.py +0 -0
  16. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/customreport/column_builder.py +0 -0
  17. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/customreport/custom_report_builder.py +0 -0
  18. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/customreport/term_builder.py +0 -0
  19. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/datatype/__init__.py +0 -0
  20. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/datatype/attachment_util.py +0 -0
  21. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/datatype/data_fields.py +0 -0
  22. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/datatype/pseudo_data_types.py +0 -0
  23. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/elain/__init__.py +0 -0
  24. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/eln/__init__.py +0 -0
  25. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/eln/experiment_handler.py +0 -0
  26. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/eln/experiment_report_util.py +0 -0
  27. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/eln/plate_designer.py +0 -0
  28. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/files/__init__.py +0 -0
  29. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/files/complex_data_loader.py +0 -0
  30. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/files/file_bridge.py +0 -0
  31. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/files/file_bridge_handler.py +0 -0
  32. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/files/file_data_handler.py +0 -0
  33. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/files/file_util.py +0 -0
  34. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/files/file_validator.py +0 -0
  35. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/files/file_writer.py +0 -0
  36. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/flowcyto/flow_cyto.py +0 -0
  37. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/flowcyto/flowcyto_data.py +0 -0
  38. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/general/__init__.py +0 -0
  39. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/general/accession_service.py +0 -0
  40. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/general/aliases.py +0 -0
  41. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/general/audit_log.py +0 -0
  42. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/general/custom_report_util.py +0 -0
  43. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/general/directive_util.py +0 -0
  44. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/general/exceptions.py +0 -0
  45. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/general/popup_util.py +0 -0
  46. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/general/sapio_links.py +0 -0
  47. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/general/storage_util.py +0 -0
  48. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/general/time_util.py +0 -0
  49. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/multimodal/multimodal.py +0 -0
  50. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/multimodal/multimodal_data.py +0 -0
  51. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/processtracking/__init__.py +0 -0
  52. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/processtracking/custom_workflow_handler.py +0 -0
  53. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/processtracking/endpoints.py +0 -0
  54. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/recordmodel/__init__.py +0 -0
  55. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/recordmodel/record_handler.py +0 -0
  56. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/rules/__init__.py +0 -0
  57. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/rules/eln_rule_handler.py +0 -0
  58. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/rules/on_save_rule_handler.py +0 -0
  59. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/sftpconnect/__init__.py +0 -0
  60. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/sftpconnect/sftp_builder.py +0 -0
  61. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/webhook/__init__.py +0 -0
  62. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/webhook/webhook_context.py +0 -0
  63. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/webhook/webhook_handlers.py +0 -0
  64. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/src/sapiopycommons/webhook/webservice_handlers.py +0 -0
  65. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/tests/AF-A0A009IHW8-F1-model_v4.cif +0 -0
  66. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/tests/_do_not_add_init_py_here +0 -0
  67. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/tests/accession_test.py +0 -0
  68. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/tests/bio_reg_test.py +0 -0
  69. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/tests/chem_test.py +0 -0
  70. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/tests/chem_test_curation_queue.py +0 -0
  71. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/tests/curation_queue_test.sdf +0 -0
  72. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/tests/data_type_models.py +0 -0
  73. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/tests/flowcyto/101_DEN084Y5_15_E01_008_clean.fcs +0 -0
  74. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/tests/flowcyto/101_DEN084Y5_15_E03_009_clean.fcs +0 -0
  75. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/tests/flowcyto/101_DEN084Y5_15_E05_010_clean.fcs +0 -0
  76. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/tests/flowcyto/8_color_ICS.wsp +0 -0
  77. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/tests/flowcyto/COVID19_W_001_O.fcs +0 -0
  78. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/tests/flowcyto_test.py +0 -0
  79. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/tests/kappa.chains.fasta +0 -0
  80. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/tests/mafft_test.py +0 -0
  81. {sapiopycommons-2025.2.3a411 → sapiopycommons-2025.2.5a412}/tests/test.gb +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sapiopycommons
3
- Version: 2025.2.3a411
3
+ Version: 2025.2.5a412
4
4
  Summary: Official Sapio Python API Utilities Package
5
5
  Project-URL: Homepage, https://github.com/sapiosciences
6
6
  Author-email: Jonathan Steck <jsteck@sapiosciences.com>, Yechen Qiao <yqiao@sapiosciences.com>
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sapiopycommons"
7
- version='2025.02.03a411'
7
+ version='2025.02.05a412'
8
8
  authors = [
9
9
  { name="Jonathan Steck", email="jsteck@sapiosciences.com" },
10
10
  { name="Yechen Qiao", email="yqiao@sapiosciences.com" },
@@ -1,4 +1,5 @@
1
1
  import base64
2
+ from typing import Final, Mapping, Any
2
3
 
3
4
  from sapiopylib.rest.DataRecordManagerService import DataRecordManager
4
5
  from sapiopylib.rest.ELNService import ElnManager
@@ -18,6 +19,11 @@ from sapiopylib.rest.utils.Protocols import ElnEntryStep, ElnExperimentProtocol
18
19
 
19
20
  from sapiopycommons.general.exceptions import SapioException
20
21
 
22
+ CREDENTIALS_HEADER: Final[str] = "SAPIO_APP_API_KEY"
23
+ API_URL_HEADER: Final[str] = "SAPIO_APP_API_URL"
24
+ EXP_ID_HEADER: Final[str] = "EXPERIMENT_ID"
25
+ TAB_PREFIX_HEADER: Final[str] = "TAB_PREFIX"
26
+
21
27
 
22
28
  # FR-47422: Create utility methods to assist the tool of tools.
23
29
  def create_tot_headers(url: str, username: str, password: str, experiment_id: int, tab_prefix: str) \
@@ -38,23 +44,36 @@ def create_tot_headers(url: str, username: str, password: str, experiment_id: in
38
44
  # and finally convert the result back into a string.
39
45
  encoded_credentials: str = base64.b64encode(credentials.encode('utf-8')).decode('utf-8')
40
46
  headers: dict[str, str] = {
41
- "SAPIO_APP_API_KEY": f"Basic {encoded_credentials}",
42
- "SAPIO_APP_API_URL": url,
43
- "EXPERIMENT_ID": str(experiment_id),
44
- "TAB_PREFIX": tab_prefix
47
+ CREDENTIALS_HEADER: f"Basic {encoded_credentials}",
48
+ API_URL_HEADER: url,
49
+ EXP_ID_HEADER: str(experiment_id),
50
+ TAB_PREFIX_HEADER: tab_prefix
45
51
  }
46
52
  return encoded_credentials, headers
47
53
 
48
54
 
49
- def create_user_from_tot_headers(headers: dict[str, str]) -> SapioUser:
55
+ def create_user_from_tot_headers(headers: Mapping[str, str]) -> SapioUser:
50
56
  """
51
57
  Create a SapioUser object from the headers passed to a tool of tools endpoint.
52
58
 
53
59
  :param headers: The headers that were passed to the endpoint.
54
60
  :return: A SapioUser object created from the headers that can be used to communicate with the Sapio server.
55
61
  """
56
- credentials = base64.b64decode(headers["SAPIO_APP_API_KEY"].removeprefix("Basic ")).decode("utf-8").split(":", 1)
57
- return SapioUser(headers["SAPIO_APP_API_URL"], username=credentials[0], password=credentials[1])
62
+ headers: dict[str, str] = format_tot_headers(headers)
63
+ credentials = (base64.b64decode(headers[CREDENTIALS_HEADER.lower()].removeprefix("Basic "))
64
+ .decode("utf-8").split(":", 1))
65
+ return SapioUser(headers[API_URL_HEADER.lower()], username=credentials[0], password=credentials[1])
66
+
67
+
68
+ def format_tot_headers(headers: Mapping[str, str]) -> dict[str, str]:
69
+ """
70
+ Format the headers passed to a tool of tools endpoint to guarantee that the keys are lowercase.
71
+
72
+ :param headers: The headers that were passed to the endpoint.
73
+ :return: The headers with all keys converted to lowercase. (Conflicting keys will cause one to overwrite the other,
74
+ but there should not be any conflicting keys in the headers passed to a tool of tools endpoint.)
75
+ """
76
+ return {k.lower(): v for k, v in headers.items()}
58
77
 
59
78
 
60
79
  class ToolOfToolsHelper:
@@ -93,7 +112,8 @@ class ToolOfToolsHelper:
93
112
  results_entry: ElnEntryStep | None
94
113
  """An entry for displaying the results of the tool. If None, the tool does not produce result records."""
95
114
 
96
- def __init__(self, headers: dict[str, str], name: str, description: str, results_data_type: str | None = None):
115
+ def __init__(self, headers: Mapping[str, str], name: str, description: str,
116
+ results_data_type: str | None = None):
97
117
  """
98
118
  :param headers: The headers that were passed to the endpoint.
99
119
  :param name: The name of the tool.
@@ -101,9 +121,10 @@ class ToolOfToolsHelper:
101
121
  :param results_data_type: The data type name for the results of the tool. If None, the tool does not produce
102
122
  result records.
103
123
  """
124
+ headers: dict[str, str] = format_tot_headers(headers)
104
125
  self.user = create_user_from_tot_headers(headers)
105
- self.exp_id = int(headers["EXPERIMENT_ID"])
106
- self.tab_prefix = headers["TAB_PREFIX"]
126
+ self.exp_id = int(headers[EXP_ID_HEADER.lower()])
127
+ self.tab_prefix = headers[TAB_PREFIX_HEADER.lower()]
107
128
  # The experiment name and record ID aren't necessary to know.
108
129
  self._protocol = ElnExperimentProtocol(ElnExperiment(self.exp_id, "", 0), self.user)
109
130
 
@@ -121,9 +142,49 @@ class ToolOfToolsHelper:
121
142
  return self.tab
122
143
  self._initialized = True
123
144
 
124
- # Create the tab for the tool progress and results.
125
- # The entry IDs list can't be empty, so we need to create a dummy entry just to get the tab created.
126
- tab_crit = ElnExperimentTabAddCriteria(f"{self.tab_prefix} {self.name}", [])
145
+ # Determine if a previous call to this endpoint already created a tab for these results. If so, grab the entries
146
+ # from that tab.
147
+ tab_name: str = f"{self.tab_prefix.strip()} {self.name.strip()}"
148
+ tabs: list[ElnExperimentTab] = self.eln_man.get_tabs_for_experiment(self.exp_id)
149
+ for tab in tabs:
150
+ if tab.tab_name != tab_name:
151
+ continue
152
+
153
+ for entry in self._protocol.get_sorted_step_list():
154
+ if entry.eln_entry.notebook_experiment_tab_id != tab.tab_id:
155
+ continue
156
+
157
+ dt: str = entry.get_data_type_names()[0]
158
+ if (entry.eln_entry.entry_type == ElnEntryType.Form
159
+ and ElnBaseDataType.get_base_type(dt) == ElnBaseDataType.EXPERIMENT_DETAIL
160
+ and not hasattr(self, "progress_entry")):
161
+ self.progress_entry = entry
162
+ self.progress_record = entry.get_records()[0]
163
+ elif (entry.eln_entry.entry_type == ElnEntryType.Dashboard
164
+ and not hasattr(self, "progress_gauge_entry")):
165
+ self.progress_gauge_entry = entry
166
+ elif (entry.eln_entry.entry_type == ElnEntryType.Text
167
+ and not hasattr(self, "description_entry")):
168
+ self.description_entry = entry
169
+ elif (entry.eln_entry.entry_type == ElnEntryType.Table
170
+ and dt == self.results_data_type
171
+ and not hasattr(self, "result_entry")):
172
+ self.results_entry = entry
173
+
174
+ if not hasattr(self, "progress_entry"):
175
+ self.results_entry = None
176
+ if not hasattr(self, "progress_gauge_entry"):
177
+ self.results_entry = None
178
+ if not hasattr(self, "description_entry"):
179
+ self.results_entry = None
180
+ if not hasattr(self, "result_entry"):
181
+ self.results_entry = None
182
+
183
+ self.tab = tab
184
+ return tab
185
+
186
+ # Otherwise, create the tab for the tool progress and results.
187
+ tab_crit = ElnExperimentTabAddCriteria(tab_name, [])
127
188
  tab: ElnExperimentTab = self.eln_man.add_tab_for_experiment(self.exp_id, tab_crit)
128
189
  self.tab = tab
129
190
 
@@ -149,7 +210,7 @@ class ToolOfToolsHelper:
149
210
 
150
211
  # Create a gauge entry to display the progress.
151
212
  gauge_entry: ElnEntryStep = self._create_gauge_chart(self._protocol, progress_entry,
152
- f"{self.name} Progress", "Progress")
213
+ f"{self.name} Progress", "Progress", "StatusMsg")
153
214
  self.progress_gauge_entry = gauge_entry
154
215
 
155
216
  # Create the text entry that displays the description of the tool.
@@ -158,7 +219,8 @@ class ToolOfToolsHelper:
158
219
 
159
220
  # Create a results entry if this tool produces result records.
160
221
  if self.results_data_type:
161
- results_entry = ELNStepFactory.create_table_step(self._protocol, f"{self.name} Results", self.results_data_type)
222
+ results_entry = ELNStepFactory.create_table_step(self._protocol, f"{self.name} Results",
223
+ self.results_data_type)
162
224
  self.results_entry = results_entry
163
225
  else:
164
226
  self.results_entry = None
@@ -237,7 +299,8 @@ class ToolOfToolsHelper:
237
299
  # TODO: Remove this once pylib has a gauge chart function in ElnStepFactory.
238
300
  @staticmethod
239
301
  def _create_gauge_chart(protocol: ElnExperimentProtocol, data_source_step: ElnEntryStep, step_name: str,
240
- field_name: str, group_by_field_name: str = "DataRecordName") -> ElnEntryStep:
302
+ field_name: str, status_field: str, group_by_field_name: str = "DataRecordName") \
303
+ -> ElnEntryStep:
241
304
  """
242
305
  Create a gauge chart step in the experiment protocol.
243
306
  """
@@ -246,7 +309,9 @@ class ToolOfToolsHelper:
246
309
  data_type_name: str = data_source_step.get_data_type_names()[0]
247
310
  series = GaugeChartSeries(data_type_name, field_name)
248
311
  series.operation_type = ChartOperationType.VALUE
249
- chart = _FixedGaugeChartDefinition()
312
+ chart = _GaugeChartDefinition()
313
+ chart.main_data_type_name = data_type_name
314
+ chart.status_field = status_field
250
315
  chart.minimum_value = 0.
251
316
  chart.maximum_value = 100.
252
317
  chart.series_list = [series]
@@ -259,6 +324,17 @@ class ToolOfToolsHelper:
259
324
 
260
325
 
261
326
  # TODO: This is only here because the get_chart_type function in pylib is wrong. Remove this once pylib is fixed.
262
- class _FixedGaugeChartDefinition(GaugeChartDefinition):
327
+ # Also using this to set the new status field setting.
328
+ class _GaugeChartDefinition(GaugeChartDefinition):
329
+ status_field: str
330
+
263
331
  def get_chart_type(self) -> ChartType:
264
332
  return ChartType.GAUGE_CHART
333
+
334
+ def to_json(self) -> dict[str, Any]:
335
+ result = super().to_json()
336
+ result["statusValueField"] = {
337
+ "dataTypeName": self.main_data_type_name,
338
+ "dataFieldName": self.status_field
339
+ }
340
+ return result