sapiopycommons 2024.8.27a312__tar.gz → 2024.8.28a313__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 (53) hide show
  1. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/.gitignore +0 -2
  2. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/PKG-INFO +2 -4
  3. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/README.md +0 -1
  4. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/pyproject.toml +2 -2
  5. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/callbacks/callback_util.py +35 -277
  6. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/chem/IndigoMolecules.py +0 -1
  7. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/chem/Molecules.py +0 -1
  8. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/files/file_bridge.py +10 -16
  9. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/files/file_util.py +6 -13
  10. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/files/file_validator.py +0 -71
  11. sapiopycommons-2024.8.28a313/src/sapiopycommons/general/custom_report_util.py +90 -0
  12. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/recordmodel/record_handler.py +45 -278
  13. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/webhook/webhook_handlers.py +1 -58
  14. sapiopycommons-2024.8.27a312/src/sapiopycommons/eln/experiment_report_util.py +0 -214
  15. sapiopycommons-2024.8.27a312/src/sapiopycommons/files/file_bridge_handler.py +0 -318
  16. sapiopycommons-2024.8.27a312/src/sapiopycommons/general/accession_service.py +0 -375
  17. sapiopycommons-2024.8.27a312/src/sapiopycommons/general/custom_report_util.py +0 -262
  18. sapiopycommons-2024.8.27a312/src/sapiopycommons/multimodal/multimodal.py +0 -146
  19. sapiopycommons-2024.8.27a312/src/sapiopycommons/multimodal/multimodal_data.py +0 -487
  20. sapiopycommons-2024.8.27a312/tests/_do_not_add_init_py_here +0 -0
  21. sapiopycommons-2024.8.27a312/tests/accession_test.py +0 -41
  22. sapiopycommons-2024.8.27a312/tests/bio_reg_test.py +0 -26
  23. sapiopycommons-2024.8.27a312/tests/chem_test.py +0 -229
  24. sapiopycommons-2024.8.27a312/tests/data_type_models.py +0 -37332
  25. sapiopycommons-2024.8.27a312/tests/kappa.chains.fasta +0 -45
  26. sapiopycommons-2024.8.27a312/tests/mafft_test.py +0 -66
  27. sapiopycommons-2024.8.27a312/tests/test.gb +0 -124
  28. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/LICENSE +0 -0
  29. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/__init__.py +0 -0
  30. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/callbacks/__init__.py +0 -0
  31. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/chem/__init__.py +0 -0
  32. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/datatype/__init__.py +0 -0
  33. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/datatype/attachment_util.py +0 -0
  34. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/eln/__init__.py +0 -0
  35. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/eln/experiment_handler.py +0 -0
  36. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/eln/plate_designer.py +0 -0
  37. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/files/__init__.py +0 -0
  38. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/files/complex_data_loader.py +0 -0
  39. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/files/file_data_handler.py +0 -0
  40. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/files/file_writer.py +0 -0
  41. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/general/__init__.py +0 -0
  42. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/general/aliases.py +0 -0
  43. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/general/exceptions.py +0 -0
  44. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/general/popup_util.py +0 -0
  45. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/general/storage_util.py +0 -0
  46. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/general/time_util.py +0 -0
  47. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/processtracking/__init__.py +0 -0
  48. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/processtracking/endpoints.py +0 -0
  49. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/recordmodel/__init__.py +0 -0
  50. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/rules/__init__.py +0 -0
  51. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/rules/eln_rule_handler.py +0 -0
  52. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/rules/on_save_rule_handler.py +0 -0
  53. {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/webhook/__init__.py +0 -0
@@ -6,5 +6,3 @@
6
6
  **/*.versionsBackup
7
7
  **/target
8
8
  /sapiopycommons/sapiopycommons.egg-info/
9
- /sapiopycommons/dist**
10
- /.pydevproject
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sapiopycommons
3
- Version: 2024.8.27a312
3
+ Version: 2024.8.28a313
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>
@@ -17,8 +17,7 @@ Classifier: Programming Language :: Python :: 3.10
17
17
  Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
18
18
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
19
  Requires-Python: >=3.10
20
- Requires-Dist: databind>=4.5
21
- Requires-Dist: sapiopylib>=2024.5.24.210
20
+ Requires-Dist: sapiopylib>=2023.12.13.174
22
21
  Description-Content-Type: text/markdown
23
22
 
24
23
 
@@ -51,7 +50,6 @@ This license does not provide any rights to use any other copyrighted artifacts
51
50
  ## Dependencies
52
51
  The following dependencies are required for this package:
53
52
  - [sapiopylib - The official Sapio Informatics Platform Python API package.](https://pypi.org/project/sapiopylib/)
54
- - [databind - Databind is a library inspired by jackson-databind to de-/serialize Python dataclasses.](https://pypi.org/project/databind/)
55
53
 
56
54
  ## Getting Help
57
55
  If you have a support contract with Sapio Sciences, please use our [technical support channels](https://sapio-sciences.atlassian.net/servicedesk/customer/portals).
@@ -28,7 +28,6 @@ This license does not provide any rights to use any other copyrighted artifacts
28
28
  ## Dependencies
29
29
  The following dependencies are required for this package:
30
30
  - [sapiopylib - The official Sapio Informatics Platform Python API package.](https://pypi.org/project/sapiopylib/)
31
- - [databind - Databind is a library inspired by jackson-databind to de-/serialize Python dataclasses.](https://pypi.org/project/databind/)
32
31
 
33
32
  ## Getting Help
34
33
  If you have a support contract with Sapio Sciences, please use our [technical support channels](https://sapio-sciences.atlassian.net/servicedesk/customer/portals).
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sapiopycommons"
7
- version='2024.08.27a312'
7
+ version='2024.08.28a313'
8
8
  authors = [
9
9
  { name="Jonathan Steck", email="jsteck@sapiosciences.com" },
10
10
  { name="Yechen Qiao", email="yqiao@sapiosciences.com" },
@@ -14,7 +14,7 @@ license = "MPL-2.0"
14
14
  readme = "README.md"
15
15
  requires-python = ">=3.10"
16
16
  dependencies = [
17
- 'sapiopylib>=2024.5.24.210', 'databind>=4.5'
17
+ 'sapiopylib>=2023.12.13.174'
18
18
  ]
19
19
  classifiers = [
20
20
  "Intended Audience :: Developers",
@@ -1,5 +1,3 @@
1
- from __future__ import annotations
2
-
3
1
  import io
4
2
  from typing import Any
5
3
 
@@ -11,11 +9,11 @@ from sapiopylib.rest.pojo.DataRecord import DataRecord
11
9
  from sapiopylib.rest.pojo.datatype.DataType import DataTypeDefinition
12
10
  from sapiopylib.rest.pojo.datatype.DataTypeLayout import DataTypeLayout
13
11
  from sapiopylib.rest.pojo.datatype.FieldDefinition import AbstractVeloxFieldDefinition, VeloxStringFieldDefinition, \
14
- VeloxIntegerFieldDefinition, VeloxDoubleFieldDefinition, FieldDefinitionParser
12
+ VeloxIntegerFieldDefinition, VeloxDoubleFieldDefinition
15
13
  from sapiopylib.rest.pojo.webhook.ClientCallbackRequest import OptionDialogRequest, ListDialogRequest, \
16
14
  FormEntryDialogRequest, InputDialogCriteria, TableEntryDialogRequest, ESigningRequestPojo, \
17
- DataRecordDialogRequest, InputSelectionRequest, FilePromptRequest, MultiFilePromptRequest, \
18
- TempTableSelectionRequest
15
+ DataRecordSelectionRequest, DataRecordDialogRequest, InputSelectionRequest, FilePromptRequest, \
16
+ MultiFilePromptRequest
19
17
  from sapiopylib.rest.pojo.webhook.ClientCallbackResult import ESigningResponsePojo
20
18
  from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
21
19
  from sapiopylib.rest.pojo.webhook.WebhookEnums import FormAccessLevel, ScanToSelectCriteria, SearchType
@@ -211,15 +209,15 @@ class CallbackUtil:
211
209
  type_def: DataTypeDefinition = self.dt_cache.get_data_type(data_type)
212
210
  field_defs: dict[str, AbstractVeloxFieldDefinition] = self.dt_cache.get_fields_for_type(data_type)
213
211
 
214
- # Make everything visible, because presumably the caller gave a field name because they want it to be seen.
215
- modifier = FieldModifier(visible=True, editable=editable)
216
-
217
212
  # Build the form using only those fields that are desired.
218
213
  builder = FormBuilder(data_type, type_def.display_name, type_def.plural_display_name)
219
214
  for field_name in fields:
220
215
  field_def = field_defs.get(field_name)
221
216
  if field_def is None:
222
217
  raise SapioException(f"No field of name \"{field_name}\" in field definitions of type \"{data_type}\"")
218
+ if editable is not None:
219
+ field_def.editable = editable
220
+ field_def.visible = True
223
221
  if hasattr(field_def, "default_value"):
224
222
  field_def.default_value = record.get_field_value(field_name)
225
223
  column: int = 0
@@ -228,7 +226,7 @@ class CallbackUtil:
228
226
  position = column_positions.get(field_name)
229
227
  column = position[0]
230
228
  span = position[1]
231
- builder.add_field(modifier.modify_field(field_def), column, span)
229
+ builder.add_field(field_def, column, span)
232
230
 
233
231
  request = FormEntryDialogRequest(title, msg, builder.get_temporary_data_type())
234
232
  response: FieldMap | None = self.callback.show_form_entry_dialog(request)
@@ -261,7 +259,7 @@ class CallbackUtil:
261
259
  :param field_name: The name and display name of the string field.
262
260
  :param default_value: The default value to place into the string field, if any.
263
261
  :param max_length: The max length of the string value. If not provided, uses the length of the default value.
264
- If neither this nor a default value are provided, defaults to 100 characters.
262
+ If neither this or a default value are not provided, defaults to 100 characters.
265
263
  :param editable: Whether the field is editable by the user.
266
264
  :param kwargs: Any additional keyword arguments to pass to the field definition.
267
265
  :return: The string that the user input into the dialog.
@@ -348,13 +346,9 @@ class CallbackUtil:
348
346
  if plural_display_name is None:
349
347
  plural_display_name = display_name + "s"
350
348
 
351
- # Key fields display their columns in order before all non-key fields.
352
- # Unmark key fields so that the column order is respected exactly as the caller provides it.
353
- modifier = FieldModifier(key_field=False)
354
-
355
349
  builder = FormBuilder(data_type, display_name, plural_display_name)
356
- for field in fields:
357
- builder.add_field(modifier.modify_field(field))
350
+ for column in fields:
351
+ builder.add_field(column)
358
352
 
359
353
  request = TableEntryDialogRequest(title, msg, builder.get_temporary_data_type(), values)
360
354
  response: list[FieldMap] | None = self.callback.show_table_entry_dialog(request)
@@ -370,9 +364,8 @@ class CallbackUtil:
370
364
  editable: bool | None = True) -> list[FieldMap]:
371
365
  """
372
366
  Create a table dialog where the user may input data into the fields of the table. The table is constructed from
373
- a given list of records of a singular type. Provided field names must match fields on the definition of the data
374
- type of the given records. The fields that are displayed will have their default value be that of the fields on
375
- the given records.
367
+ a given list of records. Provided field names must match fields on the definition of the data type of the given
368
+ records. The fields that are displayed will have their default value be that of the fields on the given records.
376
369
 
377
370
  Makes webservice calls to get the data type definition and fields of the given records if they weren't
378
371
  previously cached.
@@ -397,181 +390,25 @@ class CallbackUtil:
397
390
  type_def: DataTypeDefinition = self.dt_cache.get_data_type(data_type)
398
391
  field_defs: dict[str, AbstractVeloxFieldDefinition] = self.dt_cache.get_fields_for_type(data_type)
399
392
 
400
- # Key fields display their columns in order before all non-key fields.
401
- # Unmark key fields so that the column order is respected exactly as the caller provides it.
402
- # Also make everything visible, because presumably the caller gave a field name because they want it to be seen.
403
- modifier = FieldModifier(visible=True, key_field=False, editable=editable)
404
-
405
393
  # Build the form using only those fields that are desired.
406
394
  builder = FormBuilder(data_type, type_def.display_name, type_def.plural_display_name)
407
395
  for field_name in fields:
408
396
  field_def = field_defs.get(field_name)
409
397
  if field_def is None:
410
398
  raise SapioException(f"No field of name \"{field_name}\" in field definitions of type \"{data_type}\"")
411
- builder.add_field(modifier.modify_field(field_def))
399
+ if editable is not None:
400
+ field_def.editable = editable
401
+ field_def.visible = True
402
+ # Key fields display their columns in order before all non-key fields.
403
+ # Unmark key fields so that the column order is respected exactly as the caller provides it.
404
+ field_def.key_field = False
405
+ builder.add_field(field_def)
412
406
 
413
407
  request = TableEntryDialogRequest(title, msg, builder.get_temporary_data_type(), field_map_list)
414
408
  response: list[FieldMap] | None = self.callback.show_table_entry_dialog(request)
415
409
  if response is None:
416
410
  raise SapioUserCancelledException()
417
411
  return response
418
-
419
- def multi_type_table_dialog(self,
420
- title: str,
421
- msg: str,
422
- fields: list[(str, str) | AbstractVeloxFieldDefinition],
423
- row_contents: list[list[SapioRecord | FieldMap]],
424
- *,
425
- default_modifier: FieldModifier | None = None,
426
- field_modifiers: dict[str, FieldModifier] | None = None,
427
- data_type: str = "Default",
428
- display_name: str | None = None,
429
- plural_display_name: str | None = None) -> list[FieldMap]:
430
- """
431
- Create a table dialog where the user may input data into the fields of the table. The table is constructed from
432
- a given list of records of multiple data types or field maps. Provided field names must match with field names
433
- from the data type definition of the given records. The fields that are displayed will have their default value
434
- be that of the fields on the given records or field maps.
435
-
436
- Makes webservice calls to get the data type field definitions of the given records if they weren't
437
- previously cached.
438
-
439
- :param title: The title of the dialog.
440
- :param msg: The message to display in the dialog.
441
- :param fields: A list of objects representing the fields in the table. This could either be a two-element tuple
442
- where the first element is a data type name and the second is a field name, or it could be a field
443
- definition. If it is the former, a query will be made to find the field definition matching tht data type.
444
- The data type names of the fields must match the data type names of the records in the row contents.
445
- See the description of row_contents for what to do if you want to construct a field that pulls from a field
446
- map.
447
- If two fields share the same field name, an exception will be thrown. This is even true in the case where
448
- the data type name of the fields is different. If you wish to display two fields from two data types with
449
- the same name, then you must provide a FieldModifier for at least one of the fields where prepend_data_type
450
- is True in order to make that field's name unique again. Note that if you do this for a field, the mapping
451
- of record to field name will use the unedited field name, but the return results of this function will
452
- use the edited field name in the results dictionary for a row.
453
- :param row_contents: A list where each element is another list representing the records or a field map that will
454
- be used to populate the columns of the table. If the data type of a given record doesn't match any of the
455
- data type names of the given fields, then it will not be used.
456
- This list can contain up to one field map, which are fields not tied to a record. This is so that you can
457
- create abstract field definition not tied to a specific record in the system. If you want to define an
458
- abstract field that pulls from the field map in the row contents, then you must set the data type name to
459
- Default.
460
- If a record of a given data type appears more than once in one of the inner-lists of the row contents, or
461
- there is more than one field map, then an exception will be thrown, as there is no way of distinguishing
462
- which record should be used for a field, and not all fields could have their values combined across multiple
463
- records.
464
- The row contents may have an inner-list which is missing a record of a data type that matches one of the
465
- fields. In this case, the field value for that row under that column will be null.
466
- The inner-list does not need to be sorted in any way, as this function will map the inner-list contents to
467
- fields as necessary.
468
- The inner-list may contain null elements; these will simply be discarded by this function.
469
- :param default_modifier: A default field modifier that will be applied to the given fields. This can be used to
470
- make field definitions from the system behave differently than their system values. If this value is None,
471
- then a default field modifier is created that causes all specified fields to be both visible and not key
472
- fields. (Key fields get displayed first before any non-key fields in tables, so the key field setting is
473
- disabled by default in order to have the columns in the table respect the order of the fields as they are
474
- provided to this function.)
475
- :param field_modifiers: A mapping of data field name to field modifier for changes that should be applied to
476
- the matching field. If a data field name is not present in the provided dict, or the provided dictionary is
477
- None, then the default modifier will be used.
478
- :param data_type: The data type name for the temporary data type that will be created for this table.
479
- :param display_name: The display name for the temporary data type. If not provided, defaults to the data type
480
- name.
481
- :param plural_display_name: The plural display name for the temporary data type. If not provided, defaults to
482
- the display name + "s".
483
- :return: A list of dictionaries mapping the data field names of the given field definitions to the response
484
- value from the user for that field for each row.
485
- """
486
- # Set the default modifier to make all fields visible and not key if no default was provided.
487
- if default_modifier is None:
488
- default_modifier = FieldModifier(visible=True, key_field=False)
489
- # To make things simpler, treat null field modifiers as an empty dict.
490
- if field_modifiers is None:
491
- field_modifiers = {}
492
-
493
- # Construct the final fields list from the possible field objects.
494
- final_fields: list[AbstractVeloxFieldDefinition] = []
495
- # Keep track of whether any given field name appears more than once, as two fields could have the same
496
- # field name but different data types. In this case, the user should provide a field modifier or field
497
- # definition that changes one of the field names.
498
- field_names: list[str] = []
499
- for field in fields:
500
- # Find the field definition for this field object.
501
- if isinstance(field, tuple):
502
- field_def: AbstractVeloxFieldDefinition = self.dt_cache.get_fields_for_type(field[0]).get(field[1])
503
- elif isinstance(field, AbstractVeloxFieldDefinition):
504
- field_def: AbstractVeloxFieldDefinition = field
505
- else:
506
- raise SapioException("Unrecognized field object.")
507
-
508
- # Locate the modifier for this field and store the modified field.
509
- name: str = field_def.data_field_name
510
- modifier: FieldModifier = field_modifiers.get(name, default_modifier)
511
- field_def: AbstractVeloxFieldDefinition = modifier.modify_field(field_def)
512
- final_fields.append(field_def)
513
-
514
- # Verify that this field name isn't a duplicate.
515
- # The field name may have changed due to the modifier.
516
- name: str = field_def.data_field_name
517
- if name in field_names:
518
- raise SapioException(f"The field name \"{name}\" appears more than once in the given fields. "
519
- f"If you have provided two fields with the same name but different data types, "
520
- f"consider providing a FieldModifier where prepend_data_type is true for one of "
521
- f"the fields so that the field names will be different.")
522
- field_names.append(name)
523
-
524
- # Get the values for each row.
525
- values: list[dict[str, Any]] = []
526
- for row in row_contents:
527
- # The final values for this row:
528
- row_values: dict[str, Any] = {}
529
-
530
- # Map the records for this row by their data type. If a field map is provided, its data type is Default.
531
- row_records: dict[str, SapioRecord | FieldMap] = {}
532
- for rec in row:
533
- # Toss out null elements.
534
- if rec is None:
535
- continue
536
- # Map records to their data type name. Map field maps to Default.
537
- dt: str = "Default" if isinstance(rec, dict) else rec.data_type_name
538
- # Warn if the same data type name appears more than once.
539
- if dt in row_records:
540
- raise SapioException(f"The data type \"{dt}\" appears more than once in the given row contents.")
541
- row_records[dt] = rec
542
-
543
- # Get the field values from the above records.
544
- for field in final_fields:
545
- # Find the object that corresponds to this field given its data type name.
546
- record: SapioRecord | FieldMap | None = row_records.get(field.data_type_name)
547
- # This could be either a record, a field map, or null. Convert any records to field maps.
548
- if not isinstance(record, dict) and record is not None:
549
- record: FieldMap | None = AliasUtil.to_field_map_lists([record])[0]
550
-
551
- # Find out if this field had its data type prepended to it. If this is the case, then we need to find
552
- # the true data field name before retrieving the value from the field map.
553
- name: str = field.data_field_name
554
- if field_modifiers.get(name, default_modifier).prepend_data_type is True:
555
- name = name.split(".")[1]
556
-
557
- # Set the value for this particular field.
558
- row_values[field.data_field_name] = record.get(name) if record else None
559
- values.append(row_values)
560
-
561
- if display_name is None:
562
- display_name = data_type
563
- if plural_display_name is None:
564
- plural_display_name = display_name + "s"
565
-
566
- builder = FormBuilder(data_type, display_name, plural_display_name)
567
- for field in final_fields:
568
- builder.add_field(field)
569
-
570
- request = TableEntryDialogRequest(title, msg, builder.get_temporary_data_type(), values)
571
- response: list[FieldMap] | None = self.callback.show_table_entry_dialog(request)
572
- if response is None:
573
- raise SapioUserCancelledException()
574
- return response
575
412
 
576
413
  def record_view_dialog(self,
577
414
  title: str,
@@ -627,8 +464,7 @@ class CallbackUtil:
627
464
  values: list[FieldMap],
628
465
  multi_select: bool = True,
629
466
  *,
630
- data_type: str = "Default",
631
- display_name: str | None = None,
467
+ display_name: str = "Default",
632
468
  plural_display_name: str | None = None) -> list[FieldMap]:
633
469
  """
634
470
  Create a selection dialog for a list of field maps for the user to choose from. Requires that the caller
@@ -639,25 +475,18 @@ class CallbackUtil:
639
475
  they are provided in this list.
640
476
  :param values: The values to set for each row of the table.
641
477
  :param multi_select: Whether the user is able to select multiple rows from the list.
642
- :param data_type: The data type name for the temporary data type that will be created for this table.
643
- :param display_name: The display name for the temporary data type. If not provided, defaults to the data type
644
- name.
478
+ :param display_name: The display name for the temporary data type that will be created.
645
479
  :param plural_display_name: The plural display name for the temporary data type. If not provided, defaults to
646
480
  the display name + "s".
647
481
  :return: A list of field maps corresponding to the chosen input field maps.
648
482
  """
649
- if display_name is None:
650
- display_name = data_type
651
483
  if plural_display_name is None:
652
484
  plural_display_name = display_name + "s"
653
485
 
654
- builder = FormBuilder(data_type, display_name, plural_display_name)
655
- for field in fields:
656
- builder.add_field(field)
657
-
658
- request = TempTableSelectionRequest(builder.get_temporary_data_type(), msg, values,
659
- multi_select=multi_select)
660
- response: list[FieldMap] | None = self.callback.show_temp_table_selection_dialog(request)
486
+ # Build the form using only those fields that are desired.
487
+ request = DataRecordSelectionRequest(display_name, plural_display_name,
488
+ fields, values, msg, multi_select)
489
+ response: list[FieldMap] | None = self.callback.show_data_record_selection_dialog(request)
661
490
  if response is None:
662
491
  raise SapioUserCancelledException()
663
492
  return response
@@ -692,22 +521,21 @@ class CallbackUtil:
692
521
  type_def: DataTypeDefinition = self.dt_cache.get_data_type(data_type)
693
522
  field_defs: dict[str, AbstractVeloxFieldDefinition] = self.dt_cache.get_fields_for_type(data_type)
694
523
 
695
- # Key fields display their columns in order before all non-key fields.
696
- # Unmark key fields so that the column order is respected exactly as the caller provides it.
697
- # Also make everything visible, because presumably the caller give a field name because they want it to be seen.
698
- modifier = FieldModifier(visible=True, key_field=False)
699
-
700
524
  # Build the form using only those fields that are desired.
701
- builder = FormBuilder(data_type, type_def.display_name, type_def.plural_display_name)
525
+ field_def_list: list = []
702
526
  for field_name in fields:
703
527
  field_def = field_defs.get(field_name)
704
528
  if field_def is None:
705
529
  raise SapioException(f"No field of name \"{field_name}\" in field definitions of type \"{data_type}\"")
706
- builder.add_field(modifier.modify_field(field_def))
707
-
708
- request = TempTableSelectionRequest(builder.get_temporary_data_type(), msg, field_map_list,
709
- multi_select=multi_select)
710
- response: list[FieldMap] | None = self.callback.show_temp_table_selection_dialog(request)
530
+ field_def.visible = True
531
+ # Key fields display their columns in order before all non-key fields.
532
+ # Unmark key fields so that the column order is respected exactly as the caller provides it.
533
+ field_def.key_field = False
534
+ field_def_list.append(field_def)
535
+
536
+ request = DataRecordSelectionRequest(type_def.display_name, type_def.plural_display_name,
537
+ field_def_list, field_map_list, msg, multi_select)
538
+ response: list[FieldMap] | None = self.callback.show_data_record_selection_dialog(request)
711
539
  if response is None:
712
540
  raise SapioUserCancelledException()
713
541
  # Map the field maps in the response back to the record they come from, returning the chosen record instead of
@@ -908,73 +736,3 @@ class CallbackUtil:
908
736
  """
909
737
  data = io.StringIO(file_data) if isinstance(file_data, str) else io.BytesIO(file_data)
910
738
  self.callback.send_file(file_name, False, data)
911
-
912
-
913
- class FieldModifier:
914
- """
915
- A FieldModifier can be used to update the settings of a field definition from the system.
916
- """
917
- prepend_data_type: bool
918
- display_name: str | None
919
- required: bool | None
920
- editable: bool | None
921
- visible: bool | None
922
- key_field: bool | None
923
- column_width: int | None
924
-
925
- def __init__(self, *, prepend_data_type: bool = False,
926
- display_name: str | None = None, required: bool | None = None, editable: bool | None = None,
927
- visible: bool | None = None, key_field: bool | None = None, column_width: int | None = None):
928
- """
929
- If any values are given as None then that value will not be changed on the given field.
930
-
931
- :param prepend_data_type: If true, prepends the data type name of the field to the data field name. For example,
932
- if a field has a data type name X and a data field name Y, then the field name would become "X.Y". This is
933
- useful for cases where you have the same field name on two different data types and want to distinguish one
934
- or both of them.
935
- :param display_name: Change the display name.
936
- :param required: Change the required status.
937
- :param editable: Change the editable status.
938
- :param visible: Change the visible status.
939
- :param key_field: Change the key field status.
940
- :param column_width: Change the column width.
941
- """
942
- self.prepend_data_type = prepend_data_type
943
- self.display_name = display_name
944
- self.required = required
945
- self.editable = editable
946
- self.visible = visible
947
- self.key_field = key_field
948
- self.column_width = column_width
949
-
950
- def modify_field(self, field: AbstractVeloxFieldDefinition) -> AbstractVeloxFieldDefinition:
951
- """
952
- Apply modifications to a given field.
953
-
954
- :param field: The field to modify.
955
- :return: A copy of the input field with the modifications applied.
956
- """
957
- field = copy_field(field)
958
- if self.prepend_data_type is True:
959
- field._data_field_name = field.data_field_name + "." + field.data_field_name
960
- if self.display_name is not None:
961
- field.display_name = self.display_name
962
- if self.required is not None:
963
- field.required = self.required
964
- if self.editable is not None:
965
- field.editable = self.editable
966
- if self.visible is not None:
967
- field.visible = self.visible
968
- if self.key_field is not None:
969
- field.key_field = self.key_field
970
- if self.column_width is not None:
971
- field.default_table_column_width = self.column_width
972
- return field
973
-
974
-
975
- def copy_field(field: AbstractVeloxFieldDefinition) -> AbstractVeloxFieldDefinition:
976
- """
977
- Create a copy of a given field definition. This is used to modify field definitions from the server for existing
978
- data types without also modifying the field definition in the cache.
979
- """
980
- return FieldDefinitionParser.to_field_definition(field.to_json())
@@ -6,7 +6,6 @@ indigo = Indigo()
6
6
  renderer = IndigoRenderer(indigo)
7
7
  indigo.setOption("render-output-format", "svg")
8
8
  indigo.setOption("ignore-stereochemistry-errors", True)
9
- indigo.setOption("render-stereo-style", "ext")
10
9
  indigo.setOption("aromaticity-model", "generic")
11
10
  indigo.setOption("render-coloring", True)
12
11
  indigo_inchi = IndigoInchi(indigo);
@@ -181,7 +181,6 @@ def mol_to_sapio_substance(mol: Mol, include_stereoisomers: bool = False,
181
181
  # We need to test the INCHI can be loaded back to indigo.
182
182
  indigo_mol = indigo.loadMolecule(molBlock)
183
183
  indigo_mol.aromatize()
184
- indigo_inchi.resetOptions()
185
184
  indigo_inchi_str = indigo_inchi.getInchi(indigo_mol)
186
185
  molecule["inchi"] = indigo_inchi_str
187
186
  indigo_inchi_key_str = indigo_inchi.getInchiKey(indigo_inchi_str)
@@ -16,8 +16,7 @@ class FileBridge:
16
16
  Read a file from FileBridge.
17
17
 
18
18
  :param context: The current webhook context or a user object to send requests from.
19
- :param bridge_name: The name of the bridge to use. This is the "connection name" in the
20
- file bridge configurations.
19
+ :param bridge_name: The name of the bridge to use.
21
20
  :param file_path: The path to read the file from.
22
21
  :param base64_decode: If true, base64 decode the file. Files are by default base64 encoded when retrieved from
23
22
  FileBridge.
@@ -43,8 +42,7 @@ class FileBridge:
43
42
  Write a file to FileBridge.
44
43
 
45
44
  :param context: The current webhook context or a user object to send requests from.
46
- :param bridge_name: The name of the bridge to use. This is the "connection name" in the
47
- file bridge configurations.
45
+ :param bridge_name: The name of the bridge to use.
48
46
  :param file_path: The path to write the file to. If a file already exists at the given path then the file is
49
47
  overwritten.
50
48
  :param file_data: A string or bytes of the file to be written.
@@ -65,10 +63,9 @@ class FileBridge:
65
63
  List the contents of a FileBridge directory.
66
64
 
67
65
  :param context: The current webhook context or a user object to send requests from.
68
- :param bridge_name: The name of the bridge to use. This is the "connection name" in the
69
- file bridge configurations.
66
+ :param bridge_name: The name of the bridge to use.
70
67
  :param file_path: The path to read the directory from.
71
- :return: A list of names of files and folders in the directory.
68
+ :return: A list of name of files and folders in the directory.
72
69
  """
73
70
  sub_path = '/ext/filebridge/listDirectory'
74
71
  params = {
@@ -80,7 +77,7 @@ class FileBridge:
80
77
 
81
78
  response_body: list[str] = response.json()
82
79
  path_length = len(f"bridge://{bridge_name}/")
83
- return [urllib.parse.unquote(value)[path_length:] for value in response_body]
80
+ return [urllib.parse.unquote(value[path_length:]) for value in response_body]
84
81
 
85
82
  @staticmethod
86
83
  def create_directory(context: SapioWebhookContext | SapioUser, bridge_name: str, file_path: str) -> None:
@@ -88,8 +85,7 @@ class FileBridge:
88
85
  Create a new directory in FileBridge.
89
86
 
90
87
  :param context: The current webhook context or a user object to send requests from.
91
- :param bridge_name: The name of the bridge to use. This is the "connection name" in the
92
- file bridge configurations.
88
+ :param bridge_name: The name of the bridge to use.
93
89
  :param file_path: The path to create the directory at. If a directory already exists at the given path then an
94
90
  exception is raised.
95
91
  """
@@ -107,8 +103,7 @@ class FileBridge:
107
103
  Delete an existing file in FileBridge.
108
104
 
109
105
  :param context: The current webhook context or a user object to send requests from.
110
- :param bridge_name: The name of the bridge to use. This is the "connection name" in the
111
- file bridge configurations.
106
+ :param bridge_name: The name of the bridge to use.
112
107
  :param file_path: The path to the file to delete.
113
108
  """
114
109
  sub_path = '/ext/filebridge/deleteFile'
@@ -116,7 +111,7 @@ class FileBridge:
116
111
  'Filepath': f"bridge://{bridge_name}/{file_path}"
117
112
  }
118
113
  user: SapioUser = context if isinstance(context, SapioUser) else context.user
119
- response = user.delete(sub_path, params=params)
114
+ response = user.post(sub_path, params=params)
120
115
  user.raise_for_status(response)
121
116
 
122
117
  @staticmethod
@@ -125,8 +120,7 @@ class FileBridge:
125
120
  Delete an existing directory in FileBridge.
126
121
 
127
122
  :param context: The current webhook context or a user object to send requests from.
128
- :param bridge_name: The name of the bridge to use. This is the "connection name" in the
129
- file bridge configurations.
123
+ :param bridge_name: The name of the bridge to use.
130
124
  :param file_path: The path to the directory to delete.
131
125
  """
132
126
  sub_path = '/ext/filebridge/deleteDirectory'
@@ -134,5 +128,5 @@ class FileBridge:
134
128
  'Filepath': f"bridge://{bridge_name}/{file_path}"
135
129
  }
136
130
  user: SapioUser = context if isinstance(context, SapioUser) else context.user
137
- response = user.delete(sub_path, params=params)
131
+ response = user.post(sub_path, params=params)
138
132
  user.raise_for_status(response)
@@ -21,7 +21,7 @@ class FileUtil:
21
21
  """
22
22
  @staticmethod
23
23
  def tokenize_csv(file_bytes: bytes, required_headers: list[str] | None = None, header_row_index: int | None = 0,
24
- seperator: str = ",", *, encoding: str | None = None) -> tuple[list[dict[str, str]], list[list[str]]]:
24
+ seperator: str = ",") -> tuple[list[dict[str, str]], list[list[str]]]:
25
25
  """
26
26
  Tokenize a CSV file. The provided file must be uniform. That is, if row 1 has 10 cells, all the rows in the file
27
27
  must have 10 cells. Otherwise, the Pandas parser throws a tokenizer exception.
@@ -34,17 +34,13 @@ class FileUtil:
34
34
  meaning that required headers are also ignored if any are provided. By default, the first row (0th index)
35
35
  is assumed to be the header row.
36
36
  :param seperator: The character that separates cells in the table.
37
- :param encoding: The encoding used to read the given file bytes. If not provided, uses utf-8. If your file
38
- contains a non-utf-8 character, then a UnicodeDecodeError will be thrown. If this happens, consider using
39
- ISO-8859-1 as the encoding.
40
37
  :return: The CSV parsed into a list of dicts where each dict is a row, mapping the headers to the cells for
41
38
  that row. Also returns a list of each row above the headers (the metadata), parsed into a list of each cell.
42
39
  If the header row index is 0 or None, this list will be empty.
43
40
  """
44
41
  # Parse the file bytes into two DataFrames. The first is metadata of the file located above the header row,
45
42
  # while the second is the body of the file below the header row.
46
- file_body, file_metadata = FileUtil.csv_to_data_frames(file_bytes, header_row_index, seperator,
47
- encoding=encoding)
43
+ file_body, file_metadata = FileUtil.csv_to_data_frames(file_bytes, header_row_index, seperator)
48
44
  # Parse the metadata from above the header row index into a list of lists.
49
45
  metadata: list[list[str]] = FileUtil.data_frame_to_lists(file_metadata)
50
46
  # Parse the data from the file body into a list of dicts.
@@ -78,8 +74,8 @@ class FileUtil:
78
74
  return rows, metadata
79
75
 
80
76
  @staticmethod
81
- def csv_to_data_frames(file_bytes: bytes, header_row_index: int | None = 0, seperator: str = ",",
82
- *, encoding: str | None = None) -> tuple[DataFrame, DataFrame | None]:
77
+ def csv_to_data_frames(file_bytes: bytes, header_row_index: int | None = 0, seperator: str = ",") \
78
+ -> tuple[DataFrame, DataFrame | None]:
83
79
  """
84
80
  Parse the file bytes for a CSV into DataFrames. The provided file must be uniform. That is, if row 1 has 10
85
81
  cells, all the rows in the file must have 10 cells. Otherwise, the Pandas parser throws a tokenizer exception.
@@ -90,9 +86,6 @@ class FileUtil:
90
86
  meaning that required headers are also ignored if any are provided. By default, the first row (0th index)
91
87
  is assumed to be the header row.
92
88
  :param seperator: The character that separates cells in the table.
93
- :param encoding: The encoding used to read the given file bytes. If not provided, uses utf-8. If your file
94
- contains a non-utf-8 character, then a UnicodeDecodeError will be thrown. If this happens, consider using
95
- ISO-8859-1 as the encoding.
96
89
  :return: A tuple of two DataFrames. The first is the frame for the CSV table body, while the second is for the
97
90
  metadata from above the header row, or None if there is no metadata.
98
91
  """
@@ -104,13 +97,13 @@ class FileUtil:
104
97
  # can throw off the header row index.
105
98
  file_metadata = pandas.read_csv(file_io, header=None, dtype=dtype(str),
106
99
  skiprows=lambda x: x >= header_row_index,
107
- skip_blank_lines=False, sep=seperator, encoding=encoding)
100
+ skip_blank_lines=False, sep=seperator)
108
101
  with io.BytesIO(file_bytes) as file_io:
109
102
  # The use of the dtype argument is to ensure that everything from the file gets read as a string. Added
110
103
  # because some numerical values would get ".0" appended to them, even when casting the DataFrame cell to a
111
104
  # string.
112
105
  file_body: DataFrame = pandas.read_csv(file_io, header=header_row_index, dtype=dtype(str),
113
- skip_blank_lines=False, sep=seperator, encoding=encoding)
106
+ skip_blank_lines=False, sep=seperator)
114
107
 
115
108
  return file_body, file_metadata
116
109