sapiopycommons 2025.2.14a436__tar.gz → 2025.2.17a438__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 (83) hide show
  1. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/PKG-INFO +1 -1
  2. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/pyproject.toml +1 -1
  3. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/ai/tool_of_tools.py +72 -8
  4. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/.gitignore +0 -0
  5. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/LICENSE +0 -0
  6. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/README.md +0 -0
  7. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/__init__.py +0 -0
  8. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/ai/__init__.py +0 -0
  9. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/callbacks/__init__.py +0 -0
  10. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/callbacks/callback_util.py +0 -0
  11. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/callbacks/field_builder.py +0 -0
  12. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/chem/IndigoMolecules.py +0 -0
  13. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/chem/Molecules.py +0 -0
  14. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/chem/__init__.py +0 -0
  15. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/customreport/__init__.py +0 -0
  16. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/customreport/auto_pagers.py +0 -0
  17. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/customreport/column_builder.py +0 -0
  18. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/customreport/custom_report_builder.py +0 -0
  19. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/customreport/term_builder.py +0 -0
  20. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/datatype/__init__.py +0 -0
  21. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/datatype/attachment_util.py +0 -0
  22. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/datatype/data_fields.py +0 -0
  23. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/datatype/pseudo_data_types.py +0 -0
  24. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/eln/__init__.py +0 -0
  25. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/eln/experiment_handler.py +0 -0
  26. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/eln/experiment_report_util.py +0 -0
  27. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/eln/plate_designer.py +0 -0
  28. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/files/__init__.py +0 -0
  29. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/files/complex_data_loader.py +0 -0
  30. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/files/file_bridge.py +0 -0
  31. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/files/file_bridge_handler.py +0 -0
  32. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/files/file_data_handler.py +0 -0
  33. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/files/file_util.py +0 -0
  34. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/files/file_validator.py +0 -0
  35. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/files/file_writer.py +0 -0
  36. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/flowcyto/flow_cyto.py +0 -0
  37. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/flowcyto/flowcyto_data.py +0 -0
  38. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/general/__init__.py +0 -0
  39. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/general/accession_service.py +0 -0
  40. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/general/aliases.py +0 -0
  41. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/general/audit_log.py +0 -0
  42. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/general/custom_report_util.py +0 -0
  43. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/general/directive_util.py +0 -0
  44. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/general/exceptions.py +0 -0
  45. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/general/popup_util.py +0 -0
  46. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/general/sapio_links.py +0 -0
  47. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/general/storage_util.py +0 -0
  48. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/general/time_util.py +0 -0
  49. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/multimodal/multimodal.py +0 -0
  50. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/multimodal/multimodal_data.py +0 -0
  51. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/processtracking/__init__.py +0 -0
  52. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/processtracking/custom_workflow_handler.py +0 -0
  53. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/processtracking/endpoints.py +0 -0
  54. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/recordmodel/__init__.py +0 -0
  55. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/recordmodel/record_handler.py +0 -0
  56. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/rules/__init__.py +0 -0
  57. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/rules/eln_rule_handler.py +0 -0
  58. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/rules/on_save_rule_handler.py +0 -0
  59. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/samples/aliquot.py +0 -0
  60. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/sftpconnect/__init__.py +0 -0
  61. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/sftpconnect/sftp_builder.py +0 -0
  62. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/webhook/__init__.py +0 -0
  63. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/webhook/webhook_context.py +0 -0
  64. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/webhook/webhook_handlers.py +0 -0
  65. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/src/sapiopycommons/webhook/webservice_handlers.py +0 -0
  66. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/tests/AF-A0A009IHW8-F1-model_v4.cif +0 -0
  67. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/tests/_do_not_add_init_py_here +0 -0
  68. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/tests/accession_test.py +0 -0
  69. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/tests/aliquot_test.py +0 -0
  70. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/tests/bio_reg_test.py +0 -0
  71. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/tests/chem_test.py +0 -0
  72. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/tests/chem_test_curation_queue.py +0 -0
  73. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/tests/curation_queue_test.sdf +0 -0
  74. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/tests/data_type_models.py +0 -0
  75. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/tests/flowcyto/101_DEN084Y5_15_E01_008_clean.fcs +0 -0
  76. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/tests/flowcyto/101_DEN084Y5_15_E03_009_clean.fcs +0 -0
  77. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/tests/flowcyto/101_DEN084Y5_15_E05_010_clean.fcs +0 -0
  78. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/tests/flowcyto/8_color_ICS.wsp +0 -0
  79. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/tests/flowcyto/COVID19_W_001_O.fcs +0 -0
  80. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/tests/flowcyto_test.py +0 -0
  81. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/tests/kappa.chains.fasta +0 -0
  82. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/tests/mafft_test.py +0 -0
  83. {sapiopycommons-2025.2.14a436 → sapiopycommons-2025.2.17a438}/tests/test.gb +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sapiopycommons
3
- Version: 2025.2.14a436
3
+ Version: 2025.2.17a438
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.14a436'
7
+ version='2025.02.17a438'
8
8
  authors = [
9
9
  { name="Jonathan Steck", email="jsteck@sapiosciences.com" },
10
10
  { name="Yechen Qiao", email="yqiao@sapiosciences.com" },
@@ -6,12 +6,16 @@ from typing import Final, Mapping, Any
6
6
 
7
7
  from pandas import DataFrame
8
8
  from sapiopylib.rest.DataRecordManagerService import DataRecordManager
9
+ from sapiopylib.rest.DataTypeService import DataTypeManager
9
10
  from sapiopylib.rest.ELNService import ElnManager
10
11
  from sapiopylib.rest.User import SapioUser
11
12
  from sapiopylib.rest.pojo.DataRecord import DataRecord
13
+ from sapiopylib.rest.pojo.Sort import SortDirection
12
14
  from sapiopylib.rest.pojo.chartdata.DashboardDefinition import GaugeChartDefinition
13
15
  from sapiopylib.rest.pojo.chartdata.DashboardEnums import ChartGroupingType, ChartOperationType
14
16
  from sapiopylib.rest.pojo.chartdata.DashboardSeries import GaugeChartSeries
17
+ from sapiopylib.rest.pojo.datatype.DataType import DataTypeDefinition
18
+ from sapiopylib.rest.pojo.datatype.DataTypeLayout import DataTypeLayout, TableLayout
15
19
  from sapiopylib.rest.pojo.datatype.FieldDefinition import AbstractVeloxFieldDefinition, FieldType
16
20
  from sapiopylib.rest.pojo.eln.ElnEntryPosition import ElnEntryPosition
17
21
  from sapiopylib.rest.pojo.eln.ElnExperiment import ElnExperiment
@@ -55,6 +59,9 @@ def create_tot_headers(url: str, username: str, password: str, experiment_id: in
55
59
  # Encode the credentials to bytes, then encode them using base64,
56
60
  # and finally convert the result back into a string.
57
61
  encoded_credentials: str = base64.b64encode(credentials.encode('utf-8')).decode('utf-8')
62
+ # Remove the trailing slash from the URL if it exists.
63
+ if url.endswith("/"):
64
+ url.rstrip("/")
58
65
  headers: dict[str, str] = {
59
66
  CREDENTIALS_HEADER: f"Basic {encoded_credentials}",
60
67
  API_URL_HEADER: url,
@@ -74,7 +81,10 @@ def create_user_from_tot_headers(headers: Mapping[str, str]) -> SapioUser:
74
81
  headers: dict[str, str] = format_tot_headers(headers)
75
82
  credentials = (base64.b64decode(headers[CREDENTIALS_HEADER.lower()].removeprefix("Basic "))
76
83
  .decode("utf-8").split(":", 1))
77
- return SapioUser(headers[API_URL_HEADER.lower()], username=credentials[0], password=credentials[1])
84
+ url: str = headers[API_URL_HEADER.lower()]
85
+ if url.endswith("/"):
86
+ url.rstrip("/")
87
+ return SapioUser(url, username=credentials[0], password=credentials[1])
78
88
 
79
89
 
80
90
  def format_tot_headers(headers: Mapping[str, str]) -> dict[str, str]:
@@ -181,6 +191,7 @@ class AiHelper:
181
191
  # Managers.
182
192
  dr_man: DataRecordManager
183
193
  eln_man: ElnManager
194
+ dt_man: DataTypeManager
184
195
 
185
196
  def __init__(self, user: SapioUser, exp_id: int):
186
197
  """
@@ -192,6 +203,7 @@ class AiHelper:
192
203
 
193
204
  self.dr_man = DataRecordManager(self.user)
194
205
  self.eln_man = ElnManager(self.user)
206
+ self.dt_man = DataTypeManager(self.user)
195
207
 
196
208
  @property
197
209
  def protocol(self) -> ElnExperimentProtocol:
@@ -226,24 +238,42 @@ class AiHelper:
226
238
  def create_experiment_details_from_data_frame(self,
227
239
  tab: ElnExperimentTab,
228
240
  entry_name: str,
229
- df: DataFrame) -> ExperimentEntry | None:
241
+ df: DataFrame,
242
+ sort_field: str | None = None,
243
+ sort_direction: SortDirection = SortDirection.DESCENDING,
244
+ smiles_column: str | None = None) -> ExperimentEntry | None:
230
245
  """
231
246
  Create an experiment detail entry from a DataFrame.
232
247
 
233
248
  :param tab: The tab that the entry should be added to.
234
249
  :param entry_name: The name of the entry.
235
250
  :param df: The DataFrame to create the entry from.
251
+ :param sort_field: The field to sort the resulting entry rows by, if any.
252
+ :param sort_direction: The direction to sort the resulting entry rows in, if a sort_field is provided.
253
+ :param smiles_column: The column name in the provided DataFrame that corresponds to the SMILES strings of the
254
+ compounds tracked in the DataFrame, if any. If this is provided, then the entry will be created with
255
+ images of the compounds corresponding to the SMILES strings in each row of the table.
236
256
  :return: The newly created experiment detail entry.
237
257
  """
238
258
  json_list: list[dict[str, Any]] = []
259
+ smiles: list[str] = []
239
260
  for _, row in df.iterrows():
240
- json_list.append(row.to_dict())
241
- return self.create_experiment_details_from_json(tab, entry_name, json_list)
261
+ row_dict: dict[str, Any] = row.to_dict()
262
+ if smiles_column is not None:
263
+ smiles.append(row_dict.get(smiles_column))
264
+ json_list.append(row_dict)
265
+ images: list[bytes] | None = None
266
+ if smiles:
267
+ images = self.smiles_to_svg(smiles)
268
+ return self.create_experiment_details_from_json(tab, entry_name, json_list, sort_field, sort_direction, images)
242
269
 
243
270
  def create_experiment_details_from_json(self,
244
271
  tab: ElnExperimentTab,
245
272
  entry_name: str,
246
- json_list: list[dict[str, Any]]) -> ExperimentEntry | None:
273
+ json_list: list[dict[str, Any]],
274
+ sort_field: str | None = None,
275
+ sort_direction: SortDirection = SortDirection.DESCENDING,
276
+ images: list[bytes] | None = None) -> ExperimentEntry | None:
247
277
  """
248
278
  Create an experiment detail entry from a list of JSON dictionaries.
249
279
 
@@ -251,6 +281,11 @@ class AiHelper:
251
281
  :param entry_name: The name of the entry.
252
282
  :param json_list: The list of JSON dictionaries to create the entry from. Each dictionary is expected to have the
253
283
  same keys.
284
+ :param sort_field: The field to sort the resulting entry rows by, if any.
285
+ :param sort_direction: The direction to sort the resulting entry rows in, if a sort_field is provided.
286
+ :param images: The images to include in the entry, if any. The images will be added to the rows that they
287
+ correspond to based on the order of the images in the images list and the order of the rows in the
288
+ json list.
254
289
  :return: The newly created experiment detail entry.
255
290
  """
256
291
  if not json_list:
@@ -271,12 +306,26 @@ class AiHelper:
271
306
  fields.append(field)
272
307
  fields_by_name[key] = field
273
308
 
309
+ # Sort the JSON list if requested.
310
+ if sort_field and sort_direction != SortDirection.NONE:
311
+ if images:
312
+ old_order: list[str] = [x[sort_field] for x in json_list]
313
+ json_list.sort(key=lambda x: x.get(sort_field), reverse=sort_direction == SortDirection.DESCENDING)
314
+ # We'll need to resort the images as well.
315
+ if images:
316
+ new_order: list[str] = [x[sort_field] for x in json_list]
317
+ new_images: list[bytes] = []
318
+ for val in new_order:
319
+ # noinspection PyUnboundLocalVariable
320
+ new_images.append(images[old_order.index(val)])
321
+ images = new_images
322
+
274
323
  # Extract the valid field values from the JSON.
275
324
  field_maps: list[dict[str, Any]] = []
276
325
  for json_dict in json_list:
277
326
  field_map: dict[str, Any] = {}
278
327
  for key, field in fields_by_name.items():
279
- # Watch out for NaN values or other special values.
328
+ # Watch out for NaN values or other special values in numeric columns.
280
329
  val: Any = json_dict.get(key)
281
330
  if (field.data_field_type == FieldType.DOUBLE
282
331
  and (not isinstance(val, (int, float))) or (isinstance(val, float) and math.isnan(val))):
@@ -284,13 +333,28 @@ class AiHelper:
284
333
  field_map[field.data_field_name] = val
285
334
  field_maps.append(field_map)
286
335
 
336
+ # Create the experiment detail entry.
287
337
  detail_entry = ElnEntryCriteria(ElnEntryType.Table, entry_name,
288
338
  ElnBaseDataType.EXPERIMENT_DETAIL.data_type_name,
289
339
  self.tab_next_entry_order(tab),
290
340
  notebook_experiment_tab_id=tab.tab_id,
291
341
  field_definition_list=fields)
292
342
  entry = self.eln_man.add_experiment_entry(self.exp_id, detail_entry)
293
- self.dr_man.add_data_records_with_data(entry.data_type_name, field_maps)
343
+ records: list[DataRecord] = self.dr_man.add_data_records_with_data(entry.data_type_name, field_maps)
344
+
345
+ # If images are provided, update the data type definition of the experiment detail data type to allow
346
+ # record images and add the images to the records.
347
+ if images:
348
+ dt: DataTypeDefinition = self.dt_man.get_data_type_definition(entry.data_type_name)
349
+ dt.is_record_image_assignable = True
350
+ self.eln_man.update_eln_data_type_definition(self.exp_id, entry.entry_id, dt)
351
+
352
+ layout: DataTypeLayout = self.dt_man.get_default_layout(entry.data_type_name)
353
+ layout.table_layout = TableLayout(cell_size=128, record_image_width=128)
354
+ self.eln_man.update_eln_data_type_layout(self.exp_id, entry.entry_id, layout)
355
+
356
+ self.update_record_images(records, images)
357
+
294
358
  return entry
295
359
 
296
360
  def create_text_entry(self, tab: ElnExperimentTab, timestamp: str, description: str, auto_format: bool = True) \
@@ -538,7 +602,7 @@ class ToolOfToolsHelper:
538
602
  x.field_set_name == "Tool of Tools Progress"]
539
603
  if not progress_field_set:
540
604
  raise SapioException("Unable to locate the field set for the Tool of Tools progress.")
541
- progress_entry_crit = ElnEntryCriteria(ElnEntryType.Form, f"ELaiN: {self.name} Progress",
605
+ progress_entry_crit = ElnEntryCriteria(ElnEntryType.Form, f"{tab_name} Progress",
542
606
  ElnBaseDataType.EXPERIMENT_DETAIL.data_type_name, 1,
543
607
  notebook_experiment_tab_id=self.tab.tab_id,
544
608
  enb_field_set_id=progress_field_set[0].field_set_id)