sapiopycommons 2025.3.21a458__py3-none-any.whl → 2025.3.26a460__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.

@@ -28,6 +28,7 @@ from sapiopylib.rest.pojo.webhook.WebhookEnums import FormAccessLevel, ScanToSel
28
28
  from sapiopylib.rest.utils.DataTypeCacheManager import DataTypeCacheManager
29
29
  from sapiopylib.rest.utils.FormBuilder import FormBuilder
30
30
  from sapiopylib.rest.utils.recorddatasinks import InMemoryRecordDataSink
31
+ from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
31
32
  from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
32
33
 
33
34
  from sapiopycommons.callbacks.field_builder import FieldBuilder, AnyFieldInfo
@@ -182,7 +183,7 @@ class CallbackUtil:
182
183
  def ok_dialog(self, title: str, msg: str) -> None:
183
184
  """
184
185
  Create an option dialog where the only option is "OK". Doesn't allow the user to cancel the
185
- dialog using the X at the top right corner. Returns nothing.
186
+ dialog using the X in the top right corner. Returns nothing.
186
187
 
187
188
  :param title: The title of the dialog.
188
189
  :param msg: The message to display in the dialog. This can be formatted using HTML elements.
@@ -407,15 +408,17 @@ class CallbackUtil:
407
408
  default_modifier=default_modifier, field_modifiers=field_modifiers)
408
409
  record.set_field_values(results)
409
410
 
411
+ # CR-47491: Support providing a data type name string to receive PyRecordModels instead of requiring a WrapperType.
410
412
  def create_record_form_dialog(self,
411
413
  title: str,
412
414
  msg: str,
413
415
  fields: list[FieldIdentifier | FieldFilterCriteria] | DataTypeLayoutIdentifier,
414
- wrapper_type: type[WrappedType],
416
+ wrapper_type: type[WrappedType] | str,
415
417
  column_positions: dict[str, tuple[int, int]] | None = None,
416
418
  *,
417
419
  default_modifier: FieldModifier | None = None,
418
- field_modifiers: dict[FieldIdentifier, FieldModifier] | None = None) -> WrappedType:
420
+ field_modifiers: dict[FieldIdentifier, FieldModifier] | None = None) \
421
+ -> WrappedType | PyRecordModel:
419
422
  """
420
423
  Create a form dialog where the user may input data into the fields of the form. The form is constructed from
421
424
  a record that is created using the given record model wrapper. After the user submits this dialog, the values
@@ -431,7 +434,9 @@ class CallbackUtil:
431
434
  [Extension Data Type Name].[Data Field Name]. This parameter may also be an identifier for a data type
432
435
  layout from the data type of the provided records. If None, then the layout assigned to the current user's
433
436
  group for this data type will be used. FieldFilterCriteria may also be provided in lieu of field names.
434
- :param wrapper_type: The record model wrapper of the record to be created and updated.
437
+ :param wrapper_type: The record model wrapper or data type name of the record to be created and updated.
438
+ If a data type name is provided, the returned record will be a PyRecordModel instead of a
439
+ WrappedRecordModel.
435
440
  :param column_positions: If a tuple is provided for a field name, alters that field's column position and column
436
441
  span. (Field order is still determined by the fields list.) Has no effect if the fields parameter provides
437
442
  a data type layout.
@@ -446,7 +451,7 @@ class CallbackUtil:
446
451
  None, then the default modifier will be used.
447
452
  :return: The record model that was created and updated by the user.
448
453
  """
449
- record: WrappedType = self.rec_handler.add_model(wrapper_type)
454
+ record: WrappedType | PyRecordModel = self.rec_handler.add_model(wrapper_type)
450
455
  self.set_record_form_dialog(title, msg, fields, record, column_positions,
451
456
  default_modifier=default_modifier, field_modifiers=field_modifiers)
452
457
  return record
@@ -794,7 +799,7 @@ class CallbackUtil:
794
799
  title: str,
795
800
  msg: str,
796
801
  fields: list[FieldValue] | DataTypeLayoutIdentifier,
797
- wrapper_type: type[WrappedType],
802
+ wrapper_type: type[WrappedType] | str,
798
803
  count: int | tuple[int, int],
799
804
  *,
800
805
  default_modifier: FieldModifier | None = None,
@@ -803,7 +808,7 @@ class CallbackUtil:
803
808
  image_data: list[bytes] | None = None,
804
809
  require_input: bool = False,
805
810
  repeat_message: str | None = "Please provide a value to continue.") \
806
- -> list[WrappedType]:
811
+ -> list[WrappedType] | list[PyRecordModel]:
807
812
  """
808
813
  Create a table dialog where the user may input data into the fields of the table. The table is constructed from
809
814
  a list of records that are created using the given record model wrapper. After the user submits this dialog,
@@ -819,7 +824,8 @@ class CallbackUtil:
819
824
  [Extension Data Type Name].[Data Field Name]. This parameter may also be an identifier for a data type
820
825
  layout from the data type of the provided records. If None, then the layout assigned to the current user's
821
826
  group for this data type will be used.
822
- :param wrapper_type: The record model wrapper of the records to be created and updated.
827
+ :param wrapper_type: The record model wrapper or data type name of the records to be created and updated. If
828
+ a data type name is provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
823
829
  :param count: The number of records to create. If provided as a tuple of two integers, the user will first be
824
830
  prompted to select an integer between the two values in the tuple.
825
831
  :param default_modifier: A default field modifier that will be applied to the given fields. This can be used to
@@ -844,7 +850,7 @@ class CallbackUtil:
844
850
  count: int = self.__prompt_for_count(count, wrapper_type, require_input, repeat_message)
845
851
  if count <= 0:
846
852
  return []
847
- records: list[WrappedType] = self.rec_handler.add_models(wrapper_type, count)
853
+ records: list[WrappedType] | list[PyRecordModel] = self.rec_handler.add_models(wrapper_type, count)
848
854
  self.set_record_table_dialog(title, msg, fields, records,
849
855
  default_modifier=default_modifier, field_modifiers=field_modifiers,
850
856
  group_by=group_by, image_data=image_data)
@@ -974,7 +980,7 @@ class CallbackUtil:
974
980
  title: str,
975
981
  msg: str,
976
982
  fields: list[FieldValue] | DataTypeLayoutIdentifier,
977
- wrapper_type: type[WrappedType],
983
+ wrapper_type: type[WrappedType] | str,
978
984
  count: int | tuple[int, int],
979
985
  *,
980
986
  default_modifier: FieldModifier | None = None,
@@ -1003,7 +1009,8 @@ class CallbackUtil:
1003
1009
  [Extension Data Type Name].[Data Field Name]. This parameter may also be an identifier for a data type
1004
1010
  layout from the data type of the provided records. If None, then the layout assigned to the current user's
1005
1011
  group for this data type will be used.
1006
- :param wrapper_type: The record model wrapper of the records to be created and updated.
1012
+ :param wrapper_type: The record model wrapper or data type name of the records to be created and updated. If
1013
+ a data type name is provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
1007
1014
  :param count: The number of records to create. If provided as a tuple of two integers, the user will first be
1008
1015
  prompted to select an integer between the two values in the tuple.
1009
1016
  :param default_modifier: A default field modifier that will be applied to the given fields. This can be used to
@@ -1427,7 +1434,7 @@ class CallbackUtil:
1427
1434
 
1428
1435
  # CR-47377: Add allow_creation and default_creation_number to cover new parameters of this request type from 24.12.
1429
1436
  def input_selection_dialog(self,
1430
- wrapper_type: type[WrappedType],
1437
+ wrapper_type: type[WrappedType] | str,
1431
1438
  msg: str,
1432
1439
  multi_select: bool = True,
1433
1440
  only_key_fields: bool = False,
@@ -1442,13 +1449,14 @@ class CallbackUtil:
1442
1449
  *,
1443
1450
  require_selection: bool = False,
1444
1451
  repeat_message: str | None = "Please provide a selection to continue.") \
1445
- -> list[WrappedType]:
1452
+ -> list[WrappedType] | list[PyRecordModel]:
1446
1453
  """
1447
1454
  Display a table of records that exist in the system matching the given data type and filter criteria and have
1448
1455
  the user select one or more records from the table.
1449
1456
  The title of a selection dialog will always be "Select [plural display name]".
1450
1457
 
1451
- :param wrapper_type: The record model wrapper for the records to display in the dialog.
1458
+ :param wrapper_type: The record model wrapper or data type name for the records to display in the dialog. If
1459
+ a data type name is provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
1452
1460
  :param msg: The message to show near the top of the dialog, below the title. This can be used to
1453
1461
  instruct the user on what is desired from the dialog. This can be formatted using HTML elements.
1454
1462
  :param multi_select: Whether the user may select multiple items at once in this dialog.
@@ -1489,7 +1497,7 @@ class CallbackUtil:
1489
1497
  :return: A list of the records selected by the user in the dialog, wrapped as record models using the provided
1490
1498
  wrapper.
1491
1499
  """
1492
- data_type: str = wrapper_type.get_wrapper_data_type_name()
1500
+ data_type: str = AliasUtil.to_data_type_name(wrapper_type)
1493
1501
 
1494
1502
  # Reduce the provided lists of records down to lists of record IDs.
1495
1503
  if preselected_records:
@@ -1795,7 +1803,7 @@ class CallbackUtil:
1795
1803
  temp_dt.set_field_definition(modifier.modify_field(field_def))
1796
1804
  return temp_dt
1797
1805
 
1798
- def __prompt_for_count(self, count: tuple[int, int] | int, wrapper_type: type[WrappedType],
1806
+ def __prompt_for_count(self, count: tuple[int, int] | int, wrapper_type: type[WrappedType] | str,
1799
1807
  require_input: bool, repeat_message: str) -> int:
1800
1808
  """
1801
1809
  Given a count value, if it is a tuple representing an allowable range of values for a number of records to
@@ -1806,7 +1814,7 @@ class CallbackUtil:
1806
1814
  if hasattr(wrapper_type, "PLURAL_DISPLAY_NAME"):
1807
1815
  plural: str = wrapper_type.PLURAL_DISPLAY_NAME
1808
1816
  else:
1809
- plural: str = self.dt_cache.get_plural_display_name(wrapper_type.get_wrapper_data_type_name())
1817
+ plural: str = self.dt_cache.get_plural_display_name(AliasUtil.to_data_type_name(wrapper_type))
1810
1818
  min_val, max_val = count
1811
1819
  msg: str = f"How many {plural} should be created? ({min_val} to {max_val})"
1812
1820
  count: int = self.integer_input_dialog(f"Create {plural}", msg, "Count", min_val, min_val, max_val,
@@ -8,6 +8,7 @@ from sapiopylib.rest.pojo.CustomReport import CustomReportCriteria, CustomReport
8
8
  from sapiopylib.rest.pojo.datatype.FieldDefinition import FieldType
9
9
  from sapiopylib.rest.utils.autopaging import SapioPyAutoPager, PagerResultCriteriaType, _default_report_page_size, \
10
10
  _default_record_page_size
11
+ from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
11
12
  from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
12
13
 
13
14
  from sapiopycommons.general.aliases import FieldValue, UserIdentifier, AliasUtil, RecordModel
@@ -110,20 +111,22 @@ class QuickReportDictAutoPager(_DictReportPagerBase):
110
111
  super().__init__(user, first_page_criteria)
111
112
 
112
113
 
113
- class _RecordReportPagerBase(SapioPyAutoPager[CustomReportCriteria, WrappedType], ABC):
114
+ # CR-47491: Support providing a data type name string to receive PyRecordModels instead of requiring a WrapperType.
115
+ class _RecordReportPagerBase(SapioPyAutoPager[CustomReportCriteria, WrappedType | PyRecordModel], ABC):
114
116
  """
115
117
  A base class for automatically paging through a report and returning the results as a list of records.
116
118
  """
117
119
  _columns: list[ReportColumn]
118
- _wrapper: type[WrappedType]
120
+ _query_type: type[WrappedType] | str
119
121
  _data_type: str
120
122
  _rec_handler: RecordHandler
121
123
  _report_man: CustomReportManager
122
124
 
123
- def __init__(self, user: UserIdentifier, first_page_criteria: CustomReportCriteria, wrapper_type: type[WrappedType]):
125
+ def __init__(self, user: UserIdentifier, first_page_criteria: CustomReportCriteria,
126
+ wrapper_type: type[WrappedType] | str):
124
127
  self._columns = first_page_criteria.column_list
125
- self._wrapper = wrapper_type
126
- self._data_type = wrapper_type.get_wrapper_data_type_name()
128
+ self._query_type = wrapper_type
129
+ self._data_type = AliasUtil.to_data_type_name(wrapper_type)
127
130
  self._rec_handler = RecordHandler(user)
128
131
  super().__init__(AliasUtil.to_sapio_user(user), first_page_criteria)
129
132
  self._report_man = DataMgmtServer.get_custom_report_manager(self.user)
@@ -139,9 +142,9 @@ class _RecordReportPagerBase(SapioPyAutoPager[CustomReportCriteria, WrappedType]
139
142
  def default_first_page_criteria(self) -> PagerResultCriteriaType:
140
143
  raise ValueError("Cannot generate a default first page criteria for custom reports.")
141
144
 
142
- def get_next_page_result(self) -> tuple[CustomReportCriteria | None, Queue[WrappedType]]:
145
+ def get_next_page_result(self) -> tuple[CustomReportCriteria | None, Queue[WrappedType] | Queue[PyRecordModel]]:
143
146
  report: CustomReport = self._report_man.run_custom_report(self.next_page_criteria)
144
- queue: Queue[WrappedType] = Queue()
147
+ queue = Queue()
145
148
  id_index: int = -1
146
149
  for i, column in enumerate(self._columns):
147
150
  if column.data_type_name == self._data_type and column.data_field_name == "RecordId":
@@ -151,7 +154,7 @@ class _RecordReportPagerBase(SapioPyAutoPager[CustomReportCriteria, WrappedType]
151
154
  raise SapioException(f"This report does not contain a Record ID column for the given record model type "
152
155
  f"{self._data_type}.")
153
156
  ids: list[int] = [row[id_index] for row in report.result_table]
154
- for row in self._rec_handler.query_models_by_id(self._wrapper, ids, page_size=report.page_size):
157
+ for row in self._rec_handler.query_models_by_id(self._query_type, ids, page_size=report.page_size):
155
158
  queue.put(row)
156
159
  if report.has_next_page:
157
160
  next_page_criteria = copy(self.next_page_criteria)
@@ -165,12 +168,15 @@ class CustomReportRecordAutoPager(_RecordReportPagerBase):
165
168
  """
166
169
  A class that automatically pages through a custom report and returns the results as a list of records.
167
170
  """
168
- def __init__(self, user: UserIdentifier, report_criteria: CustomReportCriteria, wrapper_type: type[WrappedType],
169
- page_number: int = 0, page_size: int = _default_record_page_size):
171
+ def __init__(self, user: UserIdentifier, report_criteria: CustomReportCriteria,
172
+ wrapper_type: type[WrappedType] | str, page_number: int = 0,
173
+ page_size: int = _default_record_page_size):
170
174
  """
171
175
  :param user: The current webhook context or a user object to send requests from.
172
176
  :param report_criteria: The custom report criteria to run.
173
- :param wrapper_type: The record model wrapper type to use for the records.
177
+ :param wrapper_type: The record model wrapper type or data type name of the records being searched for.
178
+ If a data type name was used instead of a model wrapper, then the returned records will be PyRecordModels
179
+ instead of WrappedRecordModels.
174
180
  :param page_number: The page number to start on. The first page is page 0.
175
181
  :param page_size: The number of results to return per page.
176
182
  """
@@ -188,12 +194,14 @@ class SystemReportRecordAutoPager(_RecordReportPagerBase):
188
194
  System reports are also known as predefined searches in the system and must be defined in the data designer for
189
195
  a specific data type. That is, saved searches created by users cannot be run using this function.
190
196
  """
191
- def __init__(self, user: UserIdentifier, report_name: str, wrapper_type: type[WrappedType],
197
+ def __init__(self, user: UserIdentifier, report_name: str, wrapper_type: type[WrappedType] | str,
192
198
  page_number: int = 0, page_size: int = _default_record_page_size):
193
199
  """
194
200
  :param user: The current webhook context or a user object to send requests from.
195
201
  :param report_name: The name of the system report to run.
196
- :param wrapper_type: The record model wrapper type to use for the records.
202
+ :param wrapper_type: The record model wrapper type or data type name of the records being searched for.
203
+ If a data type name was used instead of a model wrapper, then the returned records will be PyRecordModels
204
+ instead of WrappedRecordModels.
197
205
  :param page_number: The page number to start on. The first page is page 0.
198
206
  :param page_size: The number of results to return per page.
199
207
  """
@@ -208,16 +216,18 @@ class QuickReportRecordAutoPager(_RecordReportPagerBase):
208
216
  """
209
217
  A class that automatically pages through a quick report and returns the results as a list of records.
210
218
  """
211
- def __init__(self, user: UserIdentifier, report_term: RawReportTerm, wrapper_type: type[WrappedType],
219
+ def __init__(self, user: UserIdentifier, report_term: RawReportTerm, wrapper_type: type[WrappedType] | str,
212
220
  page_number: int = 0, page_size: int = _default_record_page_size):
213
221
  """
214
222
  :param user: The current webhook context or a user object to send requests from.
215
223
  :param report_term: The raw report term to use for the quick report.
216
- :param wrapper_type: The record model wrapper type to use for the records.
224
+ :param wrapper_type: The record model wrapper type or data type name of the records being searched for.
225
+ If a data type name was used instead of a model wrapper, then the returned records will be PyRecordModels
226
+ instead of WrappedRecordModels.
217
227
  :param page_number: The page number to start on. The first page is page 0.
218
228
  :param page_size: The number of results to return per page.
219
229
  """
220
- if report_term.data_type_name != wrapper_type.get_wrapper_data_type_name():
230
+ if report_term.data_type_name != AliasUtil.to_data_type_name(wrapper_type):
221
231
  raise SapioException("The data type name of the report term must match the data type name of the wrapper type.")
222
232
  first_page_criteria: CustomReportCriteria = CustomReportUtil.get_quick_report_criteria(user, report_term)
223
233
  first_page_criteria.page_number = page_number
@@ -225,12 +235,12 @@ class QuickReportRecordAutoPager(_RecordReportPagerBase):
225
235
  super().__init__(user, first_page_criteria, wrapper_type)
226
236
 
227
237
 
228
- def _add_record_id_column(report: CustomReportCriteria, wrapper_type: type[WrappedType]) -> None:
238
+ def _add_record_id_column(report: CustomReportCriteria, wrapper_type: type[WrappedType] | str) -> None:
229
239
  """
230
240
  Given a custom report criteria, ensure that the report contains a Record ID column for the given record model's
231
241
  data type. Add one if it is missing.
232
242
  """
233
- dt: str = wrapper_type.get_wrapper_data_type_name()
243
+ dt: str = AliasUtil.to_data_type_name(wrapper_type)
234
244
  # Ensure that the root data type is the one we're looking for.
235
245
  report.root_data_type = dt
236
246
  # Enforce that the given custom report has a record ID column.
@@ -1,6 +1,7 @@
1
1
  import io
2
2
 
3
3
  from sapiopylib.rest.DataMgmtService import DataMgmtServer
4
+ from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
4
5
  from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
5
6
 
6
7
  from sapiopycommons.general.aliases import AliasUtil, SapioRecord, UserIdentifier
@@ -49,9 +50,10 @@ class AttachmentUtil:
49
50
  with io.BytesIO(file_bytes) as stream:
50
51
  dr_man.set_attachment_data(attachment, file_name, stream)
51
52
 
53
+ # CR-47491: Support providing a data type name string to receive PyRecordModels instead of requiring a WrapperType.
52
54
  @staticmethod
53
55
  def create_attachment(context: UserIdentifier, file_name: str, file_bytes: bytes,
54
- wrapper_type: type[WrappedType]) -> WrappedType:
56
+ wrapper_type: type[WrappedType] | str) -> WrappedType | PyRecordModel:
55
57
  """
56
58
  Create an attachment data type and initialize its attachment bytes at the same time.
57
59
  Makes a webservice call to create the attachment record and a second to set its bytes.
@@ -62,6 +64,6 @@ class AttachmentUtil:
62
64
  :param wrapper_type: The attachment type to create.
63
65
  :return: A record model for the newly created attachment.
64
66
  """
65
- attachment: WrappedType = RecordHandler(context).create_models(wrapper_type, 1)[0]
67
+ attachment: WrappedType | PyRecordModel = RecordHandler(context).create_models(wrapper_type, 1)[0]
66
68
  AttachmentUtil.set_attachment_bytes(context, attachment, file_name, file_bytes)
67
69
  return attachment
@@ -59,3 +59,25 @@ class ProcessWorkflowTrackingFields:
59
59
  WORKFLOW_START_USER_ID__FIELD = WrapperField("WorkflowStartUserId", FieldType.STRING)
60
60
  WORKFLOW_TAT__FIELD = WrapperField("WorkflowTAT", FieldType.DOUBLE)
61
61
  WORKFLOW_VERSION__FIELD = WrapperField("WorkflowVersion", FieldType.LONG)
62
+
63
+ class PlateDesignerWellElementFields:
64
+ DATA_TYPE_NAME = 'PlateDesignerWellElement'
65
+ ACTUAL_VOLUME_REMOVED__FIELD = WrapperField("ActualVolumeRemoved", FieldType.DOUBLE)
66
+ ALIQUOT_SAMPLE_RECORD_ID__FIELD = WrapperField("AliquotSampleRecordId", FieldType.LONG)
67
+ COL_POSITION__FIELD = WrapperField("ColPosition", FieldType.SELECTION)
68
+ CONCENTRATION__FIELD = WrapperField("Concentration", FieldType.DOUBLE)
69
+ CONCENTRATION_UNITS__FIELD = WrapperField("ConcentrationUnits", FieldType.STRING)
70
+ CONTROL_TYPE__FIELD = WrapperField("ControlType", FieldType.STRING)
71
+ DILUTION_SCHEME__FIELD = WrapperField("DilutionScheme", FieldType.DOUBLE)
72
+ IS_CONTROL__FIELD = WrapperField("IsControl", FieldType.BOOLEAN)
73
+ LAYER__FIELD = WrapperField("Layer", FieldType.INTEGER)
74
+ PLATE_RECORD_ID__FIELD = WrapperField("PlateRecordId", FieldType.LONG)
75
+ ROW_POSITION__FIELD = WrapperField("RowPosition", FieldType.SELECTION)
76
+ SOURCE_DATA_TYPE_NAME__FIELD = WrapperField("SourceDataTypeName", FieldType.STRING)
77
+ SOURCE_RECORD_ID__FIELD = WrapperField("SourceRecordId", FieldType.LONG)
78
+ SOURCE_SAMPLE_CONCENTRATION__FIELD = WrapperField("SourceSampleConcentration", FieldType.DOUBLE)
79
+ SOURCE_SAMPLE_MASS__FIELD = WrapperField("SourceSampleMass", FieldType.DOUBLE)
80
+ SOURCE_SAMPLE_VOLUME__FIELD = WrapperField("SourceSampleVolume", FieldType.DOUBLE)
81
+ SOURCE_VOLUME_TO_REMOVE__FIELD = WrapperField("SourceVolumeToRemove", FieldType.DOUBLE)
82
+ TARGET_MASS__FIELD = WrapperField("TargetMass", FieldType.DOUBLE)
83
+ VOLUME__FIELD = WrapperField("Volume", FieldType.DOUBLE)