sapiopycommons 2025.4.17a486__tar.gz → 2025.4.17a487__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.
Files changed (86) hide show
  1. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/PKG-INFO +1 -1
  2. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/pyproject.toml +1 -1
  3. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/eln/experiment_handler.py +100 -208
  4. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/general/aliases.py +0 -2
  5. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/.gitignore +0 -0
  6. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/LICENSE +0 -0
  7. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/README.md +0 -0
  8. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/__init__.py +0 -0
  9. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/callbacks/__init__.py +0 -0
  10. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/callbacks/callback_util.py +0 -0
  11. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/callbacks/field_builder.py +0 -0
  12. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/chem/IndigoMolecules.py +0 -0
  13. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/chem/Molecules.py +0 -0
  14. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/chem/__init__.py +0 -0
  15. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/customreport/__init__.py +0 -0
  16. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/customreport/auto_pagers.py +0 -0
  17. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/customreport/column_builder.py +0 -0
  18. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/customreport/custom_report_builder.py +0 -0
  19. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/customreport/term_builder.py +0 -0
  20. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/datatype/__init__.py +0 -0
  21. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/datatype/attachment_util.py +0 -0
  22. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/datatype/data_fields.py +0 -0
  23. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/datatype/pseudo_data_types.py +0 -0
  24. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/eln/__init__.py +0 -0
  25. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/eln/experiment_cache.py +0 -0
  26. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/eln/experiment_report_util.py +0 -0
  27. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/eln/experiment_step_factory.py +0 -0
  28. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/eln/experiment_tags.py +0 -0
  29. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/eln/plate_designer.py +0 -0
  30. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/eln/step_creation.py +0 -0
  31. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/files/__init__.py +0 -0
  32. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/files/complex_data_loader.py +0 -0
  33. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/files/file_bridge.py +0 -0
  34. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/files/file_bridge_handler.py +0 -0
  35. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/files/file_data_handler.py +0 -0
  36. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/files/file_util.py +0 -0
  37. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/files/file_validator.py +0 -0
  38. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/files/file_writer.py +0 -0
  39. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/flowcyto/flow_cyto.py +0 -0
  40. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/flowcyto/flowcyto_data.py +0 -0
  41. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/general/__init__.py +0 -0
  42. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/general/accession_service.py +0 -0
  43. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/general/audit_log.py +0 -0
  44. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/general/custom_report_util.py +0 -0
  45. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/general/data_structure_util.py +0 -0
  46. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/general/directive_util.py +0 -0
  47. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/general/exceptions.py +0 -0
  48. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/general/popup_util.py +0 -0
  49. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/general/sapio_links.py +0 -0
  50. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/general/storage_util.py +0 -0
  51. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/general/time_util.py +0 -0
  52. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/multimodal/multimodal.py +0 -0
  53. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/multimodal/multimodal_data.py +0 -0
  54. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/processtracking/__init__.py +0 -0
  55. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/processtracking/custom_workflow_handler.py +0 -0
  56. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/processtracking/endpoints.py +0 -0
  57. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/recordmodel/__init__.py +0 -0
  58. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/recordmodel/record_handler.py +0 -0
  59. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/rules/__init__.py +0 -0
  60. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/rules/eln_rule_handler.py +0 -0
  61. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/rules/on_save_rule_handler.py +0 -0
  62. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/samples/aliquot.py +0 -0
  63. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/sftpconnect/__init__.py +0 -0
  64. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/sftpconnect/sftp_builder.py +0 -0
  65. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/webhook/__init__.py +0 -0
  66. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/webhook/webhook_context.py +0 -0
  67. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/webhook/webhook_handlers.py +0 -0
  68. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/src/sapiopycommons/webhook/webservice_handlers.py +0 -0
  69. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/tests/AF-A0A009IHW8-F1-model_v4.cif +0 -0
  70. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/tests/_do_not_add_init_py_here +0 -0
  71. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/tests/accession_test.py +0 -0
  72. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/tests/aliquot_test.py +0 -0
  73. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/tests/bio_reg_test.py +0 -0
  74. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/tests/chem_test.py +0 -0
  75. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/tests/chem_test_curation_queue.py +0 -0
  76. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/tests/curation_queue_test.sdf +0 -0
  77. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/tests/data_type_models.py +0 -0
  78. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/tests/flowcyto/101_DEN084Y5_15_E01_008_clean.fcs +0 -0
  79. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/tests/flowcyto/101_DEN084Y5_15_E03_009_clean.fcs +0 -0
  80. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/tests/flowcyto/101_DEN084Y5_15_E05_010_clean.fcs +0 -0
  81. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/tests/flowcyto/8_color_ICS.wsp +0 -0
  82. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/tests/flowcyto/COVID19_W_001_O.fcs +0 -0
  83. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/tests/flowcyto_test.py +0 -0
  84. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/tests/kappa.chains.fasta +0 -0
  85. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/tests/mafft_test.py +0 -0
  86. {sapiopycommons-2025.4.17a486 → sapiopycommons-2025.4.17a487}/tests/test.gb +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sapiopycommons
3
- Version: 2025.4.17a486
3
+ Version: 2025.4.17a487
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.04.17a486'
7
+ version='2025.04.17a487'
8
8
  authors = [
9
9
  { name="Jonathan Steck", email="jsteck@sapiosciences.com" },
10
10
  { name="Yechen Qiao", email="yqiao@sapiosciences.com" },
@@ -8,7 +8,7 @@ from weakref import WeakValueDictionary
8
8
  from sapiopycommons.eln.experiment_cache import ExperimentCacheManager
9
9
  from sapiopycommons.eln.experiment_report_util import ExperimentReportUtil
10
10
  from sapiopycommons.general.aliases import AliasUtil, SapioRecord, ExperimentIdentifier, UserIdentifier, \
11
- DataTypeIdentifier, RecordModel, ExperimentEntryIdentifier, TabIdentifier
11
+ DataTypeIdentifier, RecordModel, ExperimentEntryIdentifier
12
12
  from sapiopycommons.general.exceptions import SapioException
13
13
  from sapiopycommons.general.time_util import TimeUtil
14
14
  from sapiopycommons.recordmodel.record_handler import RecordHandler
@@ -40,8 +40,11 @@ from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
40
40
  from sapiopylib.rest.utils.recordmodel.properties import Child
41
41
 
42
42
  Step: TypeAlias = str | ExperimentEntryIdentifier
43
- """An object representing an identifier to an ElnEntryStep. May be either the name of the step or the ElnEntryStep
44
- itself."""
43
+ """An object representing an identifier to an entry in a particular experiment. This may be the name of the experiment,
44
+ or a typical experiment entry identifier."""
45
+ Tab: TypeAlias = int | str | ElnExperimentTab
46
+ """An object representing an identifier to a tab in a particular experiment. This may be the tab's order, its name,
47
+ or the tab object itself."""
45
48
 
46
49
 
47
50
  # FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
@@ -686,7 +689,7 @@ class ExperimentHandler:
686
689
  # Re-sort the steps in case any new steps were added before the last time that this was called.
687
690
  def sort_steps(step: ElnEntryStep) -> tuple:
688
691
  entry = step.eln_entry
689
- tab_order: int = self.get_tab(entry.notebook_experiment_tab_id).tab_order
692
+ tab_order: int = self.get_tab_for_step(step).tab_order
690
693
  entry_order: int = entry.order
691
694
  column_order: int = entry.column_order
692
695
  return tab_order, entry_order, column_order
@@ -1114,128 +1117,45 @@ class ExperimentHandler:
1114
1117
  If you wish to add options to the existing map of options that an entry has, use the
1115
1118
  add_step_options method.
1116
1119
  """
1117
- # FR-47468: Deprecating this since the parameters are ordered. The new method requires keyword parameters, so
1118
- # that we can add new parameters wherever we want without breaking existing code.
1119
- warnings.warn("Update step is deprecated. Use force_entry_update_params instead.",
1120
+ # FR-47468: Deprecating this since the entry-specific update criteria should be used instead.
1121
+ warnings.warn("Update step is deprecated. Use force_entry_update instead.",
1120
1122
  DeprecationWarning)
1121
- self.force_step_update_params(step,
1122
- entry_name=entry_name,
1123
- related_entry_set=related_entry_set,
1124
- dependency_set=dependency_set,
1125
- entry_status=entry_status,
1126
- order=order,
1127
- description=description,
1128
- requires_grabber_plugin=requires_grabber_plugin,
1129
- is_initialization_required=is_initialization_required,
1130
- notebook_experiment_tab_id=notebook_experiment_tab_id,
1131
- entry_height=entry_height,
1132
- column_order=column_order,
1133
- column_span=column_span,
1134
- is_removable=is_removable,
1135
- is_renamable=is_renamable,
1136
- source_entry_id=source_entry_id,
1137
- clear_source_entry_id=clear_source_entry_id,
1138
- is_hidden=is_hidden,
1139
- is_static_view=is_static_View,
1140
- is_shown_in_template=is_shown_in_template,
1141
- template_item_fulfilled_timestamp=template_item_fulfilled_timestamp,
1142
- clear_template_item_fulfilled_timestamp=clear_template_item_fulfilled_timestamp,
1143
- entry_options_map=entry_options_map)
1144
-
1145
- # FR-47468: Some functions that can help with entry updates.
1146
- def force_step_update_params(self, step: Step, *,
1147
- entry_name: str | None = None,
1148
- related_entry_set: Iterable[int] | None = None,
1149
- dependency_set: Iterable[int] | None = None,
1150
- entry_status: ExperimentEntryStatus | None = None,
1151
- order: int | None = None,
1152
- description: str | None = None,
1153
- requires_grabber_plugin: bool | None = None,
1154
- is_initialization_required: bool | None = None,
1155
- notebook_experiment_tab_id: int | None = None,
1156
- entry_height: int | None = None,
1157
- column_order: int | None = None,
1158
- column_span: int | None = None,
1159
- is_removable: bool | None = None,
1160
- is_renamable: bool | None = None,
1161
- source_entry_id: int | None = None,
1162
- clear_source_entry_id: bool | None = None,
1163
- is_hidden: bool | None = None,
1164
- is_static_view: bool | None = None,
1165
- is_shown_in_template: bool | None = None,
1166
- template_item_fulfilled_timestamp: int | None = None,
1167
- clear_template_item_fulfilled_timestamp: bool | None = None,
1168
- entry_options_map: dict[str, str] | None = None) -> None:
1169
- """
1170
- Immediately sent an update to an entry in this experiment. All changes will be reflected by the ExperimentEntry
1171
- of the Step that is being updated.
1123
+ step: ElnEntryStep = self.__to_eln_step(step)
1124
+ update = AbstractElnEntryUpdateCriteria(step.eln_entry.entry_type)
1172
1125
 
1173
- Consider using store_step_update and commit_step_updates instead if the update does not need to be immediate.
1126
+ # These two variables could be iterables that aren't lists. Convert them to plain
1127
+ # lists, since that's what the update criteria is expecting.
1128
+ if related_entry_set is not None:
1129
+ related_entry_set = list(related_entry_set)
1130
+ if dependency_set is not None:
1131
+ dependency_set = list(dependency_set)
1174
1132
 
1175
- If no step functions have been called before and a step is being searched for by name, queries for the
1176
- list of steps in the experiment and caches them.
1133
+ update.entry_name = entry_name
1134
+ update.related_entry_set = related_entry_set
1135
+ update.dependency_set = dependency_set
1136
+ update.entry_status = entry_status
1137
+ update.order = order
1138
+ update.description = description
1139
+ update.requires_grabber_plugin = requires_grabber_plugin
1140
+ update.is_initialization_required = is_initialization_required
1141
+ update.notebook_experiment_tab_id = notebook_experiment_tab_id
1142
+ update.entry_height = entry_height
1143
+ update.column_order = column_order
1144
+ update.column_span = column_span
1145
+ update.is_removable = is_removable
1146
+ update.is_renamable = is_renamable
1147
+ update.source_entry_id = source_entry_id
1148
+ update.clear_source_entry_id = clear_source_entry_id
1149
+ update.is_hidden = is_hidden
1150
+ update.is_static_View = is_static_View
1151
+ update.is_shown_in_template = is_shown_in_template
1152
+ update.template_item_fulfilled_timestamp = template_item_fulfilled_timestamp
1153
+ update.clear_template_item_fulfilled_timestamp = clear_template_item_fulfilled_timestamp
1154
+ update.entry_options_map = entry_options_map
1177
1155
 
1178
- :param step:
1179
- The entry step to update.
1180
- The step may be provided as either a string for the name of the step or an ElnEntryStep.
1181
- If given a name, throws an exception if no step of the given name exists in the experiment.
1182
- :param entry_name: The new name of this entry.
1183
- :param related_entry_set: The new set of entry IDs for the entries that are related (implicitly dependent) to
1184
- this entry. Completely overwrites the existing related entries.
1185
- :param dependency_set: The new set of entry IDs for the entries that are dependent (explicitly dependent) on
1186
- this entry. Completely overwrites the existing dependent entries.
1187
- :param entry_status: The new status of this entry.
1188
- :param order: The row order of this entry in its tab.
1189
- :param description: The new description of this entry.
1190
- :param requires_grabber_plugin: Whether this entry's initialization is handled by a grabber plugin. If true,
1191
- then is_initialization_required is forced to true by the server.
1192
- :param is_initialization_required: Whether the user is required to manually initialize this entry.
1193
- :param notebook_experiment_tab_id: The ID of the tab that this entry should appear on.
1194
- :param entry_height: The height of this entry.
1195
- :param column_order: The column order of this entry.
1196
- :param column_span: How many columns this entry spans.
1197
- :param is_removable: Whether this entry can be removed by the user.
1198
- :param is_renamable: Whether this entry can be renamed by the user.
1199
- :param source_entry_id: The ID of this entry from its template.
1200
- :param clear_source_entry_id: True if the source entry ID should be cleared.
1201
- :param is_hidden: Whether this entry is hidden from the user.
1202
- :param is_static_view: Whether this entry is static. Static entries are uneditable and shared across all
1203
- experiments of the same template.
1204
- :param is_shown_in_template: Whether this entry is saved to and shown in the experiment's template.
1205
- :param template_item_fulfilled_timestamp: A timestamp in milliseconds for when this entry was initialized.
1206
- :param clear_template_item_fulfilled_timestamp: True if the template item fulfilled timestamp should be cleared,
1207
- uninitializing the entry.
1208
- :param entry_options_map:
1209
- The new map of options for this entry. Completely overwrites the existing options map.
1210
- Any changes to the entry options will update this ExperimentHandler's cache of entry options.
1211
- If you wish to add options to the existing map of options that an entry has, use the
1212
- add_step_options method.
1213
- """
1214
- update = self._criteria_from_params(step,
1215
- entry_name=entry_name,
1216
- related_entry_set=related_entry_set,
1217
- dependency_set=dependency_set,
1218
- entry_status=entry_status,
1219
- order=order,
1220
- description=description,
1221
- requires_grabber_plugin=requires_grabber_plugin,
1222
- is_initialization_required=is_initialization_required,
1223
- notebook_experiment_tab_id=notebook_experiment_tab_id,
1224
- entry_height=entry_height,
1225
- column_order=column_order,
1226
- column_span=column_span,
1227
- is_removable=is_removable,
1228
- is_renamable=is_renamable,
1229
- source_entry_id=source_entry_id,
1230
- clear_source_entry_id=clear_source_entry_id,
1231
- is_hidden=is_hidden,
1232
- is_static_view=is_static_view,
1233
- is_shown_in_template=is_shown_in_template,
1234
- template_item_fulfilled_timestamp=template_item_fulfilled_timestamp,
1235
- clear_template_item_fulfilled_timestamp=clear_template_item_fulfilled_timestamp,
1236
- entry_options_map=entry_options_map)
1237
1156
  self.force_step_update(step, update)
1238
1157
 
1158
+ # FR-47468: Some functions that can help with entry updates.
1239
1159
  def force_step_update(self, step: Step, update: AbstractElnEntryUpdateCriteria) -> None:
1240
1160
  """
1241
1161
  Immediately sent an update to an entry in this experiment. All changes will be reflected by the ExperimentEntry
@@ -1307,67 +1227,6 @@ class ExperimentHandler:
1307
1227
  self._update_entry_details(self._steps_by_id[step_id], criteria)
1308
1228
  self._step_updates.clear()
1309
1229
 
1310
- def _criteria_from_params(self, step: Step, *,
1311
- entry_name: str | None = None,
1312
- related_entry_set: Iterable[int] | None = None,
1313
- dependency_set: Iterable[int] | None = None,
1314
- entry_status: ExperimentEntryStatus | None = None,
1315
- order: int | None = None,
1316
- description: str | None = None,
1317
- requires_grabber_plugin: bool | None = None,
1318
- is_initialization_required: bool | None = None,
1319
- notebook_experiment_tab_id: int | None = None,
1320
- entry_height: int | None = None,
1321
- column_order: int | None = None,
1322
- column_span: int | None = None,
1323
- is_removable: bool | None = None,
1324
- is_renamable: bool | None = None,
1325
- source_entry_id: int | None = None,
1326
- clear_source_entry_id: bool | None = None,
1327
- is_hidden: bool | None = None,
1328
- is_static_view: bool | None = None,
1329
- is_shown_in_template: bool | None = None,
1330
- template_item_fulfilled_timestamp: int | None = None,
1331
- clear_template_item_fulfilled_timestamp: bool | None = None,
1332
- entry_options_map: dict[str, str] | None = None) -> AbstractElnEntryUpdateCriteria:
1333
- """
1334
- Create an abstract update criteria object from the provided parameters for the given step.
1335
- """
1336
- step: ElnEntryStep = self.__to_eln_step(step)
1337
- update = AbstractElnEntryUpdateCriteria(step.eln_entry.entry_type)
1338
-
1339
- # These two variables could be iterables that aren't lists. Convert them to plain
1340
- # lists, since that's what the update criteria is expecting.
1341
- if related_entry_set is not None:
1342
- related_entry_set = list(related_entry_set)
1343
- if dependency_set is not None:
1344
- dependency_set = list(dependency_set)
1345
-
1346
- update.entry_name = entry_name
1347
- update.related_entry_set = related_entry_set
1348
- update.dependency_set = dependency_set
1349
- update.entry_status = entry_status
1350
- update.order = order
1351
- update.description = description
1352
- update.requires_grabber_plugin = requires_grabber_plugin
1353
- update.is_initialization_required = is_initialization_required
1354
- update.notebook_experiment_tab_id = notebook_experiment_tab_id
1355
- update.entry_height = entry_height
1356
- update.column_order = column_order
1357
- update.column_span = column_span
1358
- update.is_removable = is_removable
1359
- update.is_renamable = is_renamable
1360
- update.source_entry_id = source_entry_id
1361
- update.clear_source_entry_id = clear_source_entry_id
1362
- update.is_hidden = is_hidden
1363
- update.is_static_View = is_static_view
1364
- update.is_shown_in_template = is_shown_in_template
1365
- update.template_item_fulfilled_timestamp = template_item_fulfilled_timestamp
1366
- update.clear_template_item_fulfilled_timestamp = clear_template_item_fulfilled_timestamp
1367
- update.entry_options_map = entry_options_map
1368
-
1369
- return update
1370
-
1371
1230
  @staticmethod
1372
1231
  def _merge_updates(new_update: AbstractElnEntryUpdateCriteria, old_update: AbstractElnEntryUpdateCriteria) -> None:
1373
1232
  """
@@ -1552,12 +1411,14 @@ class ExperimentHandler:
1552
1411
  The step may be provided as either a string for the name of the step or an ElnEntryStep.
1553
1412
  If given a name, throws an exception if no step of the given name exists in the experiment.
1554
1413
  :param mapping: The new options and values to add to the existing step options, provided as some Mapping
1555
- (e.g. a Dict). If an option key already exists and is provided in the mapping, overwrites the existing value
1556
- for that key.
1414
+ (e.g. a dictionary). If an option key already exists and is provided in the mapping, overwrites the existing
1415
+ value for that key.
1557
1416
  """
1558
1417
  options: dict[str, str] = self.get_step_options(step)
1559
1418
  options.update(mapping)
1560
- self.force_step_update_params(step, entry_options_map=options)
1419
+ update = AbstractElnEntryUpdateCriteria(step.eln_entry.entry_type)
1420
+ update.entry_options_map = options
1421
+ self.force_step_update(step, update)
1561
1422
 
1562
1423
  def initialize_step(self, step: Step) -> None:
1563
1424
  """
@@ -1575,7 +1436,9 @@ class ExperimentHandler:
1575
1436
  # Avoid unnecessary calls if the step is already initialized.
1576
1437
  step: ElnEntryStep = self.__to_eln_step(step)
1577
1438
  if step.eln_entry.template_item_fulfilled_timestamp is None:
1578
- self.force_step_update_params(step, template_item_fulfilled_timestamp=TimeUtil.now_in_millis())
1439
+ update = AbstractElnEntryUpdateCriteria(step.eln_entry.entry_type)
1440
+ update.template_item_fulfilled_timestamp = TimeUtil.now_in_millis()
1441
+ self.force_step_update(step, update)
1579
1442
 
1580
1443
  def uninitialize_step(self, step: Step) -> None:
1581
1444
  """
@@ -1593,7 +1456,9 @@ class ExperimentHandler:
1593
1456
  # Avoid unnecessary calls if the step is already uninitialized.
1594
1457
  step: ElnEntryStep = self.__to_eln_step(step)
1595
1458
  if step.eln_entry.template_item_fulfilled_timestamp is not None:
1596
- self.force_step_update_params(step, clear_template_item_fulfilled_timestamp=True)
1459
+ update = AbstractElnEntryUpdateCriteria(step.eln_entry.entry_type)
1460
+ update.clear_template_item_fulfilled_timestamp = True
1461
+ self.force_step_update(step, update)
1597
1462
 
1598
1463
  def complete_step(self, step: Step) -> None:
1599
1464
  """
@@ -1650,7 +1515,9 @@ class ExperimentHandler:
1650
1515
  """
1651
1516
  step = self.__to_eln_step(step)
1652
1517
  if step.eln_entry.entry_status in self._ENTRY_LOCKED_STATUSES:
1653
- self.force_step_update_params(step, entry_status=ExperimentEntryStatus.Disabled)
1518
+ update = AbstractElnEntryUpdateCriteria(step.eln_entry.entry_type)
1519
+ update.entry_status = ExperimentEntryStatus.Disabled
1520
+ self.force_step_update(step, update)
1654
1521
 
1655
1522
  def step_is_submitted(self, step: Step) -> bool:
1656
1523
  """
@@ -1728,28 +1595,33 @@ class ExperimentHandler:
1728
1595
  self.add_tab_to_cache(tab)
1729
1596
  return tab
1730
1597
 
1731
- def get_tab(self, tab_name: str | int, exception_on_none: bool = True) -> ElnExperimentTab:
1598
+ def get_tab(self, tab: str | int, exception_on_none: bool = True) -> ElnExperimentTab:
1732
1599
  """
1733
1600
  Return the tab with the input name.
1734
1601
 
1735
1602
  If no tab functions have been called before and a tab is being searched for by name, queries for the
1736
1603
  list of tabs in the experiment and caches them.
1737
1604
 
1738
- :param tab_name: The name or ID of the tab to get.
1605
+ :param tab: The name or order of the tab to get. The order is 1-indexed.
1739
1606
  :param exception_on_none: If True, raises an exception if no tab with the given name exists.
1740
1607
  :return: The tab with the input name, or None if no such tab exists.
1741
1608
  """
1742
- if tab_name not in self._tabs_by_name and tab_name not in self._tabs_by_id:
1743
- self.get_all_tabs()
1744
- if isinstance(tab_name, str):
1745
- tab = self._tabs_by_name.get(tab_name)
1609
+ if isinstance(tab, str):
1610
+ if tab not in self._tabs_by_name:
1611
+ self.get_all_tabs()
1612
+ eln_tab = self._tabs_by_name.get(tab)
1613
+ elif isinstance(tab, int):
1614
+ # The given integer is expected to be 1-indexed, but we read from the list with a 0-index.
1615
+ tab -= 1
1616
+ tabs = self.get_all_tabs()
1617
+ eln_tab = tabs[tab] if len(tabs) > tab else None
1746
1618
  else:
1747
- tab = self._tabs_by_id.get(tab_name)
1748
- if tab is None and exception_on_none:
1749
- raise SapioException(f"No tab with the name\\ID \"{tab_name}\" exists in this experiment.")
1750
- return tab
1619
+ raise SapioException(f"Tab must be a string or an integer, not {type(tab)}.")
1620
+ if eln_tab is None and exception_on_none:
1621
+ raise SapioException(f"No tab with the name\\order \"{tab}\" exists in this experiment.")
1622
+ return eln_tab
1751
1623
 
1752
- def get_steps_in_tab(self, tab: TabIdentifier | str, data_type: DataTypeIdentifier | None = None) \
1624
+ def get_steps_in_tab(self, tab: Tab, data_type: DataTypeIdentifier | None = None) \
1753
1625
  -> list[ElnEntryStep]:
1754
1626
  """
1755
1627
  Get all the steps in the input tab sorted in order of appearance.
@@ -1760,8 +1632,8 @@ class ExperimentHandler:
1760
1632
  If the steps in the experiment have not been queried before, queries for the list of steps in the experiment
1761
1633
  and caches them.
1762
1634
 
1763
- :param tab: The tab to get the steps of. This can be either an ElnExperimentTab object, or the name or ID of
1764
- the tab.
1635
+ :param tab: The tab to get the steps of. This can be the tab's order, name, or the tab object itself.
1636
+ The order is 1-indexed.
1765
1637
  :param data_type: The data type to filter the steps by. If None, all steps are returned.
1766
1638
  :return: A list of all the steps in the input tab sorted in order of appearance.
1767
1639
  """
@@ -1772,7 +1644,29 @@ class ExperimentHandler:
1772
1644
  steps.append(step)
1773
1645
  return steps
1774
1646
 
1775
- def get_next_entry_order_in_tab(self, tab: TabIdentifier | str) -> int:
1647
+ def get_tab_for_step(self, step: Step) -> ElnExperimentTab:
1648
+ """
1649
+ Get the tab that a particular step is located in.
1650
+
1651
+ If no tab functions have been called before and a tab is being searched for by name, queries for the
1652
+ list of tabs in the experiment and caches them.
1653
+
1654
+ If the steps in the experiment have not been queried before, queries for the list of steps in the experiment
1655
+ and caches them.
1656
+
1657
+ :param step:
1658
+ The step to get the position of.
1659
+ The step may be provided as either a string for the name of the step or an ElnEntryStep.
1660
+ If given a name, throws an exception if no step of the given name exists in the experiment.
1661
+ :return: The tab that the input step is located in.
1662
+ """
1663
+ step = self.__to_eln_step(step)
1664
+ tab_id = step.eln_entry.notebook_experiment_tab_id
1665
+ if tab_id not in self._tabs_by_id:
1666
+ self.get_all_tabs()
1667
+ return self._tabs_by_id.get(tab_id)
1668
+
1669
+ def get_next_entry_order_in_tab(self, tab: Tab) -> int:
1776
1670
  """
1777
1671
  Get the next available order for a new entry in the input tab.
1778
1672
 
@@ -1782,8 +1676,8 @@ class ExperimentHandler:
1782
1676
  If the steps in the experiment have not been queried before, queries for the list of steps in the experiment
1783
1677
  and caches them.
1784
1678
 
1785
- :param tab: The tab to get the next entry order of. This can be either an ElnExperimentTab object, or the name
1786
- or ID of the tab.
1679
+ :param tab: The tab to get the steps of. This can be the tab's order, name, or the tab object itself.
1680
+ The order is 1-indexed.
1787
1681
  :return: The next available order for a new entry in the input tab.
1788
1682
  """
1789
1683
  steps = self.get_steps_in_tab(tab)
@@ -1863,16 +1757,14 @@ class ExperimentHandler:
1863
1757
  return self.add_entry_to_caches(step)
1864
1758
  return step
1865
1759
 
1866
- def __to_eln_tab(self, tab: TabIdentifier | str) -> ElnExperimentTab:
1760
+ def __to_eln_tab(self, tab: Tab) -> ElnExperimentTab:
1867
1761
  """
1868
- Convert a variable that could be either a string or an ElnExperimentTab to just an ElnExperimentTab.
1762
+ Convert a variable that could be either a tab name, tab order, or ElnExperimentTab to just a tab object.
1869
1763
  This will query and cache the tabs for the experiment if the input tab is a name and the tabs have not been
1870
1764
  cached before.
1871
1765
 
1872
1766
  :return: The input tab as an ElnExperimentTab.
1873
1767
  """
1874
- if isinstance(tab, str):
1768
+ if not isinstance(tab, ElnExperimentTab):
1875
1769
  return self.get_tab(tab)
1876
- if isinstance(tab, int):
1877
- return [x for x in self._tabs if x.tab_id == tab][0]
1878
1770
  return tab
@@ -38,8 +38,6 @@ ExperimentIdentifier: TypeAlias = ElnExperimentProtocol | ElnExperiment | int
38
38
  ID."""
39
39
  ExperimentEntryIdentifier: TypeAlias = ElnEntryStep | ExperimentEntry | int
40
40
  """An ExperimentEntryIdentifier is either an ELN entry step, experiment entry, or an integer for the entry's ID."""
41
- TabIdentifier: TypeAlias = int | ElnExperimentTab
42
- """A TabIdentifier is either an integer for the tab's ID or an ElnExperimentTab object."""
43
41
  FieldMap: TypeAlias = dict[str, FieldValue]
44
42
  """A field map is simply a dict of data field names to values. The purpose of aliasing this is to help distinguish
45
43
  any random dict in a webhook from one which is explicitly used for record fields."""