sapiopycommons 2025.4.9a150__py3-none-any.whl → 2025.4.9a476__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (42) hide show
  1. sapiopycommons/callbacks/callback_util.py +1262 -392
  2. sapiopycommons/callbacks/field_builder.py +2 -0
  3. sapiopycommons/chem/Molecules.py +0 -2
  4. sapiopycommons/customreport/auto_pagers.py +281 -0
  5. sapiopycommons/customreport/term_builder.py +1 -1
  6. sapiopycommons/datatype/attachment_util.py +4 -2
  7. sapiopycommons/datatype/data_fields.py +23 -1
  8. sapiopycommons/eln/experiment_cache.py +173 -0
  9. sapiopycommons/eln/experiment_handler.py +933 -279
  10. sapiopycommons/eln/experiment_report_util.py +15 -10
  11. sapiopycommons/eln/experiment_step_factory.py +474 -0
  12. sapiopycommons/eln/experiment_tags.py +7 -0
  13. sapiopycommons/eln/plate_designer.py +159 -59
  14. sapiopycommons/eln/step_creation.py +235 -0
  15. sapiopycommons/files/file_bridge.py +76 -0
  16. sapiopycommons/files/file_bridge_handler.py +325 -110
  17. sapiopycommons/files/file_data_handler.py +2 -2
  18. sapiopycommons/files/file_util.py +40 -15
  19. sapiopycommons/files/file_validator.py +6 -5
  20. sapiopycommons/files/file_writer.py +1 -1
  21. sapiopycommons/flowcyto/flow_cyto.py +1 -1
  22. sapiopycommons/general/accession_service.py +3 -3
  23. sapiopycommons/general/aliases.py +51 -28
  24. sapiopycommons/general/audit_log.py +2 -2
  25. sapiopycommons/general/custom_report_util.py +24 -1
  26. sapiopycommons/general/data_structure_util.py +115 -0
  27. sapiopycommons/general/directive_util.py +86 -0
  28. sapiopycommons/general/exceptions.py +41 -2
  29. sapiopycommons/general/popup_util.py +2 -2
  30. sapiopycommons/multimodal/multimodal.py +1 -0
  31. sapiopycommons/processtracking/custom_workflow_handler.py +46 -30
  32. sapiopycommons/recordmodel/record_handler.py +547 -159
  33. sapiopycommons/rules/eln_rule_handler.py +41 -30
  34. sapiopycommons/rules/on_save_rule_handler.py +41 -30
  35. sapiopycommons/samples/aliquot.py +48 -0
  36. sapiopycommons/webhook/webhook_handlers.py +448 -55
  37. sapiopycommons/webhook/webservice_handlers.py +2 -2
  38. {sapiopycommons-2025.4.9a150.dist-info → sapiopycommons-2025.4.9a476.dist-info}/METADATA +1 -1
  39. sapiopycommons-2025.4.9a476.dist-info/RECORD +67 -0
  40. sapiopycommons-2025.4.9a150.dist-info/RECORD +0 -59
  41. {sapiopycommons-2025.4.9a150.dist-info → sapiopycommons-2025.4.9a476.dist-info}/WHEEL +0 -0
  42. {sapiopycommons-2025.4.9a150.dist-info → sapiopycommons-2025.4.9a476.dist-info}/licenses/LICENSE +0 -0
@@ -4,15 +4,16 @@ from sapiopylib.rest.pojo.CustomReport import CustomReportCriteria, AbstractRepo
4
4
  from sapiopylib.rest.pojo.datatype.FieldDefinition import FieldType
5
5
  from sapiopylib.rest.pojo.eln.ElnExperiment import ElnExperiment, ElnExperimentQueryCriteria
6
6
  from sapiopylib.rest.pojo.eln.SapioELNEnums import ElnExperimentStatus, ElnBaseDataType
7
+ from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
7
8
  from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
8
9
 
10
+ from sapiopycommons.customreport.auto_pagers import CustomReportDictAutoPager
9
11
  from sapiopycommons.customreport.custom_report_builder import CustomReportBuilder
10
12
  from sapiopycommons.customreport.term_builder import TermBuilder
11
13
  from sapiopycommons.datatype.pseudo_data_types import EnbEntryOptionsPseudoDef, NotebookExperimentOptionPseudoDef, \
12
14
  NotebookExperimentPseudoDef, ExperimentEntryRecordPseudoDef, EnbEntryPseudoDef
13
15
  from sapiopycommons.general.aliases import SapioRecord, UserIdentifier, AliasUtil, FieldValue, \
14
16
  ExperimentEntryIdentifier, ExperimentIdentifier
15
- from sapiopycommons.general.custom_report_util import CustomReportUtil
16
17
  from sapiopycommons.general.exceptions import SapioException
17
18
  from sapiopycommons.recordmodel.record_handler import RecordHandler
18
19
 
@@ -201,7 +202,7 @@ class ExperimentReportUtil:
201
202
  criteria = report_builder.build_report_criteria()
202
203
 
203
204
  ret_val: dict[SapioRecord, int] = {}
204
- rows: list[dict[str, FieldValue]] = CustomReportUtil.run_custom_report(context, criteria)
205
+ rows: list[dict[str, FieldValue]] = CustomReportDictAutoPager(context, criteria).get_all_at_once()
205
206
  for row in rows:
206
207
  dt: str = row[EnbEntryPseudoDef.DATA_TYPE_NAME__FIELD_NAME.field_name]
207
208
  exp_id: int = row[EnbEntryPseudoDef.EXPERIMENT_ID__FIELD_NAME.field_name]
@@ -209,16 +210,20 @@ class ExperimentReportUtil:
209
210
  ret_val[record] = exp_id
210
211
  return ret_val
211
212
 
213
+ # CR-47491: Support providing a data type name string to receive PyRecordModels instead of requiring a WrapperType.
212
214
  @staticmethod
213
215
  def map_experiments_to_records_of_type(context: UserIdentifier, exp_ids: list[ExperimentIdentifier],
214
- wrapper_type: type[WrappedType]) -> dict[int, list[WrappedType]]:
216
+ wrapper_type: type[WrappedType] | str) \
217
+ -> dict[int, list[WrappedType] | list[PyRecordModel]]:
215
218
  """
216
219
  Return a dictionary mapping each experiment id to a list of records of the given type that were used in each
217
220
  experiment. If an experiment didn't use any records of the given type then it will be mapped to an empty list.
218
221
 
219
222
  :param context: The current webhook context or a user object to send requests from.
220
223
  :param exp_ids: A list of experiment identifiers.
221
- :param wrapper_type: The record model wrapper to use, corresponds to which data type we will query for.
224
+ :param wrapper_type: The record model wrapper or data type name to use, corresponds to which data type we will
225
+ query for. If a data type name is provided, the returned records will be PyRecordModels instead of
226
+ WrappedRecordModels.
222
227
  :return: A dictionary mapping each experiment id to a list of records of the given type that were used in that
223
228
  experiment.
224
229
  """
@@ -227,7 +232,7 @@ class ExperimentReportUtil:
227
232
 
228
233
  user = AliasUtil.to_sapio_user(context)
229
234
  record_handler = RecordHandler(user)
230
- data_type_name: str = wrapper_type.get_wrapper_data_type_name()
235
+ data_type_name: str = AliasUtil.to_data_type_name(wrapper_type)
231
236
 
232
237
  exp_ids: list[int] = AliasUtil.to_notebook_ids(exp_ids)
233
238
  rows = ExperimentReportUtil.__get_record_experiment_relation_rows(user, data_type_name, exp_ids=exp_ids)
@@ -268,7 +273,7 @@ class ExperimentReportUtil:
268
273
 
269
274
  # Ensure that each experiment appears in the dictionary, even if it has no experiment options.
270
275
  options: dict[int, dict[str, str]] = {x: {} for x in exp_ids}
271
- results: list[dict[str, FieldValue]] = CustomReportUtil.run_custom_report(context, report)
276
+ results: list[dict[str, FieldValue]] = CustomReportDictAutoPager(context, report).get_all_at_once()
272
277
  for row in results:
273
278
  exp_id: int = row[NotebookExperimentOptionPseudoDef.EXPERIMENT_ID__FIELD_NAME.field_name]
274
279
  key: str = row[NotebookExperimentOptionPseudoDef.OPTION_KEY__FIELD_NAME.field_name]
@@ -299,7 +304,7 @@ class ExperimentReportUtil:
299
304
 
300
305
  # Ensure that each entry appears in the dictionary, even if it has no entry options.
301
306
  options: dict[int, dict[str, str]] = {x: {} for x in entries}
302
- results: list[dict[str, FieldValue]] = CustomReportUtil.run_custom_report(context, report)
307
+ results: list[dict[str, FieldValue]] = CustomReportDictAutoPager(context, report).get_all_at_once()
303
308
  for row in results:
304
309
  entry_id: int = row[EnbEntryOptionsPseudoDef.ENTRY_ID__FIELD_NAME.field_name]
305
310
  key: str = row[EnbEntryOptionsPseudoDef.ENTRY_OPTION_KEY__FIELD_NAME.field_name]
@@ -332,7 +337,7 @@ class ExperimentReportUtil:
332
337
  report = report_builder.build_report_criteria()
333
338
 
334
339
  ret_val: dict[int, str] = {}
335
- results: list[dict[str, FieldValue]] = CustomReportUtil.run_custom_report(context, report)
340
+ results: list[dict[str, FieldValue]] = CustomReportDictAutoPager(context, report).get_all_at_once()
336
341
  for row in results:
337
342
  exp_id: int = row[NotebookExperimentPseudoDef.EXPERIMENT_ID__FIELD_NAME.field_name]
338
343
  name: str = row["TemplateExperimentName"]
@@ -581,7 +586,7 @@ class ExperimentReportUtil:
581
586
  """
582
587
  user = AliasUtil.to_sapio_user(context)
583
588
  exp_ids: list[int] = []
584
- for row in CustomReportUtil.run_custom_report(user, report):
589
+ for row in CustomReportDictAutoPager(user, report):
585
590
  exp_ids.append(row[NotebookExperimentPseudoDef.EXPERIMENT_ID__FIELD_NAME.field_name])
586
591
  if not exp_ids:
587
592
  return []
@@ -646,4 +651,4 @@ class ExperimentReportUtil:
646
651
  report_builder.add_join(records_entry_join, ExperimentEntryRecordPseudoDef.DATA_TYPE_NAME)
647
652
  report_builder.add_join(experiment_entry_enb_entry_join, EnbEntryPseudoDef.DATA_TYPE_NAME)
648
653
  report_builder.add_join(enb_entry_experiment_join, NotebookExperimentPseudoDef.DATA_TYPE_NAME)
649
- return CustomReportUtil.run_custom_report(user, report_builder.build_report_criteria())
654
+ return CustomReportDictAutoPager(user, report_builder.build_report_criteria()).get_all_at_once()
@@ -0,0 +1,474 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable
4
+ from typing import cast
5
+
6
+ from sapiopycommons.datatype.data_fields import SystemFields
7
+ from sapiopycommons.eln.step_creation import StepCreation, AttachmentStepCreation, GlobalDtFormStepCreation, \
8
+ ELnDtFormStepCreation, PluginStepCreation, TextStepCreation, TempDataStepCreation, ELnDtTableStepCreation, \
9
+ GlobalDtTableStepCreation, DashboardStepCreation
10
+ from sapiopycommons.eln.experiment_cache import ExperimentCacheManager
11
+ from sapiopycommons.eln.experiment_handler import ExperimentHandler, Step
12
+ from sapiopycommons.eln.experiment_tags import PLATE_DESIGNER_PLUGIN
13
+ from sapiopycommons.general.aliases import AliasUtil, SapioRecord, DataTypeIdentifier, FieldMap, \
14
+ ExperimentEntryIdentifier
15
+ from sapiopycommons.general.exceptions import SapioException
16
+ from sapiopylib.rest.DataRecordManagerService import DataRecordManager
17
+ from sapiopylib.rest.ELNService import ElnManager
18
+ from sapiopylib.rest.User import SapioUser
19
+ from sapiopylib.rest.pojo.DataRecord import DataRecord
20
+ from sapiopylib.rest.pojo.TableColumn import TableColumn
21
+ from sapiopylib.rest.pojo.eln.ElnEntryPosition import ElnEntryPosition
22
+ from sapiopylib.rest.pojo.eln.ExperimentEntry import EntryAttachment, EntryRecordAttachment
23
+ from sapiopylib.rest.pojo.eln.ExperimentEntryCriteria import ExperimentEntryCriteriaUtil, AbstractElnEntryCriteria, \
24
+ ElnAttachmentEntryCriteria, \
25
+ ElnFormEntryCriteria, ElnPluginEntryCriteria, ElnTextEntryCriteria, ElnTempDataEntryCriteria, ElnTableEntryCriteria, \
26
+ ElnDashboardEntryCriteria
27
+ from sapiopylib.rest.pojo.eln.SapioELNEnums import ElnBaseDataType
28
+ from sapiopylib.rest.pojo.eln.eln_headings import ElnExperimentTab
29
+ from sapiopylib.rest.pojo.eln.field_set import ElnFieldSetInfo
30
+ from sapiopylib.rest.utils.Protocols import ElnEntryStep
31
+ from sapiopylib.rest.utils.plates.MultiLayerPlating import MultiLayerPlateConfig, MultiLayerPlateLayer, \
32
+ MultiLayerDataTypeConfig, MultiLayerReplicateConfig, MultiLayerDilutionConfig
33
+ from sapiopylib.rest.utils.plates.MultiLayerPlatingUtils import MultiLayerPlatingManager
34
+ from sapiopylib.rest.utils.plates.PlatingUtils import PlatingOrder
35
+
36
+
37
+ # CR-47564: Moved the entry creation functions to their own class.
38
+ class ExperimentStepFactory:
39
+ user: SapioUser
40
+ _exp_handler: ExperimentHandler
41
+
42
+ _exp_id: int
43
+
44
+ _eln_man: ElnManager
45
+ _exp_cache: ExperimentCacheManager
46
+
47
+ def __init__(self, exp_handler: ExperimentHandler):
48
+ self.user = exp_handler.user
49
+ self._exp_handler = exp_handler
50
+ self._exp_id = exp_handler.protocol.get_id()
51
+ self._eln_man = ElnManager(self.user)
52
+ self._exp_cache = ExperimentCacheManager(self.user)
53
+
54
+ # FR-47468: Add functions for creating new entries in the experiment.
55
+ def create_attachment_step(self, entry_name: str, data_type: DataTypeIdentifier,
56
+ attachments: Iterable[EntryAttachment] | Iterable[SapioRecord] | None = None,
57
+ criteria: AttachmentStepCreation = AttachmentStepCreation(),
58
+ position: ElnEntryPosition | None = None) -> ElnEntryStep:
59
+ """
60
+ Create a new attachment entry in the experiment.
61
+
62
+ :param entry_name: The name of the entry.
63
+ :param data_type: The data type of the entry.
64
+ :param attachments: The list of attachments to initially populate the entry with.
65
+ :param criteria: Additional criteria for creating the entry.
66
+ :param position: Information about where to place the entry in the experiment.
67
+ :return: The newly created attachment entry.
68
+ """
69
+ entry_criteria = cast(ElnAttachmentEntryCriteria,
70
+ self._create_step_criteria(entry_name, data_type, criteria, position))
71
+
72
+ if attachments:
73
+ entry_attachments: list[EntryAttachment] = []
74
+ for entry in attachments:
75
+ if isinstance(entry, EntryAttachment):
76
+ entry_attachments.append(entry)
77
+ elif isinstance(entry, SapioRecord):
78
+ entry: SapioRecord
79
+ file_name: str = entry.get_field_value("FilePath")
80
+ if not file_name:
81
+ file_name = entry.get_field_value(SystemFields.DATA_RECORD_NAME__FIELD.field_name)
82
+ rec_id: int = AliasUtil.to_record_id(entry)
83
+ entry_attachments.append(EntryRecordAttachment(file_name, rec_id))
84
+ else:
85
+ raise SapioException("Attachments must be of type EntryAttachment or SapioRecord.")
86
+ entry_criteria.entry_attachment_list = entry_attachments
87
+
88
+ step = self._exp_handler.add_entry_to_caches(self._eln_man.add_experiment_entry(self._exp_id, entry_criteria))
89
+ return step
90
+
91
+ def create_dashboard_step(self, entry_name, data_type: DataTypeIdentifier,
92
+ criteria: DashboardStepCreation = DashboardStepCreation(),
93
+ position: ElnEntryPosition | None = None) -> ElnEntryStep:
94
+ entry_criteria = cast(ElnDashboardEntryCriteria,
95
+ self._create_step_criteria(entry_name, data_type, criteria, position))
96
+
97
+ if criteria:
98
+ if entry_criteria.source_entry_id:
99
+ entry_criteria.source_entry_id = self.__to_entry_ids([criteria.source_entry])[0]
100
+ if criteria.dashboard_guids:
101
+ entry_criteria.dashboard_guid_list = list(criteria.dashboard_guids)
102
+
103
+ step = self._exp_handler.add_entry_to_caches(self._eln_man.add_experiment_entry(self._exp_id, entry_criteria))
104
+ return step
105
+
106
+ def create_form_step(self, entry_name: str,
107
+ data_type: DataTypeIdentifier,
108
+ record: SapioRecord | None = None,
109
+ criteria: GlobalDtFormStepCreation = GlobalDtFormStepCreation(),
110
+ position: ElnEntryPosition | None = None) -> ElnEntryStep:
111
+ """
112
+ Create a new form entry in the experiment.
113
+
114
+ :param entry_name: The name of the entry.
115
+ :param data_type: The data type of the entry.
116
+ :param record: The record to initially populate the entry with.
117
+ :param criteria: Additional criteria for creating the entry.
118
+ :param position: Information about where to place the entry in the experiment.
119
+ :return: The newly created form entry.
120
+ """
121
+ if record:
122
+ rdt: str = AliasUtil.to_data_type_name(record)
123
+ sdt: str = AliasUtil.to_data_type_name(data_type)
124
+ if rdt != sdt:
125
+ raise SapioException(f"Cannot set {rdt} records for entry {entry_name} of type "
126
+ f"{sdt}.")
127
+
128
+ entry_criteria = cast(ElnFormEntryCriteria,
129
+ self._create_step_criteria(entry_name, data_type, criteria, position))
130
+ if record:
131
+ entry_criteria.record_id = AliasUtil.to_record_id(record)
132
+ if criteria:
133
+ entry_criteria.data_type_layout_name = criteria.layout_name
134
+ if entry_criteria.form_name_list:
135
+ entry_criteria.form_name_list = list(criteria.form_names)
136
+ if criteria.extension_types:
137
+ entry_criteria.extension_type_list = AliasUtil.to_data_type_names(criteria.extension_types)
138
+ if criteria.field_names:
139
+ entry_criteria.data_field_name_list = AliasUtil.to_data_field_names(criteria.field_names)
140
+
141
+ step = self._exp_handler.add_entry_to_caches(self._eln_man.add_experiment_entry(self._exp_id, entry_criteria))
142
+ return step
143
+
144
+ def create_experiment_detail_form_step(self, entry_name: str,
145
+ field_map: FieldMap | None = None,
146
+ criteria: ELnDtFormStepCreation = ELnDtFormStepCreation(),
147
+ position: ElnEntryPosition | None = None) -> ElnEntryStep:
148
+ """
149
+ Create a new ELN experiment details form entry in the experiment.
150
+
151
+ :param entry_name: The name of the entry.
152
+ :param field_map: A field map that will be used to populate the entry. The data field names in
153
+ the map must match the field names of the provided field definitions.
154
+ :param criteria: Additional criteria for creating the entry.
155
+ :param position: Information about where to place the entry in the experiment.
156
+ :return: The newly created form entry.
157
+ """
158
+ dt = ElnBaseDataType.EXPERIMENT_DETAIL
159
+ return self._create_eln_dt_form_step(entry_name, dt, field_map, criteria, position)
160
+
161
+ def create_sample_detail_form_step(self, entry_name: str,
162
+ field_map: FieldMap | None = None,
163
+ criteria: ELnDtFormStepCreation = ELnDtFormStepCreation(),
164
+ position: ElnEntryPosition | None = None) -> ElnEntryStep:
165
+ """
166
+ Create a new ELN sample details form entry in the experiment.
167
+
168
+ :param entry_name: The name of the entry.
169
+ :param field_map: A field map that will be used to populate the entry. The data field names in
170
+ the map must match the field names of the provided field definitions.
171
+ :param criteria: Additional criteria for creating the entry.
172
+ :param position: Information about where to place the entry in the experiment.
173
+ :return: The newly created form entry.
174
+ """
175
+ dt = ElnBaseDataType.SAMPLE_DETAIL
176
+ return self._create_eln_dt_form_step(entry_name, dt, field_map, criteria, position)
177
+
178
+ def _create_eln_dt_form_step(self, entry_name: str,
179
+ dt: ElnBaseDataType,
180
+ field_map: FieldMap | None = None,
181
+ criteria: ELnDtFormStepCreation = ELnDtFormStepCreation(),
182
+ position: ElnEntryPosition | None = None) -> ElnEntryStep:
183
+ entry_criteria = cast(ElnFormEntryCriteria,
184
+ self._create_step_criteria(entry_name, dt.data_type_name, criteria, position))
185
+
186
+ if field_map:
187
+ entry_criteria.field_map = field_map
188
+ if criteria:
189
+ entry_criteria.is_field_addable = criteria.is_field_addable
190
+ entry_criteria.is_existing_field_removable = criteria.is_existing_field_removable
191
+ if criteria.field_sets:
192
+ field_sets: set[int] = set()
193
+ for field_set in criteria.field_sets:
194
+ if isinstance(field_set, int):
195
+ field_sets.add(field_set)
196
+ elif isinstance(field_set, ElnFieldSetInfo):
197
+ field_sets.add(field_set.field_set_id)
198
+ elif isinstance(field_set, str):
199
+ field_sets.add(self._exp_cache.get_field_set(field_set).field_set_id)
200
+ entry_criteria.field_set_id_list = list(field_sets)
201
+ entry_criteria.field_definition_list = criteria.field_definitions
202
+ if criteria.predefined_field_names:
203
+ if entry_criteria.field_definition_list is None:
204
+ entry_criteria.field_definition_list = []
205
+ for field_name in criteria.predefined_field_names:
206
+ entry_criteria.field_definition_list.append(self._exp_cache.get_predefined_field(field_name, dt))
207
+
208
+ step = self._exp_handler.add_entry_to_caches(self._eln_man.add_experiment_entry(self._exp_id, entry_criteria))
209
+ return step
210
+
211
+ def create_plugin_step(self, entry_name: str,
212
+ data_type: DataTypeIdentifier,
213
+ criteria: PluginStepCreation | None,
214
+ position: ElnEntryPosition | None = None) -> ElnEntryStep:
215
+ """
216
+ Create a new plugin entry in the experiment.
217
+
218
+ :param entry_name: The name of the entry.
219
+ :param data_type: The data type of the entry.
220
+ :param criteria: Additional criteria for creating the entry, such as plugin name and whether it provides
221
+ :param position: Information about where to place the entry in the experiment.
222
+ :return: The newly created plugin entry.
223
+ """
224
+ entry_criteria = cast(ElnPluginEntryCriteria,
225
+ self._create_step_criteria(entry_name, data_type, criteria, position))
226
+
227
+ if criteria:
228
+ entry_criteria.csp_plugin_name = criteria.plugin_name
229
+ entry_criteria.using_template_data = criteria.using_template_data
230
+ entry_criteria.provides_template_data = criteria.provides_template_data
231
+
232
+ step = self._exp_handler.add_entry_to_caches(self._eln_man.add_experiment_entry(self._exp_id, entry_criteria))
233
+ return step
234
+
235
+ def create_table_step(self, entry_name: str, data_type: DataTypeIdentifier,
236
+ records: list[SapioRecord] | None = None,
237
+ criteria: GlobalDtTableStepCreation = GlobalDtTableStepCreation(),
238
+ position: ElnEntryPosition | None = None) -> ElnEntryStep:
239
+ """
240
+ Create a new table entry in the experiment.
241
+
242
+ :param entry_name: The name of the entry.
243
+ :param data_type: The data type of the entry.
244
+ :param criteria: Additional criteria for creating the entry.
245
+ :param position: Information about where to place the entry in the experiment.
246
+ :param records: The list of records to initially populate the entry with.
247
+ :return: The newly created table entry.
248
+ """
249
+ entry_criteria = cast(ElnTableEntryCriteria,
250
+ self._create_step_criteria(entry_name, data_type, criteria, position))
251
+ if criteria:
252
+ entry_criteria.data_type_layout_name = criteria.layout_name
253
+ entry_criteria.show_key_fields = criteria.show_key_fields
254
+ if criteria.extension_types:
255
+ entry_criteria.extension_type_list = list(criteria.extension_types)
256
+ if criteria.table_columns:
257
+ entry_criteria.table_column_list = list(criteria.table_columns)
258
+ if criteria.field_names:
259
+ if entry_criteria.table_column_list is None:
260
+ entry_criteria.table_column_list = []
261
+ for field in AliasUtil.to_data_field_names(criteria.field_names):
262
+ entry_criteria.table_column_list.append(TableColumn(data_type, field))
263
+
264
+ step = self._exp_handler.add_entry_to_caches(self._eln_man.add_experiment_entry(self._exp_id, entry_criteria))
265
+ if records:
266
+ self._exp_handler.set_step_records(step, records)
267
+ return step
268
+
269
+ def create_experiment_detail_table_step(self, entry_name: str,
270
+ field_maps: list[FieldMap] | None = None,
271
+ criteria: ELnDtTableStepCreation = ELnDtTableStepCreation(),
272
+ position: ElnEntryPosition | None = None) -> ElnEntryStep:
273
+ """
274
+ Create a new ELN experiment details table entry in the experiment.
275
+
276
+ :param entry_name: The name of the entry.
277
+ :param field_maps: A field maps list that will be used to populate the entry. The data field names in
278
+ the maps must match the field names of the provided field definitions.
279
+ :param criteria: Additional criteria for creating the entry.
280
+ :param position: Information about where to place the entry in the experiment.
281
+ :return: The newly created table entry.
282
+ """
283
+ dt = ElnBaseDataType.EXPERIMENT_DETAIL
284
+ return self._create_eln_dt_table_step(entry_name, dt, field_maps, criteria, position)
285
+
286
+ def create_sample_detail_table_step(self, entry_name: str,
287
+ field_maps: list[FieldMap] | None = None,
288
+ criteria: ELnDtTableStepCreation = ELnDtTableStepCreation(),
289
+ position: ElnEntryPosition | None = None) -> ElnEntryStep:
290
+ """
291
+ Create a new ELN sample details table entry in the experiment.
292
+
293
+ :param entry_name: The name of the entry.
294
+ :param field_maps: A field maps list that will be used to populate the entry. The data field names in
295
+ the maps must match the field names of the provided field definitions.
296
+ :param criteria: Additional criteria for creating the entry.
297
+ :param position: Information about where to place the entry in the experiment.
298
+ :return: The newly created table entry.
299
+ """
300
+ dt = ElnBaseDataType.SAMPLE_DETAIL
301
+ return self._create_eln_dt_table_step(entry_name, dt, field_maps, criteria, position)
302
+
303
+ def _create_eln_dt_table_step(self, entry_name: str,
304
+ dt: ElnBaseDataType,
305
+ field_maps: list[FieldMap] | None = None,
306
+ criteria: ELnDtTableStepCreation = ELnDtTableStepCreation(),
307
+ position: ElnEntryPosition | None = None) -> ElnEntryStep:
308
+ entry_criteria = cast(ElnTableEntryCriteria,
309
+ self._create_step_criteria(entry_name, dt.data_type_name, criteria, position))
310
+
311
+ if field_maps:
312
+ entry_criteria.field_map_list = field_maps
313
+ if criteria:
314
+ entry_criteria.is_field_addable = criteria.is_field_addable
315
+ entry_criteria.is_existing_field_removable = criteria.is_existing_field_removable
316
+ if criteria.field_sets:
317
+ field_sets: set[int] = set()
318
+ for field_set in criteria.field_sets:
319
+ if isinstance(field_set, int):
320
+ field_sets.add(field_set)
321
+ elif isinstance(field_set, ElnFieldSetInfo):
322
+ field_sets.add(field_set.field_set_id)
323
+ elif isinstance(field_set, str):
324
+ field_sets.add(self._exp_cache.get_field_set(field_set).field_set_id)
325
+ entry_criteria.field_set_id_list = list(field_sets)
326
+ entry_criteria.field_definition_list = list(criteria.field_definitions)
327
+ if criteria.predefined_field_names:
328
+ if entry_criteria.field_definition_list is None:
329
+ entry_criteria.field_definition_list = []
330
+ for field_name in criteria.predefined_field_names:
331
+ entry_criteria.field_definition_list.append(self._exp_cache.get_predefined_field(field_name, dt))
332
+ if criteria.table_columns:
333
+ entry_criteria.table_column_list = list(criteria.table_columns)
334
+
335
+ step = self._exp_handler.add_entry_to_caches(self._eln_man.add_experiment_entry(self._exp_id, entry_criteria))
336
+ return step
337
+
338
+ def create_temp_data_step(self, entry_name: str,
339
+ data_type: DataTypeIdentifier,
340
+ criteria: TempDataStepCreation = TempDataStepCreation(),
341
+ position: ElnEntryPosition | None = None) -> ElnEntryStep:
342
+ """
343
+ Create a new temp data entry in the experiment.
344
+
345
+ :param entry_name: The name of the entry.
346
+ :param data_type: The data type of the entry.
347
+ :param criteria: Additional criteria for creating the entry.
348
+ :param position: Information about where to place the entry in the experiment.
349
+ :return: The newly created temp data entry.
350
+ """
351
+ entry_criteria = cast(ElnTempDataEntryCriteria,
352
+ self._create_step_criteria(entry_name, data_type, criteria, position))
353
+
354
+ if criteria:
355
+ entry_criteria.temp_data_plugin_path = criteria.plugin_path
356
+
357
+ step = self._exp_handler.add_entry_to_caches(self._eln_man.add_experiment_entry(self._exp_id, entry_criteria))
358
+ return step
359
+
360
+ def create_text_step(self, entry_name: str,
361
+ text: str | None = None,
362
+ criteria: TextStepCreation = TextStepCreation(),
363
+ position: ElnEntryPosition | None = None) -> ElnEntryStep:
364
+ """
365
+ Create a new text entry in the experiment.
366
+
367
+ :param entry_name: The name of the entry.
368
+ :param text: The text to populate the entry with.
369
+ :param criteria: Additional criteria for creating the entry.
370
+ :param position: Information about where to place the entry in the experiment.
371
+ :return: The newly created text entry.
372
+ """
373
+ dt = ElnBaseDataType.TEXT_ENTRY_DETAIL.data_type_name
374
+ entry_criteria = cast(ElnTextEntryCriteria,
375
+ self._create_step_criteria(entry_name, dt, criteria, position))
376
+
377
+ step = self._exp_handler.add_entry_to_caches(self._eln_man.add_experiment_entry(self._exp_id, entry_criteria))
378
+
379
+ if text:
380
+ text_record: DataRecord = step.get_records()[0]
381
+ text_record.set_field_value(ElnBaseDataType.get_text_entry_data_field_name(), text)
382
+ DataRecordManager(self.user).commit_data_records([text_record])
383
+
384
+ return step
385
+
386
+ def create_plate_designer_step(self, entry_name: str,
387
+ source_entry: Step | None = None,
388
+ criteria: PluginStepCreation = PluginStepCreation(),
389
+ position: ElnEntryPosition | None = None) -> ElnEntryStep:
390
+ """
391
+ Create a new 3D plate designer entry in the experiment.
392
+
393
+ :param entry_name: The name of the entry.
394
+ :param source_entry: The entry that the plate designer will source its samples from.
395
+ :param criteria: Additional criteria for creating the entry.
396
+ :param position: Information about where to place the entry in the experiment.
397
+ :return: The newly created plate designer entry.
398
+ """
399
+ criteria.plugin_name = PLATE_DESIGNER_PLUGIN
400
+ if criteria.entry_height is None:
401
+ criteria.entry_height = 600
402
+ if source_entry is not None:
403
+ criteria.related_entry_set = [source_entry]
404
+ default_layer = MultiLayerPlateLayer(
405
+ MultiLayerDataTypeConfig("Sample"),
406
+ PlatingOrder.FillBy.BY_COLUMN,
407
+ MultiLayerReplicateConfig(),
408
+ MultiLayerDilutionConfig()
409
+ )
410
+ initial_step_options: dict[str, str] = {
411
+ "MultiLayerPlating_Plate_RecordIdList": "",
412
+ "MultiLayerPlating_Entry_Prefs": MultiLayerPlatingManager.get_entry_prefs_json([default_layer]),
413
+ "MultiLayerPlating_Entry_PrePlating_Prefs": MultiLayerPlatingManager.get_plate_configs_json(MultiLayerPlateConfig())
414
+ }
415
+ if criteria.entry_options is None:
416
+ criteria.entry_options = initial_step_options
417
+ else:
418
+ criteria.entry_options.update(initial_step_options)
419
+ return self.create_plugin_step(entry_name, "Sample", criteria, position)
420
+
421
+ def _create_step_criteria(self, name: str, dt: DataTypeIdentifier,
422
+ criteria: StepCreation, position: ElnEntryPosition | None) \
423
+ -> AbstractElnEntryCriteria:
424
+ """
425
+ Create the criteria for a new entry in the experiment of the given type.
426
+ """
427
+ if position is not None:
428
+ order: int = position.order
429
+ tab_id: int = position.tab_id
430
+ column_order: int = position.column_order
431
+ column_span: int = position.column_span
432
+ else:
433
+ last_tab: ElnExperimentTab = self._exp_handler.get_last_tab()
434
+ order: int = self._exp_handler.get_next_entry_order_in_tab(last_tab)
435
+ tab_id: int = last_tab.tab_id
436
+ column_order: int = 0
437
+ column_span: int = last_tab.max_number_of_columns
438
+
439
+ dt: str = AliasUtil.to_data_type_name(dt)
440
+ et = criteria.entry_type
441
+ entry_criteria = ExperimentEntryCriteriaUtil.get_entry_creation_criteria(et, name, dt, order)
442
+ entry_criteria.notebook_experiment_tab_id = tab_id
443
+ entry_criteria.column_order = column_order
444
+ entry_criteria.column_span = column_span
445
+ if criteria:
446
+ entry_criteria.is_shown_in_template = criteria.is_shown_in_template
447
+ entry_criteria.is_removable = criteria.is_removable
448
+ entry_criteria.is_renamable = criteria.is_renamable
449
+ entry_criteria.is_static_view = criteria.is_static_view
450
+ if criteria.related_entry_set:
451
+ entry_criteria.related_entry_set = self.__to_entry_ids(criteria.related_entry_set)
452
+ if criteria.dependency_set:
453
+ entry_criteria.dependency_set = self.__to_entry_ids(criteria.dependency_set)
454
+ entry_criteria.requires_grabber_plugin = criteria.requires_grabber_plugin
455
+ entry_criteria.entry_singleton_id = criteria.entry_singleton_id
456
+ entry_criteria.is_hidden = criteria.is_hidden
457
+ entry_criteria.entry_height = criteria.entry_height
458
+ entry_criteria.description = criteria.description
459
+ entry_criteria.is_initialization_required = criteria.is_initialization_required
460
+ entry_criteria.collapse_entry = criteria.collapse_entry
461
+ entry_criteria.entry_status = criteria.entry_status
462
+ entry_criteria.template_item_fulfilled_timestamp = criteria.template_item_fulfilled_timestamp
463
+ entry_criteria.entry_options = criteria.entry_options
464
+
465
+ return entry_criteria
466
+
467
+ def __to_entry_ids(self, entries: Iterable[ExperimentEntryIdentifier | str]) -> list[int] | None:
468
+ entry_ids: set[int] = set()
469
+ for entry in entries:
470
+ if isinstance(entry, str):
471
+ entry_ids.add(self._exp_handler.get_step(entry).get_id())
472
+ else:
473
+ entry_ids.add(AliasUtil.to_entry_id(entry))
474
+ return list(entry_ids)
@@ -0,0 +1,7 @@
1
+ # CSP paths:
2
+ PLATE_DESIGNER_PLUGIN: str = "com.velox.gwt.client.plugins.multilayerplating.core.ElnMultiLayerPlatingClientPlugin"
3
+
4
+ # 3D Plate Designer entry tags:
5
+ PLATE_IDS_TAG: str = "MultiLayerPlating_Plate_RecordIdList"
6
+ PLATE_CONFIG_PREFS_TAG: str = "MultiLayerPlating_Entry_PrePlating_Prefs"
7
+ PLATE_LAYER_PREFS_TAG: str = "MultiLayerPlating_Entry_Prefs"