sapiopycommons 2025.4.8a473__py3-none-any.whl → 2025.4.9a150__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 +392 -1262
  2. sapiopycommons/callbacks/field_builder.py +0 -2
  3. sapiopycommons/chem/Molecules.py +2 -0
  4. sapiopycommons/customreport/term_builder.py +1 -1
  5. sapiopycommons/datatype/attachment_util.py +2 -4
  6. sapiopycommons/datatype/data_fields.py +1 -23
  7. sapiopycommons/eln/experiment_handler.py +279 -933
  8. sapiopycommons/eln/experiment_report_util.py +10 -15
  9. sapiopycommons/eln/plate_designer.py +59 -159
  10. sapiopycommons/files/file_bridge.py +0 -76
  11. sapiopycommons/files/file_bridge_handler.py +110 -325
  12. sapiopycommons/files/file_data_handler.py +2 -2
  13. sapiopycommons/files/file_util.py +15 -40
  14. sapiopycommons/files/file_validator.py +5 -6
  15. sapiopycommons/files/file_writer.py +1 -1
  16. sapiopycommons/flowcyto/flow_cyto.py +1 -1
  17. sapiopycommons/general/accession_service.py +3 -3
  18. sapiopycommons/general/aliases.py +28 -51
  19. sapiopycommons/general/audit_log.py +2 -2
  20. sapiopycommons/general/custom_report_util.py +1 -24
  21. sapiopycommons/general/exceptions.py +2 -41
  22. sapiopycommons/general/popup_util.py +2 -2
  23. sapiopycommons/multimodal/multimodal.py +0 -1
  24. sapiopycommons/processtracking/custom_workflow_handler.py +30 -46
  25. sapiopycommons/recordmodel/record_handler.py +159 -547
  26. sapiopycommons/rules/eln_rule_handler.py +30 -41
  27. sapiopycommons/rules/on_save_rule_handler.py +30 -41
  28. sapiopycommons/webhook/webhook_handlers.py +55 -448
  29. sapiopycommons/webhook/webservice_handlers.py +2 -2
  30. {sapiopycommons-2025.4.8a473.dist-info → sapiopycommons-2025.4.9a150.dist-info}/METADATA +1 -1
  31. sapiopycommons-2025.4.9a150.dist-info/RECORD +59 -0
  32. sapiopycommons/customreport/auto_pagers.py +0 -281
  33. sapiopycommons/eln/experiment_cache.py +0 -173
  34. sapiopycommons/eln/experiment_step_factory.py +0 -474
  35. sapiopycommons/eln/experiment_tags.py +0 -7
  36. sapiopycommons/eln/step_creation.py +0 -235
  37. sapiopycommons/general/data_structure_util.py +0 -115
  38. sapiopycommons/general/directive_util.py +0 -86
  39. sapiopycommons/samples/aliquot.py +0 -48
  40. sapiopycommons-2025.4.8a473.dist-info/RECORD +0 -67
  41. {sapiopycommons-2025.4.8a473.dist-info → sapiopycommons-2025.4.9a150.dist-info}/WHEEL +0 -0
  42. {sapiopycommons-2025.4.8a473.dist-info → sapiopycommons-2025.4.9a150.dist-info}/licenses/LICENSE +0 -0
@@ -4,16 +4,15 @@ 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
8
7
  from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
9
8
 
10
- from sapiopycommons.customreport.auto_pagers import CustomReportDictAutoPager
11
9
  from sapiopycommons.customreport.custom_report_builder import CustomReportBuilder
12
10
  from sapiopycommons.customreport.term_builder import TermBuilder
13
11
  from sapiopycommons.datatype.pseudo_data_types import EnbEntryOptionsPseudoDef, NotebookExperimentOptionPseudoDef, \
14
12
  NotebookExperimentPseudoDef, ExperimentEntryRecordPseudoDef, EnbEntryPseudoDef
15
13
  from sapiopycommons.general.aliases import SapioRecord, UserIdentifier, AliasUtil, FieldValue, \
16
14
  ExperimentEntryIdentifier, ExperimentIdentifier
15
+ from sapiopycommons.general.custom_report_util import CustomReportUtil
17
16
  from sapiopycommons.general.exceptions import SapioException
18
17
  from sapiopycommons.recordmodel.record_handler import RecordHandler
19
18
 
@@ -202,7 +201,7 @@ class ExperimentReportUtil:
202
201
  criteria = report_builder.build_report_criteria()
203
202
 
204
203
  ret_val: dict[SapioRecord, int] = {}
205
- rows: list[dict[str, FieldValue]] = CustomReportDictAutoPager(context, criteria).get_all_at_once()
204
+ rows: list[dict[str, FieldValue]] = CustomReportUtil.run_custom_report(context, criteria)
206
205
  for row in rows:
207
206
  dt: str = row[EnbEntryPseudoDef.DATA_TYPE_NAME__FIELD_NAME.field_name]
208
207
  exp_id: int = row[EnbEntryPseudoDef.EXPERIMENT_ID__FIELD_NAME.field_name]
@@ -210,20 +209,16 @@ class ExperimentReportUtil:
210
209
  ret_val[record] = exp_id
211
210
  return ret_val
212
211
 
213
- # CR-47491: Support providing a data type name string to receive PyRecordModels instead of requiring a WrapperType.
214
212
  @staticmethod
215
213
  def map_experiments_to_records_of_type(context: UserIdentifier, exp_ids: list[ExperimentIdentifier],
216
- wrapper_type: type[WrappedType] | str) \
217
- -> dict[int, list[WrappedType] | list[PyRecordModel]]:
214
+ wrapper_type: type[WrappedType]) -> dict[int, list[WrappedType]]:
218
215
  """
219
216
  Return a dictionary mapping each experiment id to a list of records of the given type that were used in each
220
217
  experiment. If an experiment didn't use any records of the given type then it will be mapped to an empty list.
221
218
 
222
219
  :param context: The current webhook context or a user object to send requests from.
223
220
  :param exp_ids: A list of experiment identifiers.
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.
221
+ :param wrapper_type: The record model wrapper to use, corresponds to which data type we will query for.
227
222
  :return: A dictionary mapping each experiment id to a list of records of the given type that were used in that
228
223
  experiment.
229
224
  """
@@ -232,7 +227,7 @@ class ExperimentReportUtil:
232
227
 
233
228
  user = AliasUtil.to_sapio_user(context)
234
229
  record_handler = RecordHandler(user)
235
- data_type_name: str = AliasUtil.to_data_type_name(wrapper_type)
230
+ data_type_name: str = wrapper_type.get_wrapper_data_type_name()
236
231
 
237
232
  exp_ids: list[int] = AliasUtil.to_notebook_ids(exp_ids)
238
233
  rows = ExperimentReportUtil.__get_record_experiment_relation_rows(user, data_type_name, exp_ids=exp_ids)
@@ -273,7 +268,7 @@ class ExperimentReportUtil:
273
268
 
274
269
  # Ensure that each experiment appears in the dictionary, even if it has no experiment options.
275
270
  options: dict[int, dict[str, str]] = {x: {} for x in exp_ids}
276
- results: list[dict[str, FieldValue]] = CustomReportDictAutoPager(context, report).get_all_at_once()
271
+ results: list[dict[str, FieldValue]] = CustomReportUtil.run_custom_report(context, report)
277
272
  for row in results:
278
273
  exp_id: int = row[NotebookExperimentOptionPseudoDef.EXPERIMENT_ID__FIELD_NAME.field_name]
279
274
  key: str = row[NotebookExperimentOptionPseudoDef.OPTION_KEY__FIELD_NAME.field_name]
@@ -304,7 +299,7 @@ class ExperimentReportUtil:
304
299
 
305
300
  # Ensure that each entry appears in the dictionary, even if it has no entry options.
306
301
  options: dict[int, dict[str, str]] = {x: {} for x in entries}
307
- results: list[dict[str, FieldValue]] = CustomReportDictAutoPager(context, report).get_all_at_once()
302
+ results: list[dict[str, FieldValue]] = CustomReportUtil.run_custom_report(context, report)
308
303
  for row in results:
309
304
  entry_id: int = row[EnbEntryOptionsPseudoDef.ENTRY_ID__FIELD_NAME.field_name]
310
305
  key: str = row[EnbEntryOptionsPseudoDef.ENTRY_OPTION_KEY__FIELD_NAME.field_name]
@@ -337,7 +332,7 @@ class ExperimentReportUtil:
337
332
  report = report_builder.build_report_criteria()
338
333
 
339
334
  ret_val: dict[int, str] = {}
340
- results: list[dict[str, FieldValue]] = CustomReportDictAutoPager(context, report).get_all_at_once()
335
+ results: list[dict[str, FieldValue]] = CustomReportUtil.run_custom_report(context, report)
341
336
  for row in results:
342
337
  exp_id: int = row[NotebookExperimentPseudoDef.EXPERIMENT_ID__FIELD_NAME.field_name]
343
338
  name: str = row["TemplateExperimentName"]
@@ -586,7 +581,7 @@ class ExperimentReportUtil:
586
581
  """
587
582
  user = AliasUtil.to_sapio_user(context)
588
583
  exp_ids: list[int] = []
589
- for row in CustomReportDictAutoPager(user, report):
584
+ for row in CustomReportUtil.run_custom_report(user, report):
590
585
  exp_ids.append(row[NotebookExperimentPseudoDef.EXPERIMENT_ID__FIELD_NAME.field_name])
591
586
  if not exp_ids:
592
587
  return []
@@ -651,4 +646,4 @@ class ExperimentReportUtil:
651
646
  report_builder.add_join(records_entry_join, ExperimentEntryRecordPseudoDef.DATA_TYPE_NAME)
652
647
  report_builder.add_join(experiment_entry_enb_entry_join, EnbEntryPseudoDef.DATA_TYPE_NAME)
653
648
  report_builder.add_join(enb_entry_experiment_join, NotebookExperimentPseudoDef.DATA_TYPE_NAME)
654
- return CustomReportDictAutoPager(user, report_builder.build_report_criteria()).get_all_at_once()
649
+ return CustomReportUtil.run_custom_report(user, report_builder.build_report_criteria())
@@ -1,64 +1,43 @@
1
1
  from sapiopylib.rest.utils.Protocols import ElnEntryStep
2
- from sapiopylib.rest.utils.plates.PlatingUtils import PlateLocation
3
- from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
4
2
  from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
5
3
 
6
- from sapiopycommons.datatype.data_fields import PlateDesignerWellElementFields
7
4
  from sapiopycommons.eln.experiment_handler import ExperimentHandler
8
- from sapiopycommons.eln.experiment_tags import PLATE_IDS_TAG
9
- from sapiopycommons.general.aliases import RecordIdentifier, AliasUtil, RecordModel, FieldValue
5
+ from sapiopycommons.general.aliases import SapioRecord, RecordIdentifier, AliasUtil
10
6
  from sapiopycommons.general.exceptions import SapioException
11
7
  from sapiopycommons.recordmodel.record_handler import RecordHandler
12
8
 
13
- # Shorthands for longer type names.
14
- WellElement = PlateDesignerWellElementFields
9
+ PLATE_IDS_TAG: str = "MultiLayerPlating_Plate_RecordIdList"
15
10
 
16
11
 
17
- # FR-47486: Change the PlateDesignerEntry class to extend ElnEntryStep instead of containing one.
18
- class PlateDesignerEntry(ElnEntryStep):
12
+ class PlateDesignerEntry:
19
13
  """
20
14
  A wrapper for 3D plate designer entries in experiments, providing functions for common actions when dealing with
21
15
  such entries.
22
16
  """
23
- _exp_handler: ExperimentHandler
24
- _rec_handler: RecordHandler
25
- _plates: list[RecordModel] | None
26
- _aliquots: list[RecordModel] | None
27
- _sources: list[RecordModel] | None
28
- _designer_elements: list[RecordModel] | None
29
- _designer_elements_by_plate: dict[int, list[RecordModel]] | None
30
- _plate_ids: list[int] | None
17
+ step: ElnEntryStep
18
+ __exp_handler: ExperimentHandler
19
+ __rec_handler: RecordHandler
20
+ __plates: list[SapioRecord] | None
21
+ __aliquots: list[SapioRecord] | None
22
+ __sources: list[SapioRecord] | None
23
+ __designer_elements: list[SapioRecord] | None
24
+ __plate_ids: list[int] | None
31
25
 
32
26
  def __init__(self, step: ElnEntryStep, exp_handler: ExperimentHandler):
33
27
  """
34
28
  :param step: The ElnEntryStep that is the 3D plate designer entry.
35
29
  :param exp_handler: An ExperimentHandler for the experiment that this entry comes from.
36
30
  """
37
- super().__init__(exp_handler.protocol, step.eln_entry)
38
- self._exp_handler = exp_handler
39
- self._rec_handler = RecordHandler(exp_handler.user)
40
- self._plates = None
41
- self._aliquots = None
42
- self._sources = None
43
- self._designer_elements = None
44
- self._designer_elements_by_plate = None
45
- self._plate_ids = None
46
-
47
- @property
48
- def step(self) -> ElnEntryStep:
49
- return self
50
-
51
- def clear_cache(self) -> None:
52
- """
53
- Clear the caches for the plates and plate designer well elements in this plate designer entry. This will require
54
- a new webservice call to get the plates and designer elements the next time they are requested.
55
- """
56
- self._plates = None
57
- self._designer_elements = None
58
- self._designer_elements_by_plate = None
31
+ self.step = step
32
+ self.__exp_handler = exp_handler
33
+ self.__rec_handler = RecordHandler(exp_handler.context)
34
+ self.__plates = None
35
+ self.__aliquots = None
36
+ self.__sources = None
37
+ self.__designer_elements = None
38
+ self.__plate_ids = None
59
39
 
60
- # CR-47491: Support not providing a wrapper type to receive PyRecordModels instead of WrappedRecordModels.
61
- def get_plates(self, wrapper_type: type[WrappedType] | None = None) -> list[WrappedType] | list[PyRecordModel]:
40
+ def get_plates(self, wrapper_type: type[WrappedType]) -> list[WrappedType]:
62
41
  """
63
42
  Get the plates that are in the designer entry.
64
43
 
@@ -66,16 +45,13 @@ class PlateDesignerEntry(ElnEntryStep):
66
45
  will be invalidated if a set_plates or add_plates call is made, requiring a new webservice call the next time
67
46
  this function is called.
68
47
 
69
- :param wrapper_type: The record model wrapper to use on the plates. If not provided, the returned records will
70
- be PyRecordModels instead of WrappedRecordModels.
48
+ :param wrapper_type: The record model wrapper to use.
71
49
  :return: A list of the plates in the designer entry.
72
50
  """
73
- if self._plates is not None:
74
- return self._plates
75
- if wrapper_type is None:
76
- wrapper_type = "Plate"
77
- self._plates = self._rec_handler.query_models_by_id(wrapper_type, self.__get_plate_ids())
78
- return self._plates
51
+ if self.__plates is not None:
52
+ return self.__plates
53
+ self.__plates = self.__rec_handler.query_models_by_id(wrapper_type, self.__get_plate_ids())
54
+ return self.__plates
79
55
 
80
56
  def set_plates(self, plates: list[RecordIdentifier]) -> None:
81
57
  """
@@ -100,50 +76,47 @@ class PlateDesignerEntry(ElnEntryStep):
100
76
  record_ids: list[int] = AliasUtil.to_record_ids(plates)
101
77
  self.__set_plate_ids(self.__get_plate_ids() + record_ids)
102
78
 
103
- def get_sources(self, wrapper_type: type[WrappedType] | str) -> list[WrappedType] | list[PyRecordModel]:
79
+ def get_sources(self, wrapper_type: type[WrappedType]) -> list[WrappedType]:
104
80
  """
105
81
  Get the source records that were used to populate the plate designer entry's sample table. This looks for the
106
- entries that the plate designer entry is implicitly dependent upon and gets their records if they match the
107
- data type name of the given wrapper.
82
+ entries that the plate designer entry is dependent upon and gets their records if they match the data type name
83
+ of the given wrapper.
108
84
 
109
85
  Makes a webservice call to retrieve the dependent entries if the experiment handler had not already cached it.
110
86
  Makes another webservice call to get the records from the dependent entry and caches them for future calls.
111
87
 
112
- :param wrapper_type: The record model wrapper or data type name of the records to retrieve. If a data type name
113
- is provided, then the returned records will be PyRecordModels instead of WrappedRecordModels.
88
+ :param wrapper_type: The record model wrapper to use.
114
89
  :return: A list of the source records that populate the plate designer entry's sample table.
115
90
  """
116
- if self._sources is not None:
117
- return self._sources
91
+ if self.__sources is not None:
92
+ return self.__sources
118
93
 
119
94
  records: list[WrappedType] = []
120
- dependent_ids: list[int] = self.step.eln_entry.related_entry_id_set
121
- for step in self._exp_handler.get_all_steps(wrapper_type):
95
+ dependent_ids: list[int] = self.step.eln_entry.dependency_set
96
+ for step in self.__exp_handler.get_all_steps(wrapper_type):
122
97
  if step.get_id() in dependent_ids:
123
- records.extend(self._exp_handler.get_step_models(step, wrapper_type))
98
+ records.extend(self.__exp_handler.get_step_models(step, wrapper_type))
124
99
 
125
- self._sources = records
126
- return self._sources
100
+ self.__sources = records
101
+ return self.__sources
127
102
 
128
- def get_aliquots(self, wrapper_type: type[WrappedType] | None = None) -> list[WrappedType] | list[PyRecordModel]:
103
+ def get_aliquots(self, wrapper_type: type[WrappedType]) -> list[WrappedType]:
129
104
  """
130
105
  Get the aliquots that were created from this plate designer entry upon its submission.
131
106
 
132
107
  Makes a webservice call to retrieve the aliquots from the plate designer entry and caches them for future calls.
133
108
 
134
- :param wrapper_type: The record model wrapper to use. If not provided, the returned records will be
135
- PyRecordModels instead of WrappedRecordModels.
109
+ :param wrapper_type: The record model wrapper to use.
136
110
  :return: A list of the aliquots created by the plate designer entry.
137
111
  """
138
- if not self._exp_handler.step_is_submitted(self.step):
112
+ if not self.__exp_handler.step_is_submitted(self.step):
139
113
  raise SapioException("The plate designer entry must be submitted before its aliquots can be retrieved.")
140
- if self._aliquots is not None:
141
- return self._aliquots
142
- self._aliquots = self._exp_handler.get_step_models(self.step, wrapper_type)
143
- return self._aliquots
114
+ if self.__aliquots is not None:
115
+ return self.__aliquots
116
+ self.__aliquots = self.__exp_handler.get_step_models(self.step, wrapper_type)
117
+ return self.__aliquots
144
118
 
145
- def get_plate_designer_well_elements(self, wrapper_type: type[WrappedType] | None = None) \
146
- -> list[WrappedType] | list[PyRecordModel]:
119
+ def get_plate_designer_well_elements(self, wrapper_type: type[WrappedType]) -> list[WrappedType]:
147
120
  """
148
121
  Get the plate designer well elements for the plates in the plate designer entry. These are the records in the
149
122
  system that determine how wells are displayed on each plate in the entry.
@@ -152,101 +125,28 @@ class PlateDesignerEntry(ElnEntryStep):
152
125
  This cache will be invalidated if a set_plates or add_plates call is made, requiring a new webservice call the
153
126
  next time this function is called.
154
127
 
155
- :param wrapper_type: The record model wrapper to use. If not provided, the returned records will be
156
- PyRecordModels instead of WrappedRecordModels.
157
- :return: A list of the plate designer well elements in the designer entry.
158
- """
159
- if self._designer_elements is not None:
160
- return self._designer_elements
161
- if wrapper_type is None:
162
- wrapper_type: str = WellElement.DATA_TYPE_NAME
163
- self._designer_elements = self._rec_handler.query_models(wrapper_type,
164
- WellElement.PLATE_RECORD_ID__FIELD,
165
- self.__get_plate_ids())
166
- if self._designer_elements_by_plate is None:
167
- self._designer_elements_by_plate = {}
168
- self._designer_elements_by_plate.clear()
169
- for element in self._designer_elements:
170
- plate_id: int = element.get(WellElement.PLATE_RECORD_ID__FIELD)
171
- self._designer_elements_by_plate.setdefault(plate_id, []).append(element)
172
- return self._designer_elements
173
-
174
- def get_well_elements_by_plate(self, plate: RecordIdentifier, wrapper_type: type[WrappedType] | None = None) \
175
- -> list[WrappedType] | list[PyRecordModel]:
176
- """
177
- Get the plate designer well elements for the given plate in the plate designer entry. These are the records in
178
- the system that determine how wells are displayed on the plate in the entry.
179
-
180
- Makes a webservice call to get the plate designer well elements of the entry and caches them for future calls.
181
- This cache will be invalidated if a set_plates or add_plates call is made, requiring a new webservice call the
182
- next time this function is called.
183
-
184
- :param plate: The plate to get the well elements for.
185
- :param wrapper_type: The record model wrapper to use. If not provided, the returned records will be
186
- PyRecordModels instead of WrappedRecordModels.
128
+ :param wrapper_type: The record model wrapper to use.
187
129
  :return: A list of the plate designer well elements in the designer entry.
188
130
  """
189
- plate: int = AliasUtil.to_record_id(plate)
190
- if plate not in self.__get_plate_ids():
191
- raise SapioException(f"Plate record ID {plate} is not in this plate designer entry.")
192
- if self._designer_elements_by_plate is None:
193
- self.get_plate_designer_well_elements(wrapper_type)
194
- return self._designer_elements_by_plate[plate]
195
-
196
- def create_well_element(self, sample: RecordModel, plate: RecordModel, location: PlateLocation | None = None,
197
- wrapper_type: type[WrappedType] | None = None) -> WrappedType | PyRecordModel:
198
- """
199
- Create a new plate designer well element for the input sample and plate. A record model manager store and commit
200
- must be called to save this new well element to the server.
201
-
202
- :param sample: The sample that the element is for. Must exist in the system (i.e. have a >0 record ID).
203
- :param plate: The plate that the element is for. Must exist in the system (i.e. have a >0 record ID).
204
- :param location: The location of the well element. If not provided, the row and column position fields of the
205
- sample will be used.
206
- :param wrapper_type: The record model wrapper to use for the plate designer well element. If not provided, the
207
- returned record will be a PyRecordModel instead of a WrappedRecordModel.
208
- :return: The newly created PlateDesignerWellElementModel.
209
- """
210
- # Confirm that we can actually make a designer element for the input records.
211
- if AliasUtil.to_record_id(sample) <= 0:
212
- raise SapioException("Cannot create plate designer well element for sample without a record ID.")
213
- if AliasUtil.to_record_id(plate) <= 0:
214
- raise SapioException("Cannot create plate designer well element for plate without a record ID.")
215
- if AliasUtil.to_data_type_name(sample) != "Sample":
216
- raise SapioException("Sample record must be of type Sample.")
217
- if AliasUtil.to_data_type_name(plate) != "Plate":
218
- raise SapioException("Plate record must be of type Plate.")
219
-
220
- dt: type[WrappedType] | str = wrapper_type if wrapper_type else WellElement.DATA_TYPE_NAME
221
- plate_id: int = AliasUtil.to_record_id(plate)
222
- fields: dict[str, FieldValue] = {
223
- WellElement.SOURCE_RECORD_ID__FIELD: AliasUtil.to_record_id(sample),
224
- WellElement.PLATE_RECORD_ID__FIELD: plate_id,
225
- WellElement.ROW_POSITION__FIELD: location.row_pos if location else sample.get_field_value("RowPosition"),
226
- WellElement.COL_POSITION__FIELD: str(location.col_pos) if location else sample.get_field_value("ColPosition"),
227
- WellElement.SOURCE_DATA_TYPE_NAME__FIELD: "Sample",
228
- }
229
- element = self._rec_handler.add_models_with_data(dt, [fields])[0]
230
-
231
- # Add the new element to the cache.
232
- if self._designer_elements is not None:
233
- self._designer_elements.append(element)
234
- if self._designer_elements_by_plate is not None and plate_id in self._designer_elements_by_plate:
235
- self._designer_elements_by_plate.setdefault(plate_id, []).append(element)
236
- return element
131
+ if self.__designer_elements is not None:
132
+ return self.__designer_elements
133
+ self.__designer_elements = self.__rec_handler.query_models(wrapper_type, "PlateRecordId",
134
+ self.__get_plate_ids())
135
+ return self.__designer_elements
237
136
 
238
137
  def __get_plate_ids(self) -> list[int]:
239
- if self._plate_ids is not None:
240
- return self._plate_ids
241
- id_tag: str = self._exp_handler.get_step_option(self.step, PLATE_IDS_TAG)
138
+ if self.__plate_ids is not None:
139
+ return self.__plate_ids
140
+ id_tag: str = self.__exp_handler.get_step_option(self.step, PLATE_IDS_TAG)
242
141
  if not id_tag:
243
142
  raise SapioException("No plates in the plate designer entry")
244
- self._plate_ids = [int(x) for x in id_tag.split(",")]
245
- return self._plate_ids
143
+ self.__plate_ids = [int(x) for x in id_tag.split(",")]
144
+ return self.__plate_ids
246
145
 
247
146
  def __set_plate_ids(self, record_ids: list[int]) -> None:
248
147
  record_ids.sort()
249
- self._exp_handler.add_step_options(self.step, {PLATE_IDS_TAG: ",".join([str(x) for x in record_ids])})
250
- self._plate_ids = record_ids
148
+ self.__exp_handler.add_step_options(self.step, {PLATE_IDS_TAG: ",".join([str(x) for x in record_ids])})
149
+ self.__plate_ids = record_ids
251
150
  # The plates and designer elements caches have been invalidated.
252
- self.clear_cache()
151
+ self.__plates = None
152
+ self.__designer_elements = None
@@ -1,7 +1,6 @@
1
1
  import base64
2
2
  import io
3
3
  import urllib.parse
4
- from typing import Any
5
4
 
6
5
  from requests import Response
7
6
  from sapiopylib.rest.User import SapioUser
@@ -9,36 +8,6 @@ from sapiopylib.rest.User import SapioUser
9
8
  from sapiopycommons.general.aliases import UserIdentifier, AliasUtil
10
9
 
11
10
 
12
- # FR-47387: Add support for the metadata endpoints in FileBridge.
13
- class FileBridgeMetadata:
14
- """
15
- Metadata for a file or directory in FileBridge.
16
- """
17
- file_name: str
18
- """The name of the file or directory."""
19
- is_file: bool
20
- """True if the metadata is for a file, False if it is for a directory."""
21
- is_directory: bool
22
- """True if the metadata is for a directory, False if it is for a file."""
23
- size: int
24
- """The size of the file in bytes. For directories, this value will always be zero."""
25
- creation_time: int
26
- """The time the file or directory was created, in milliseconds since the epoch."""
27
- last_accessed_time: int
28
- """The time the file or directory was last accessed, in milliseconds since the epoch."""
29
- last_modified_time: int
30
- """The time the file or directory was last modified, in milliseconds since the epoch."""
31
-
32
- def __init__(self, json_dict: dict[str, Any]):
33
- self.file_name = json_dict['fileName']
34
- self.is_file = json_dict['isFile']
35
- self.is_directory = json_dict['isDirectory']
36
- self.size = json_dict['size']
37
- self.creation_time = json_dict['creationTime']
38
- self.last_accessed_time = json_dict['lastAccessTime']
39
- self.last_modified_time = json_dict['lastModifiedTime']
40
-
41
-
42
11
  # FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
43
12
  class FileBridge:
44
13
  @staticmethod
@@ -168,48 +137,3 @@ class FileBridge:
168
137
  user: SapioUser = AliasUtil.to_sapio_user(context)
169
138
  response = user.delete(sub_path, params=params)
170
139
  user.raise_for_status(response)
171
-
172
- @staticmethod
173
- def file_metadata(context: UserIdentifier, bridge_name: str, file_path: str) -> FileBridgeMetadata:
174
- """
175
- Get metadata for a file or directory in FileBridge.
176
-
177
- The file path may be to a directory, in which case only the metadata for that directory will be returned. If you
178
- want the metadata for the contents of a directory, then use the directory_metadata function.
179
-
180
- :param context: The current webhook context or a user object to send requests from.
181
- :param bridge_name: The name of the bridge to use. This is the "connection name" in the
182
- file bridge configurations.
183
- :param file_path: The path to the file to retrieve the metadata from.
184
- :return: The metadata for the file.
185
- """
186
- sub_path = '/ext/filebridge/file/metadata'
187
- params = {
188
- 'Filepath': f"bridge://{bridge_name}/{file_path}"
189
- }
190
- user: SapioUser = AliasUtil.to_sapio_user(context)
191
- response = user.get(sub_path, params=params)
192
- user.raise_for_status(response)
193
- response_body: dict[str, Any] = response.json()
194
- return FileBridgeMetadata(response_body)
195
-
196
- @staticmethod
197
- def directory_metadata(context: UserIdentifier, bridge_name: str, file_path: str) -> list[FileBridgeMetadata]:
198
- """
199
- Get metadata for every file or nested directory in a directory in FileBridge.
200
-
201
- :param context: The current webhook context or a user object to send requests from.
202
- :param bridge_name: The name of the bridge to use. This is the "connection name" in the
203
- file bridge configurations.
204
- :param file_path: The path to the directory to retrieve the metadata of the contents.
205
- :return: A list of the metadata for the contents of the directory.
206
- """
207
- sub_path = '/ext/filebridge/directory/metadata'
208
- params = {
209
- 'Filepath': f"bridge://{bridge_name}/{file_path}"
210
- }
211
- user: SapioUser = AliasUtil.to_sapio_user(context)
212
- response = user.get(sub_path, params=params)
213
- user.raise_for_status(response)
214
- response_body: list[dict[str, Any]] = response.json()
215
- return [FileBridgeMetadata(x) for x in response_body]