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
@@ -6,11 +6,12 @@ from sapiopylib.rest.User import SapioUser
6
6
  from sapiopylib.rest.pojo.DataRecord import DataRecord
7
7
  from sapiopylib.rest.pojo.eln.SapioELNEnums import ElnBaseDataType
8
8
  from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
9
- from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelManager, RecordModelInstanceManager
9
+ from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
10
10
  from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
11
11
 
12
12
  from sapiopycommons.general.aliases import FieldMap, AliasUtil, DataTypeIdentifier
13
13
  from sapiopycommons.general.exceptions import SapioException
14
+ from sapiopycommons.recordmodel.record_handler import RecordHandler
14
15
 
15
16
 
16
17
  # FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
@@ -18,23 +19,23 @@ class ElnRuleHandler:
18
19
  """
19
20
  A class which helps with the parsing and navigation of the ELN rule result map of a webhook context.
20
21
  """
21
- __context: SapioWebhookContext
22
+ _context: SapioWebhookContext
22
23
  """The context that this handler is working from."""
23
24
 
24
- __inst_man: RecordModelInstanceManager
25
- """The record model instance manager, used for wrapping the data records as record models."""
25
+ _rec_handler: RecordHandler
26
+ """The record handler, used for wrapping the data records as record models."""
26
27
 
27
28
  # Reformatted and cached version of the Velox ELN rule result map for easier handling.
28
- __records: dict[str, set[DataRecord]]
29
+ _records: dict[str, set[DataRecord]]
29
30
  """A mapping of data type to the set of data records from the context that match that data type."""
30
- __entry_to_records: dict[str, dict[str, set[DataRecord]]]
31
+ _entry_to_records: dict[str, dict[str, set[DataRecord]]]
31
32
  """A mapping of entry name to the sets of data records for that entry, each set of records being mapped by its
32
33
  data type."""
33
- __field_maps: dict[str, dict[int, FieldMap]]
34
+ _field_maps: dict[str, dict[int, FieldMap]]
34
35
  """A mapping of data type to the field maps from the context that match that data type. In order to prevent
35
36
  duplicate field maps, each field map is in a dict keyed by the RecordId field in the field map, since field maps
36
37
  are just dictionaries and dictionaries aren't hashable and therefore can't go in a set."""
37
- __entry_to_field_maps: dict[str, dict[str, dict[int, FieldMap]]]
38
+ _entry_to_field_maps: dict[str, dict[str, dict[int, FieldMap]]]
38
39
  """A mapping of entry name to the lists of field maps for that entry, each grouping of field maps being mapped by
39
40
  its data type."""
40
41
 
@@ -59,8 +60,8 @@ class ElnRuleHandler:
59
60
 
60
61
  if context.velox_eln_rule_result_map is None:
61
62
  raise SapioException("No Velox ELN rule result map in context for ElnRuleHandler to parse.")
62
- self.__context = context
63
- self.__inst_man = RecordModelManager(context.user).instance_manager
63
+ self._context = context
64
+ self._rec_handler = RecordHandler(context)
64
65
  self.__cache_records()
65
66
 
66
67
  def __cache_records(self) -> None:
@@ -70,10 +71,10 @@ class ElnRuleHandler:
70
71
  from to another dict that maps the data types to the records of that type.
71
72
  Doesn't cache any relationship info from the VeloxRuleType of the rule results.
72
73
  """
73
- self.__records = {}
74
- self.__entry_to_records = {}
74
+ self._records = {}
75
+ self._entry_to_records = {}
75
76
  # Each entry in the context has a list of results for that entry.
76
- for entry, entry_results in self.__context.velox_eln_rule_result_map.items():
77
+ for entry, entry_results in self._context.velox_eln_rule_result_map.items():
77
78
  # Keep track of the records for this specific entry.
78
79
  entry_dict: dict[str, set[DataRecord]] = {}
79
80
  # Entry results consist of a record ID of the record in the entry and a list of results tied to that record.
@@ -89,16 +90,16 @@ class ElnRuleHandler:
89
90
  if ElnBaseDataType.is_eln_type(data_type):
90
91
  data_type = ElnBaseDataType.get_base_type(data_type).data_type_name
91
92
  # Update the list of records of this type that exist so far globally.
92
- self.__records.setdefault(data_type, set()).add(record)
93
+ self._records.setdefault(data_type, set()).add(record)
93
94
  # Do the same for the list of records of this type for this specific entry.
94
95
  entry_dict.setdefault(data_type, set()).add(record)
95
96
  # Update the records for this entry.
96
- self.__entry_to_records.update({entry: entry_dict})
97
+ self._entry_to_records.update({entry: entry_dict})
97
98
 
98
- self.__field_maps = {}
99
- self.__entry_to_field_maps = {}
99
+ self._field_maps = {}
100
+ self._entry_to_field_maps = {}
100
101
  # Repeat the same thing for the field map results.
101
- for entry, entry_results in self.__context.velox_eln_rule_field_map_result_map.items():
102
+ for entry, entry_results in self._context.velox_eln_rule_field_map_result_map.items():
102
103
  entry_dict: dict[str, dict[int, FieldMap]] = {}
103
104
  for record_result in entry_results:
104
105
  for result in record_result.velox_type_rule_field_map_result_list:
@@ -107,37 +108,41 @@ class ElnRuleHandler:
107
108
  data_type = ElnBaseDataType.get_base_type(data_type).data_type_name
108
109
  for field_map in result.field_map_list:
109
110
  rec_id: int = field_map.get("RecordId")
110
- self.__field_maps.setdefault(data_type, {}).update({rec_id: field_map})
111
+ self._field_maps.setdefault(data_type, {}).update({rec_id: field_map})
111
112
  entry_dict.setdefault(data_type, {}).update({rec_id: field_map})
112
- self.__entry_to_field_maps.update({entry: entry_dict})
113
+ self._entry_to_field_maps.update({entry: entry_dict})
113
114
 
114
115
  def get_entry_names(self) -> list[str]:
115
116
  """
116
117
  :return: A list of the entry names that may be used with the get_records and get_models functions. These are the
117
118
  entries from the experiment that the records in the rule context originate from.
118
119
  """
119
- return list(self.__entry_to_records.keys())
120
+ return list(self._entry_to_records.keys())
120
121
 
121
122
  def get_field_maps_entry_names(self) -> list[str]:
122
123
  """
123
124
  :return: A list of the entry names that may be used with the get_field_maps function. These are the
124
125
  entries from the experiment that the field maps in the rule context originate from.
125
126
  """
126
- return list(self.__entry_to_field_maps.keys())
127
+ return list(self._entry_to_field_maps.keys())
127
128
 
129
+ # CR-47529: Add info about HVDT behavior to the docstring of these functions.
128
130
  def get_records(self, data_type: DataTypeIdentifier, entry: str | None = None) -> list[DataRecord]:
129
131
  """
130
132
  Get records from the cached context with the given data type. Capable of being filtered to searching within
131
133
  the context of an entry name. If the given data type or entry does not exist in the context,
132
134
  returns an empty list.
133
135
 
136
+ Note that if you are attempting to retrieve record that are high volume data types and are receiving nothing,
137
+ the HVDTs may have been sent as field maps. Consider using the get_field_maps function if this occurs.
138
+
134
139
  :param data_type: The data type of the records to return.
135
140
  :param entry: The name of the entry to grab the records from. If None, returns the records that match the data
136
141
  type from every entry. If an entry is provided, but it does not exist in the context, returns an empty list.
137
142
  :return: The records from the context that match the input parameters.
138
143
  """
139
144
  data_type: str = AliasUtil.to_data_type_name(data_type)
140
- records: dict[str, set[DataRecord]] = self.__entry_to_records.get(entry, {}) if entry else self.__records
145
+ records: dict[str, set[DataRecord]] = self._entry_to_records.get(entry, {}) if entry else self._records
141
146
  return list(records.get(data_type, []))
142
147
 
143
148
  # FR-46701: Add functions to the rule handlers for accessing the field maps of inaccessible records in the context.
@@ -149,7 +154,7 @@ class ElnRuleHandler:
149
154
 
150
155
  Field maps will only exist in the context if the data record that the fields are from is no longer accessible
151
156
  to the user. This can occur because the data record was deleted, or because the user does not have access to the
152
- record due to ACL.
157
+ record due to ACL. This can also occur under certain circumstances if the records are HVDTs.
153
158
 
154
159
  :param data_type: The data type of the field maps to return.
155
160
  :param entry: The name of the entry to grab the field maps from. If None, returns the field maps that match the
@@ -158,19 +163,25 @@ class ElnRuleHandler:
158
163
  :return: The field maps from the context that match the input parameters.
159
164
  """
160
165
  data_type: str = AliasUtil.to_data_type_name(data_type)
161
- field_maps: dict[str, dict[int, FieldMap]] = self.__entry_to_field_maps.get(entry, {}) if entry else self.__field_maps
166
+ field_maps: dict[str, dict[int, FieldMap]] = self._entry_to_field_maps.get(entry, {}) if entry else self._field_maps
162
167
  return list(field_maps.get(data_type, {}).values())
163
168
 
164
- def get_models(self, wrapper_type: type[WrappedType], entry: str | None = None) -> list[WrappedType]:
169
+ # CR-47491: Support providing a data type name string to receive PyRecordModels instead of requiring a WrapperType.
170
+ def get_models(self, wrapper_type: type[WrappedType] | str, entry: str | None = None) \
171
+ -> list[WrappedType] | list[PyRecordModel]:
165
172
  """
166
173
  Get record models from the cached context with the given data type. Capable of being filtered to searching
167
174
  within the context of an entry name. If the given data type or entry does not exist in the context,
168
175
  returns an empty list.
169
176
 
170
- :param wrapper_type: The record model wrapper to use.
177
+ Note that if you are attempting to retrieve record that are high volume data types and are receiving nothing,
178
+ the HVDTs may have been sent as field maps. Consider using the get_field_maps function if this occurs.
179
+
180
+ :param wrapper_type: The record model wrapper or data type name of the record to get from the context.
171
181
  :param entry: The name of the entry to grab the records from. If None, returns the records that match the data
172
182
  type from every entry. If an entry is provided, but it does not exist in the context, returns an empty list.
173
- :return: The record models from the context that match the input parameters.
183
+ :return: The record models from the context that match the input parameters. If a data type name was used
184
+ instead of a model wrapper, then the returned records will be PyRecordModels instead of WrappedRecordModels.
174
185
  """
175
- dt: str = wrapper_type.get_wrapper_data_type_name()
176
- return self.__inst_man.add_existing_records_of_type(self.get_records(dt, entry), wrapper_type)
186
+ dt: str = AliasUtil.to_data_type_name(wrapper_type)
187
+ return self._rec_handler.wrap_models(self.get_records(dt, entry), wrapper_type)
@@ -6,11 +6,12 @@ from sapiopylib.rest.User import SapioUser
6
6
  from sapiopylib.rest.pojo.DataRecord import DataRecord
7
7
  from sapiopylib.rest.pojo.eln.SapioELNEnums import ElnBaseDataType
8
8
  from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
9
- from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelManager, RecordModelInstanceManager
9
+ from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
10
10
  from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
11
11
 
12
12
  from sapiopycommons.general.aliases import FieldMap, DataTypeIdentifier, AliasUtil
13
13
  from sapiopycommons.general.exceptions import SapioException
14
+ from sapiopycommons.recordmodel.record_handler import RecordHandler
14
15
 
15
16
 
16
17
  # FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
@@ -18,23 +19,23 @@ class OnSaveRuleHandler:
18
19
  """
19
20
  A class which helps with the parsing and navigation of the on save rule result map of a webhook context.
20
21
  """
21
- __context: SapioWebhookContext
22
+ _context: SapioWebhookContext
22
23
  """The context that this handler is working from."""
23
24
 
24
- __inst_man: RecordModelInstanceManager
25
- """The record model instance manager, used for wrapping the data records as record models."""
25
+ _rec_handler: RecordHandler
26
+ """The record handler, used for wrapping the data records as record models."""
26
27
 
27
28
  # Reformatted and cached version of the Velox on save rule result map for easier handling.
28
- __records: dict[str, set[DataRecord]]
29
+ _records: dict[str, set[DataRecord]]
29
30
  """A mapping of data type to the set of data records from the context that match that data type."""
30
- __base_id_to_records: dict[int, dict[str, set[DataRecord]]]
31
+ _base_id_to_records: dict[int, dict[str, set[DataRecord]]]
31
32
  """A mapping of record IDs of records in the context.data_record_list to the sets of data records related to that
32
33
  record, each set of records being mapped by its data type."""
33
- __field_maps: dict[str, dict[int, FieldMap]]
34
+ _field_maps: dict[str, dict[int, FieldMap]]
34
35
  """A mapping of data type to the field maps from the context that match that data type. In order to prevent
35
36
  duplicate field maps, each field map is in a dict keyed by the RecordId field in the field map, since field maps
36
37
  are just dictionaries and dictionaries aren't hashable and therefore can't go in a set."""
37
- __base_id_to_field_maps: dict[int, dict[str, dict[int, FieldMap]]]
38
+ _base_id_to_field_maps: dict[int, dict[str, dict[int, FieldMap]]]
38
39
  """A mapping of record IDs of records in the context.data_record_list to the field maps related to that
39
40
  record, each grouping of field maps being mapped by its data type."""
40
41
 
@@ -59,8 +60,8 @@ class OnSaveRuleHandler:
59
60
 
60
61
  if context.velox_on_save_result_map is None:
61
62
  raise SapioException("No Velox on save rule result map in context for OnSaveRuleHandler to parse.")
62
- self.__context = context
63
- self.__inst_man = RecordModelManager(context.user).instance_manager
63
+ self._context = context
64
+ self._rec_handler = RecordHandler(context)
64
65
  self.__cache_records()
65
66
 
66
67
  def __cache_records(self) -> None:
@@ -69,10 +70,10 @@ class OnSaveRuleHandler:
69
70
  each record to a set of all records of that data type. The other cache maps the record ID that the records relate
70
71
  to with another dict that maps the data types to the records of that type. Doesn't cache any relationship info.
71
72
  """
72
- self.__records = {}
73
- self.__base_id_to_records = {}
73
+ self._records = {}
74
+ self._base_id_to_records = {}
74
75
  # Each record ID in the context has a list of results for that record.
75
- for record_id, rule_results in self.__context.velox_on_save_result_map.items():
76
+ for record_id, rule_results in self._context.velox_on_save_result_map.items():
76
77
  # Keep track of the records for this specific record ID.
77
78
  id_dict: dict[str, set[DataRecord]] = {}
78
79
  # The list of results for a record consist of a list of data records and a VeloxType that specifies
@@ -86,16 +87,16 @@ class OnSaveRuleHandler:
86
87
  if ElnBaseDataType.is_eln_type(data_type):
87
88
  data_type = ElnBaseDataType.get_base_type(data_type).data_type_name
88
89
  # Update the list of records of this type that exist so far globally.
89
- self.__records.setdefault(data_type, set()).add(record)
90
+ self._records.setdefault(data_type, set()).add(record)
90
91
  # Do the same for the list of records of this type that relate to this record ID.
91
92
  id_dict.setdefault(data_type, set()).add(record)
92
93
  # Update the related records for this record ID.
93
- self.__base_id_to_records.update({record_id: id_dict})
94
+ self._base_id_to_records.update({record_id: id_dict})
94
95
 
95
- self.__field_maps = {}
96
- self.__base_id_to_field_maps = {}
96
+ self._field_maps = {}
97
+ self._base_id_to_field_maps = {}
97
98
  # Repeat the same thing for the field map results.
98
- for record_id, rule_results in self.__context.velox_on_save_field_map_result_map.items():
99
+ for record_id, rule_results in self._context.velox_on_save_field_map_result_map.items():
99
100
  id_dict: dict[str, dict[int, FieldMap]] = {}
100
101
  for record_result in rule_results:
101
102
  data_type: str = record_result.velox_type_pojo.data_type_name
@@ -103,37 +104,41 @@ class OnSaveRuleHandler:
103
104
  data_type = ElnBaseDataType.get_base_type(data_type).data_type_name
104
105
  for field_map in record_result.field_map_list:
105
106
  rec_id: int = field_map.get("RecordId")
106
- self.__field_maps.setdefault(data_type, {}).update({rec_id: field_map})
107
+ self._field_maps.setdefault(data_type, {}).update({rec_id: field_map})
107
108
  id_dict.setdefault(data_type, {}).update({rec_id: field_map})
108
- self.__base_id_to_field_maps.update({record_id: id_dict})
109
+ self._base_id_to_field_maps.update({record_id: id_dict})
109
110
 
110
111
  def get_base_record_ids(self) -> list[int]:
111
112
  """
112
113
  :return: A list of the record IDs that may be used with the get_records and get_models functions. These are the
113
114
  record IDs to the records that caused the rule to trigger.
114
115
  """
115
- return list(self.__base_id_to_records.keys())
116
+ return list(self._base_id_to_records.keys())
116
117
 
117
118
  def get_field_maps_base_record_ids(self) -> list[int]:
118
119
  """
119
120
  :return: A list of the record IDs that may be used with the get_field_maps function. These are the
120
121
  record IDs to the records that caused the rule to trigger.
121
122
  """
122
- return list(self.__base_id_to_field_maps.keys())
123
+ return list(self._base_id_to_field_maps.keys())
123
124
 
125
+ # CR-47529: Add info about HVDT behavior to the docstring of these functions.
124
126
  def get_records(self, data_type: DataTypeIdentifier, record_id: int | None = None) -> list[DataRecord]:
125
127
  """
126
128
  Get records from the cached context with the given data type. Capable of being filtered to searching within
127
129
  the context of a record ID. If the given data type or record ID does not exist in the context,
128
130
  returns an empty list.
129
131
 
132
+ Note that if you are attempting to retrieve record that are high volume data types and are receiving nothing,
133
+ the HVDTs may have been sent as field maps. Consider using the get_field_maps function if this occurs.
134
+
130
135
  :param data_type: The data type of the records to return.
131
136
  :param record_id: The record ID of the base record to search from. If None, returns the records that match the
132
137
  data type from every ID. If an ID is provided, but it does not exist in the context, returns an empty list.
133
138
  :return: The records from the context that match the input parameters.
134
139
  """
135
140
  data_type: str = AliasUtil.to_data_type_name(data_type)
136
- records: dict[str, set[DataRecord]] = self.__base_id_to_records.get(record_id, {}) if record_id else self.__records
141
+ records: dict[str, set[DataRecord]] = self._base_id_to_records.get(record_id, {}) if record_id else self._records
137
142
  return list(records.get(data_type, []))
138
143
 
139
144
  # FR-46701: Add functions to the rule handlers for accessing the field maps of inaccessible records in the context.
@@ -145,7 +150,7 @@ class OnSaveRuleHandler:
145
150
 
146
151
  Field maps will only exist in the context if the data record that the fields are from is no longer accessible
147
152
  to the user. This can occur because the data record was deleted, or because the user does not have access to the
148
- record due to ACL.
153
+ record due to ACL. This can also occur under certain circumstances if the records are HVDTs.
149
154
 
150
155
  :param data_type: The data type of the field maps to return.
151
156
  :param record_id: The record ID of the base record to search from. If None, returns the field maps that match
@@ -154,19 +159,25 @@ class OnSaveRuleHandler:
154
159
  :return: The field maps from the context that match the input parameters.
155
160
  """
156
161
  data_type: str = AliasUtil.to_data_type_name(data_type)
157
- field_maps: dict[str, dict[int, FieldMap]] = self.__base_id_to_field_maps.get(record_id, {}) if record_id else self.__field_maps
162
+ field_maps: dict[str, dict[int, FieldMap]] = self._base_id_to_field_maps.get(record_id, {}) if record_id else self._field_maps
158
163
  return list(field_maps.get(data_type, {}).values())
159
164
 
160
- def get_models(self, wrapper_type: type[WrappedType], record_id: int | None = None) -> list[WrappedType]:
165
+ # CR-47491: Support providing a data type name string to receive PyRecordModels instead of requiring a WrapperType.
166
+ def get_models(self, wrapper_type: type[WrappedType] | str, record_id: int | None = None) \
167
+ -> list[WrappedType] | list[PyRecordModel]:
161
168
  """
162
169
  Get records from the cached context with the given data type. Capable of being filtered to searching within
163
170
  the context of a record ID. If the given data type or record ID does not exist in the context,
164
171
  returns an empty list.
165
172
 
166
- :param wrapper_type: The record model wrapper to use.
173
+ Note that if you are attempting to retrieve record that are high volume data types and are receiving nothing,
174
+ the HVDTs may have been sent as field maps. Consider using the get_field_maps function if this occurs.
175
+
176
+ :param wrapper_type: The record model wrapper or data type name of the record to get from the context.
167
177
  :param record_id: The record ID of the base record to search from. If None, returns the records that match the
168
178
  data type from ID. If an ID is provided, but it does not exist in the context, returns an empty list.
169
- :return: The record models from the context that match the input parameters.
179
+ :return: The record models from the context that match the input parameters. If a data type name was used
180
+ instead of a model wrapper, then the returned records will be PyRecordModels instead of WrappedRecordModels.
170
181
  """
171
- dt: str = wrapper_type.get_wrapper_data_type_name()
172
- return self.__inst_man.add_existing_records_of_type(self.get_records(dt, record_id), wrapper_type)
182
+ dt: str = AliasUtil.to_data_type_name(wrapper_type)
183
+ return self._rec_handler.wrap_models(self.get_records(dt, record_id), wrapper_type)
@@ -0,0 +1,48 @@
1
+ from sapiopycommons.general.aliases import SapioRecord
2
+ from sapiopylib.rest.User import SapioUser
3
+ from sapiopylib.rest.utils.MultiMap import SetMultimap
4
+
5
+ # FR-47421 Added module
6
+
7
+ def create_aliquot_for_samples(parent_sample_to_num_aliquots_map: dict[SapioRecord, int], user: SapioUser) -> SetMultimap[SapioRecord, int]:
8
+ """"
9
+ Ask server to create aliquot records for provided sample parent records.
10
+ :param parent_sample_to_num_aliquots_map: The dictionary containing (parent sample record) -> (number of aliquots to create) mapping.
11
+ :return: The dictionary containing (parent sample record) -> (list of new aliquot record ids) mapping.
12
+ """
13
+ # throw error if at least one record id is blank
14
+ has_negative_record_ids = any([record.record_id < 0 for record in parent_sample_to_num_aliquots_map.keys()])
15
+ if has_negative_record_ids:
16
+ raise ValueError("At least one record requested for aliquot has a negative record ID. "
17
+ "You should have stored record model changes first.")
18
+ has_blank_record_ids = any([record.record_id is None for record in parent_sample_to_num_aliquots_map.keys()])
19
+ if has_blank_record_ids:
20
+ raise ValueError("At least one record requested for aliquot does not currently have a record ID.")
21
+ record_id_to_sapio_record_map = {record.record_id: record for record in parent_sample_to_num_aliquots_map.keys()}
22
+ parent_record_id_to_num_aliquots_map = {record.record_id: num_aliquots for record, num_aliquots in parent_sample_to_num_aliquots_map.items()}
23
+ aliquot_result: SetMultimap[int, int] = create_aliquot_for_samples_record_ids(parent_record_id_to_num_aliquots_map, user)
24
+ ret: SetMultimap[SapioRecord, int] = SetMultimap()
25
+ for parent_record_id in aliquot_result.keys():
26
+ parent_record = record_id_to_sapio_record_map[parent_record_id]
27
+ for aliquot_record_id in aliquot_result.get(parent_record_id):
28
+ ret.put(parent_record, aliquot_record_id)
29
+ return ret
30
+
31
+
32
+ def create_aliquot_for_samples_record_ids(parent_record_id_to_num_aliquots_map: dict[int, int], user: SapioUser) -> SetMultimap[int, int]:
33
+ """
34
+ Ask the server to create aliquot records for the provided sample record IDs.
35
+ :param sample_record_id_list: The dictionary containing (parent sample record id) -> (number of aliquots to create) mapping.
36
+ :return: The dictionary containing (parent sample record id) -> (list of new aliquot record ids) mapping.
37
+ """
38
+ if not parent_record_id_to_num_aliquots_map:
39
+ return SetMultimap()
40
+ endpoint_path = 'sample/aliquot'
41
+ response = user.plugin_put(endpoint_path, payload=parent_record_id_to_num_aliquots_map)
42
+ user.raise_for_status(response)
43
+ response_map: dict[int, list[int]] = response.json()
44
+ ret: SetMultimap[int, int] = SetMultimap()
45
+ for parent_record_id, aliquot_record_ids in response_map.items():
46
+ for aliquot_record_id in aliquot_record_ids:
47
+ ret.put(int(parent_record_id), int(aliquot_record_id))
48
+ return ret