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

@@ -70,56 +70,70 @@ class RecordHandler:
70
70
  self.rel_man = self.rec_man.relationship_manager
71
71
  self.an_man = RecordModelAncestorManager(self.rec_man)
72
72
 
73
- def wrap_model(self, record: DataRecord, wrapper_type: type[WrappedType]) -> WrappedType:
73
+ # CR-47491: Support not providing a wrapper type to receive PyRecordModels instead of WrappedRecordModels.
74
+ def wrap_model(self, record: DataRecord | PyRecordModel, wrapper_type: type[WrappedType] | None = None) \
75
+ -> WrappedType | PyRecordModel:
74
76
  """
75
- Shorthand for adding a single data record as a record model.
77
+ Shorthand for adding a single data record or PyRecordModel as a WrappedRecordModel.
76
78
 
77
- :param record: The data record to wrap.
78
- :param wrapper_type: The record model wrapper to use.
79
+ :param record: The data record or PyRecordModel to wrap.
80
+ :param wrapper_type: The record model wrapper to use. If not provided, the record is returned as a
81
+ PyRecordModel instead of a WrappedRecordModel.
79
82
  :return: The record model for the input.
80
83
  """
81
- self.__verify_data_type([record], wrapper_type)
82
- return self.inst_man.add_existing_record_of_type(record, wrapper_type)
84
+ if wrapper_type is not None:
85
+ self.__verify_data_type(record, wrapper_type)
86
+ if isinstance(record, PyRecordModel):
87
+ return self.inst_man.wrap(record, wrapper_type)
88
+ return self.inst_man.add_existing_record_of_type(record, wrapper_type)
89
+ if isinstance(record, PyRecordModel):
90
+ return record
91
+ return self.inst_man.add_existing_record(record)
83
92
 
84
- def wrap_models(self, records: Iterable[DataRecord], wrapper_type: type[WrappedType]) -> list[WrappedType]:
93
+ def wrap_models(self, records: Iterable[DataRecord | PyRecordModel],
94
+ wrapper_type: type[WrappedType] | None = None) \
95
+ -> list[WrappedType] | list[PyRecordModel]:
85
96
  """
86
- Shorthand for adding a list of data records as record models.
97
+ Shorthand for adding a list of data records or PyRecordModels as a WrappedRecordModels.
87
98
 
88
99
  :param records: The data records to wrap.
89
- :param wrapper_type: The record model wrapper to use.
100
+ :param wrapper_type: The record model wrapper to use. If not provided, the records are returned as
101
+ PyRecordModels instead of WrappedRecordModels.
90
102
  :return: The record models for the input.
91
103
  """
92
- self.__verify_data_type(records, wrapper_type)
93
- return self.inst_man.add_existing_records_of_type(list(records), wrapper_type)
104
+ return [self.wrap_model(x, wrapper_type) for x in records]
94
105
 
95
- def query_models(self, wrapper_type: type[WrappedType], field: FieldIdentifier, value_list: Iterable[FieldValue],
96
- page_limit: int | None = None, page_size: int | None = None) -> list[WrappedType]:
106
+ # CR-47491: Support providing a data type name string to receive PyRecordModels instead of requiring a WrapperType.
107
+ def query_models(self, wrapper_type: type[WrappedType] | str, field: FieldIdentifier,
108
+ value_list: Iterable[FieldValue], page_limit: int | None = None, page_size: int | None = None) \
109
+ -> list[WrappedType] | list[PyRecordModel]:
97
110
  """
98
111
  Shorthand for using the data record manager to query for a list of data records by field value
99
112
  and then converting the results into a list of record models.
100
113
 
101
- :param wrapper_type: The record model wrapper to use.
114
+ :param wrapper_type: The record model wrapper to use, or the data type name of the records.
102
115
  :param field: The field to query on.
103
116
  :param value_list: The values of the field to query on.
104
117
  :param page_limit: The maximum number of pages to query. If None, exhausts all possible pages. This parameter
105
118
  only functions if you set a page size or the platform enforces a page size.
106
119
  :param page_size: The size of the pages to query. If None, the page size may be limited by the platform.
107
- :return: The record models for the queried records.
120
+ :return: The record models for the queried records. If a data type name was used instead of a model wrapper,
121
+ then the returned records will be PyRecordModels instead of WrappedRecordModels.
108
122
  """
109
123
  criteria: DataRecordPojoPageCriteria | None = None
110
124
  if page_size is not None:
111
125
  criteria = DataRecordPojoPageCriteria(page_size=page_size)
112
126
  return self.query_models_with_criteria(wrapper_type, field, value_list, criteria, page_limit)[0]
113
127
 
114
- def query_and_map_models(self, wrapper_type: type[WrappedType], field: FieldIdentifier,
128
+ def query_and_map_models(self, wrapper_type: type[WrappedType] | str, field: FieldIdentifier,
115
129
  value_list: Iterable[FieldValue], page_limit: int | None = None,
116
130
  page_size: int | None = None, *, mapping_field: FieldIdentifier | None = None) \
117
- -> dict[FieldValue, list[WrappedType]]:
131
+ -> dict[FieldValue, list[WrappedType] | list[PyRecordModel]]:
118
132
  """
119
133
  Shorthand for using query_models to search for records given values on a specific field and then using
120
134
  map_by_field to turn the returned list into a dictionary mapping field values to records.
121
135
 
122
- :param wrapper_type: The record model wrapper to use.
136
+ :param wrapper_type: The record model wrapper to use, or the data type name of the records.
123
137
  :param field: The field to query and map on.
124
138
  :param value_list: The values of the field to query on.
125
139
  :param page_limit: The maximum number of pages to query. If None, exhausts all possible pages. This parameter
@@ -127,22 +141,24 @@ class RecordHandler:
127
141
  :param page_size: The size of the pages to query. If None, the page size may be limited by the platform.
128
142
  :param mapping_field: If provided, use this field to map against instead of the field that was queried on.
129
143
  :return: The record models for the queried records mapped by field values to the records with that value.
144
+ If a data type name was used instead of a model wrapper, then the returned records will be PyRecordModels
145
+ instead of WrappedRecordModels.
130
146
  """
131
147
  if mapping_field is None:
132
148
  mapping_field = field
133
149
  return self.map_by_field(self.query_models(wrapper_type, field, value_list, page_limit, page_size),
134
150
  mapping_field)
135
151
 
136
- def query_and_unique_map_models(self, wrapper_type: type[WrappedType], field: FieldIdentifier,
152
+ def query_and_unique_map_models(self, wrapper_type: type[WrappedType] | str, field: FieldIdentifier,
137
153
  value_list: Iterable[FieldValue], page_limit: int | None = None,
138
154
  page_size: int | None = None, *, mapping_field: FieldIdentifier | None = None) \
139
- -> dict[FieldValue, WrappedType]:
155
+ -> dict[FieldValue, WrappedType | PyRecordModel]:
140
156
  """
141
157
  Shorthand for using query_models to search for records given values on a specific field and then using
142
158
  map_by_unique_field to turn the returned list into a dictionary mapping field values to records.
143
159
  If any two records share the same field value, throws an exception.
144
160
 
145
- :param wrapper_type: The record model wrapper to use.
161
+ :param wrapper_type: The record model wrapper to use, or the data type name of the records.
146
162
  :param field: The field to query and map on.
147
163
  :param value_list: The values of the field to query on.
148
164
  :param page_limit: The maximum number of pages to query. If None, exhausts all possible pages. This parameter
@@ -150,134 +166,150 @@ class RecordHandler:
150
166
  :param page_size: The size of the pages to query. If None, the page size may be limited by the platform.
151
167
  :param mapping_field: If provided, use this field to map against instead of the field that was queried on.
152
168
  :return: The record models for the queried records mapped by field values to the record with that value.
169
+ If a data type name was used instead of a model wrapper, then the returned records will be PyRecordModels
170
+ instead of WrappedRecordModels.
153
171
  """
154
172
  if mapping_field is None:
155
173
  mapping_field = field
156
174
  return self.map_by_unique_field(self.query_models(wrapper_type, field, value_list, page_limit, page_size),
157
175
  mapping_field)
158
176
 
159
- def query_models_with_criteria(self, wrapper_type: type[WrappedType], field: FieldIdentifier,
177
+ def query_models_with_criteria(self, wrapper_type: type[WrappedType] | str, field: FieldIdentifier,
160
178
  value_list: Iterable[FieldValue],
161
179
  paging_criteria: DataRecordPojoPageCriteria | None = None,
162
180
  page_limit: int | None = None) \
163
- -> tuple[list[WrappedType], DataRecordPojoPageCriteria]:
181
+ -> tuple[list[WrappedType] | list[PyRecordModel], DataRecordPojoPageCriteria]:
164
182
  """
165
183
  Shorthand for using the data record manager to query for a list of data records by field value
166
184
  and then converting the results into a list of record models.
167
185
 
168
- :param wrapper_type: The record model wrapper to use.
186
+ :param wrapper_type: The record model wrapper to use, or the data type name of the records.
169
187
  :param field: The field to query on.
170
188
  :param value_list: The values of the field to query on.
171
189
  :param paging_criteria: The paging criteria to start the query with.
172
190
  :param page_limit: The maximum number of pages to query from the starting criteria. If None, exhausts all
173
191
  possible pages. This parameter only functions if you set a page size in the paging criteria or the platform
174
192
  enforces a page size.
175
- :return: The record models for the queried records and the final paging criteria.
193
+ :return: The record models for the queried records and the final paging criteria. If a data type name was used
194
+ instead of a model wrapper, then the returned records will be PyRecordModels instead of WrappedRecordModels.
176
195
  """
177
- dt: str = wrapper_type.get_wrapper_data_type_name()
196
+ dt: str = AliasUtil.to_data_type_name(wrapper_type)
197
+ if isinstance(wrapper_type, str):
198
+ wrapper_type = None
178
199
  field: str = AliasUtil.to_data_field_name(field)
179
200
  pager = QueryDataRecordsAutoPager(dt, field, list(value_list), self.user, paging_criteria)
180
201
  pager.max_page = page_limit
181
202
  return self.wrap_models(pager.get_all_at_once(), wrapper_type), pager.next_page_criteria
182
203
 
183
- def query_models_by_id(self, wrapper_type: type[WrappedType], ids: Iterable[int],
184
- page_limit: int | None = None, page_size: int | None = None) -> list[WrappedType]:
204
+ def query_models_by_id(self, wrapper_type: type[WrappedType] | str, ids: Iterable[int],
205
+ page_limit: int | None = None, page_size: int | None = None) \
206
+ -> list[WrappedType] | list[PyRecordModel]:
185
207
  """
186
208
  Shorthand for using the data record manager to query for a list of data records by record ID
187
209
  and then converting the results into a list of record models.
188
210
 
189
- :param wrapper_type: The record model wrapper to use.
211
+ :param wrapper_type: The record model wrapper to use, or the data type name of the records.
190
212
  :param ids: The list of record IDs to query.
191
213
  :param page_limit: The maximum number of pages to query. If None, exhausts all possible pages. This parameter
192
214
  only functions if you set a page size or the platform enforces a page size.
193
215
  :param page_size: The size of the pages to query. If None, the page size may be limited by the platform.
194
- :return: The record models for the queried records.
216
+ :return: The record models for the queried records. If a data type name was used instead of a model wrapper,
217
+ then the returned records will be PyRecordModels instead of WrappedRecordModels.
195
218
  """
196
219
  criteria: DataRecordPojoPageCriteria | None = None
197
220
  if page_size is not None:
198
221
  criteria = DataRecordPojoPageCriteria(page_size=page_size)
199
222
  return self.query_models_by_id_with_criteria(wrapper_type, ids, criteria, page_limit)[0]
200
223
 
201
- def query_models_by_id_with_criteria(self, wrapper_type: type[WrappedType], ids: Iterable[int],
224
+ def query_models_by_id_with_criteria(self, wrapper_type: type[WrappedType] | str, ids: Iterable[int],
202
225
  paging_criteria: DataRecordPojoPageCriteria | None = None,
203
226
  page_limit: int | None = None) \
204
- -> tuple[list[WrappedType], DataRecordPojoPageCriteria]:
227
+ -> tuple[list[WrappedType] | list[PyRecordModel], DataRecordPojoPageCriteria]:
205
228
  """
206
229
  Shorthand for using the data record manager to query for a list of data records by record ID
207
230
  and then converting the results into a list of record models.
208
231
 
209
- :param wrapper_type: The record model wrapper to use.
232
+ :param wrapper_type: The record model wrapper to use, or the data type name of the records.
210
233
  :param ids: The list of record IDs to query.
211
234
  :param paging_criteria: The paging criteria to start the query with.
212
235
  :param page_limit: The maximum number of pages to query from the starting criteria. If None, exhausts all
213
236
  possible pages. This parameter only functions if you set a page size in the paging criteria or the platform
214
237
  enforces a page size.
215
- :return: The record models for the queried records and the final paging criteria.
238
+ :return: The record models for the queried records and the final paging criteria. If a data type name was used
239
+ instead of a model wrapper, then the returned records will be PyRecordModels instead of WrappedRecordModels.
216
240
  """
217
- dt: str = wrapper_type.get_wrapper_data_type_name()
241
+ dt: str = AliasUtil.to_data_type_name(wrapper_type)
242
+ if isinstance(wrapper_type, str):
243
+ wrapper_type = None
218
244
  pager = QueryDataRecordByIdListAutoPager(dt, list(ids), self.user, paging_criteria)
219
245
  pager.max_page = page_limit
220
246
  return self.wrap_models(pager.get_all_at_once(), wrapper_type), pager.next_page_criteria
221
247
 
222
- def query_models_by_id_and_map(self, wrapper_type: type[WrappedType], ids: Iterable[int],
248
+ def query_models_by_id_and_map(self, wrapper_type: type[WrappedType] | str, ids: Iterable[int],
223
249
  page_limit: int | None = None, page_size: int | None = None) \
224
- -> dict[int, WrappedType]:
250
+ -> dict[int, WrappedType | PyRecordModel]:
225
251
  """
226
252
  Shorthand for using the data record manager to query for a list of data records by record ID
227
253
  and then converting the results into a dictionary of record ID to the record model for that ID.
228
254
 
229
- :param wrapper_type: The record model wrapper to use.
255
+ :param wrapper_type: The record model wrapper to use, or the data type name of the records.
230
256
  :param ids: The list of record IDs to query.
231
257
  :param page_limit: The maximum number of pages to query. If None, exhausts all possible pages. This parameter
232
258
  only functions if you set a page size or the platform enforces a page size.
233
259
  :param page_size: The size of the pages to query. If None, the page size may be limited by the platform.
234
260
  :return: The record models for the queried records mapped in a dictionary by their record ID.
261
+ If a data type name was used instead of a model wrapper, then the returned records will be PyRecordModels
262
+ instead of WrappedRecordModels.
235
263
  """
236
264
  return {AliasUtil.to_record_id(x): x for x in self.query_models_by_id(wrapper_type, ids, page_limit, page_size)}
237
265
 
238
- def query_all_models(self, wrapper_type: type[WrappedType], page_limit: int | None = None,
239
- page_size: int | None = None) -> list[WrappedType]:
266
+ def query_all_models(self, wrapper_type: type[WrappedType] | str, page_limit: int | None = None,
267
+ page_size: int | None = None) -> list[WrappedType] | list[PyRecordModel]:
240
268
  """
241
269
  Shorthand for using the data record manager to query for all data records of a given type
242
270
  and then converting the results into a list of record models.
243
271
 
244
- :param wrapper_type: The record model wrapper to use.
272
+ :param wrapper_type: The record model wrapper to use, or the data type name of the records.
245
273
  :param page_limit: The maximum number of pages to query. If None, exhausts all possible pages. This parameter
246
274
  only functions if you set a page size or the platform enforces a page size.
247
275
  :param page_size: The size of the pages to query. If None, the page size may be limited by the platform.
248
- :return: The record models for the queried records.
276
+ :return: The record models for the queried records. If a data type name was used instead of a model wrapper,
277
+ then the returned records will be PyRecordModels instead of WrappedRecordModels.
249
278
  """
250
279
  criteria: DataRecordPojoPageCriteria | None = None
251
280
  if page_size is not None:
252
281
  criteria = DataRecordPojoPageCriteria(page_size=page_size)
253
282
  return self.query_all_models_with_criteria(wrapper_type, criteria, page_limit)[0]
254
283
 
255
- def query_all_models_with_criteria(self, wrapper_type: type[WrappedType],
284
+ def query_all_models_with_criteria(self, wrapper_type: type[WrappedType] | str,
256
285
  paging_criteria: DataRecordPojoPageCriteria | None = None,
257
286
  page_limit: int | None = None) \
258
- -> tuple[list[WrappedType], DataRecordPojoPageCriteria]:
287
+ -> tuple[list[WrappedType] | list[PyRecordModel], DataRecordPojoPageCriteria]:
259
288
  """
260
289
  Shorthand for using the data record manager to query for all data records of a given type
261
290
  and then converting the results into a list of record models.
262
291
 
263
- :param wrapper_type: The record model wrapper to use.
292
+ :param wrapper_type: The record model wrapper to use, or the data type name of the records.
264
293
  :param paging_criteria: The paging criteria to start the query with.
265
294
  :param page_limit: The maximum number of pages to query from the starting criteria. If None, exhausts all
266
295
  possible pages. This parameter only functions if you set a page size in the paging criteria or the platform
267
296
  enforces a page size.
268
- :return: The record models for the queried records and the final paging criteria.
297
+ :return: The record models for the queried records and the final paging criteria. If a data type name was used
298
+ instead of a model wrapper, then the returned records will be PyRecordModels instead of WrappedRecordModels.
269
299
  """
270
- dt: str = wrapper_type.get_wrapper_data_type_name()
300
+ dt: str = AliasUtil.to_data_type_name(wrapper_type)
301
+ if isinstance(wrapper_type, str):
302
+ wrapper_type = None
271
303
  pager = QueryAllRecordsOfTypeAutoPager(dt, self.user, paging_criteria)
272
304
  pager.max_page = page_limit
273
305
  return self.wrap_models(pager.get_all_at_once(), wrapper_type), pager.next_page_criteria
274
306
 
275
- def query_models_by_report(self, wrapper_type: type[WrappedType],
307
+ def query_models_by_report(self, wrapper_type: type[WrappedType] | str,
276
308
  report_name: str | RawReportTerm | CustomReportCriteria,
277
309
  filters: dict[FieldIdentifierKey, Iterable[FieldValue]] | None = None,
278
310
  page_limit: int | None = None,
279
311
  page_size: int | None = None,
280
- page_number: int | None = None) -> list[WrappedType]:
312
+ page_number: int | None = None) -> list[WrappedType] | list[PyRecordModel]:
281
313
  """
282
314
  Run a report and use the results of that report to query for and return the records in the report results.
283
315
  First runs the report, then runs a data record manager query on the results of the custom report.
@@ -287,7 +319,7 @@ class RecordHandler:
287
319
 
288
320
  Any given custom report criteria should only have columns from a single data type.
289
321
 
290
- :param wrapper_type: The record model wrapper to use.
322
+ :param wrapper_type: The record model wrapper to use, or the data type name of the records.
291
323
  :param report_name: The name of a system report, or a raw report term for a quick report, or custom report
292
324
  criteria for a custom report.
293
325
  :param filters: If provided, filter the results of the report using the given mapping of headers to values to
@@ -299,7 +331,8 @@ class RecordHandler:
299
331
  :param page_number: The page number to start the search from, If None, starts on the first page.
300
332
  If the input report is a custom report criteria, uses the value from the criteria, unless this value is
301
333
  not None, in which case it overwrites the given report's value. Note that the number of the first page is 0.
302
- :return: The record models for the queried records that matched the given report.
334
+ :return: The record models for the queried records that matched the given report. If a data type name was used
335
+ instead of a model wrapper, then the returned records will be PyRecordModels instead of WrappedRecordModels.
303
336
  """
304
337
  warnings.warn("Deprecated in favor of the [System/Custom/Quick]ReportRecordAutoPager classes.", DeprecationWarning)
305
338
  if isinstance(report_name, str):
@@ -309,7 +342,7 @@ class RecordHandler:
309
342
  results: list[dict[str, FieldValue]] = CustomReportUtil.run_quick_report(self.user, report_name, filters,
310
343
  page_limit, page_size, page_number)
311
344
  elif isinstance(report_name, CustomReportCriteria):
312
- dt: str = wrapper_type.get_wrapper_data_type_name()
345
+ dt: str = AliasUtil.to_data_type_name(wrapper_type)
313
346
  # Ensure that the root data type is the one we're looking for.
314
347
  report_name.root_data_type = dt
315
348
  # Raise an exception if any column in the report doesn't match the given data type.
@@ -329,35 +362,40 @@ class RecordHandler:
329
362
  ids: list[int] = [row["RecordId"] for row in results]
330
363
  return self.query_models_by_id(wrapper_type, ids)
331
364
 
332
- def add_model(self, wrapper_type: type[WrappedType]) -> WrappedType:
365
+ def add_model(self, wrapper_type: type[WrappedType] | str) -> WrappedType | PyRecordModel:
333
366
  """
334
367
  Shorthand for using the instance manager to add a new record model of the given type.
335
368
 
336
- :param wrapper_type: The record model wrapper to use.
337
- :return: The newly added record model.
369
+ :param wrapper_type: The record model wrapper to use, or the data type name of the record.
370
+ :return: The newly added record model. If a data type name was used instead of a model wrapper, then the
371
+ returned record will be a PyRecordModel instead of a WrappedRecordModel.
338
372
  """
339
- return self.inst_man.add_new_record_of_type(wrapper_type)
373
+ return self.add_models(wrapper_type, 1)[0]
340
374
 
341
- def add_models(self, wrapper_type: type[WrappedType], num: int) -> list[WrappedType]:
375
+ def add_models(self, wrapper_type: type[WrappedType] | str, num: int) -> list[WrappedType] | list[PyRecordModel]:
342
376
  """
343
377
  Shorthand for using the instance manager to add new record models of the given type.
344
378
 
345
- :param wrapper_type: The record model wrapper to use.
379
+ :param wrapper_type: The record model wrapper to use, or the data type name of the records.
346
380
  :param num: The number of models to create.
347
- :return: The newly added record models.
381
+ :return: The newly added record models. If a data type name was used instead of a model wrapper, then the
382
+ returned records will be PyRecordModels instead of WrappedRecordModels.
348
383
  """
384
+ if isinstance(wrapper_type, str):
385
+ return self.inst_man.add_new_records(wrapper_type, num)
349
386
  return self.inst_man.add_new_records_of_type(num, wrapper_type)
350
387
 
351
- def add_models_with_data(self, wrapper_type: type[WrappedType], fields: list[FieldIdentifierMap]) \
352
- -> list[WrappedType]:
388
+ def add_models_with_data(self, wrapper_type: type[WrappedType] | str, fields: list[FieldIdentifierMap]) \
389
+ -> list[WrappedType] | list[PyRecordModel]:
353
390
  """
354
391
  Shorthand for using the instance manager to add new models of the given type, and then initializing all those
355
392
  models with the given fields.
356
393
 
357
- :param wrapper_type: The record model wrapper to use.
394
+ :param wrapper_type: The record model wrapper to use, or the data type name of the records.
358
395
  :param fields: A list of field maps to initialize the record models with.
359
396
  :return: The newly added record models with the provided fields set. The records will be in the same order as
360
- the fields in the fields list.
397
+ the fields in the fields list. If a data type name was used instead of a model wrapper, then the returned
398
+ records will be PyRecordModels instead of WrappedRecordModels.
361
399
  """
362
400
  fields: list[FieldMap] = AliasUtil.to_data_field_names_list_dict(fields)
363
401
  models: list[WrappedType] = self.add_models(wrapper_type, len(fields))
@@ -365,8 +403,9 @@ class RecordHandler:
365
403
  model.set_field_values(field_list)
366
404
  return models
367
405
 
368
- def find_or_add_model(self, wrapper_type: type[WrappedType], primary_identifier: FieldIdentifier,
369
- id_value: FieldValue, secondary_identifiers: FieldIdentifierMap | None = None) -> WrappedType:
406
+ def find_or_add_model(self, wrapper_type: type[WrappedType] | str, primary_identifier: FieldIdentifier,
407
+ id_value: FieldValue, secondary_identifiers: FieldIdentifierMap | None = None) \
408
+ -> WrappedType | PyRecordModel:
370
409
  """
371
410
  Find a unique record that matches the given field values. If no such records exist, add a record model to the
372
411
  cache with the identifying fields set to the desired values. This record will be created in the system when
@@ -377,12 +416,14 @@ class RecordHandler:
377
416
 
378
417
  Makes a webservice call to query for the existing record.
379
418
 
380
- :param wrapper_type: The record model wrapper to use.
419
+ :param wrapper_type: The record model wrapper to use, or the data type name of the record.
381
420
  :param primary_identifier: The data field name of the field to search on.
382
421
  :param id_value: The value of the identifying field to search for.
383
422
  :param secondary_identifiers: Optional fields used to filter the records that are returned after searching on
384
423
  the primary identifier.
385
424
  :return: The record model with the identifying field value, either pulled from the system or newly created.
425
+ If a data type name was used instead of a model wrapper, then the returned record will be a PyRecordModel
426
+ instead of a WrappedRecordModel.
386
427
  """
387
428
  # PR-46335: Initialize the secondary identifiers parameter if None is provided to avoid an exception.
388
429
  # If no secondary identifiers were provided, use an empty dictionary.
@@ -403,22 +444,25 @@ class RecordHandler:
403
444
  secondary_identifiers.update({primary_identifier: id_value})
404
445
  return self.add_models_with_data(wrapper_type, [secondary_identifiers])[0]
405
446
 
406
- def create_models(self, wrapper_type: type[WrappedType], num: int) -> list[WrappedType]:
447
+ def create_models(self, wrapper_type: type[WrappedType] | str, num: int) -> list[WrappedType] | list[PyRecordModel]:
407
448
  """
408
449
  Shorthand for creating new records via the data record manager and then returning them as wrapped
409
450
  record models. Useful in cases where your record model needs to have a valid record ID.
410
451
 
411
452
  Makes a webservice call to create the data records.
412
453
 
413
- :param wrapper_type: The record model wrapper to use.
454
+ :param wrapper_type: The record model wrapper to use, or the data type name of the records.
414
455
  :param num: The number of new records to create.
415
- :return: The newly created record models.
456
+ :return: The newly created record models. If a data type name was used instead of a model wrapper, then the
457
+ returned records will be PyRecordModels instead of WrappedRecordModels.
416
458
  """
417
- dt: str = wrapper_type.get_wrapper_data_type_name()
459
+ dt: str = AliasUtil.to_data_type_name(wrapper_type)
460
+ if isinstance(wrapper_type, str):
461
+ wrapper_type = None
418
462
  return self.wrap_models(self.dr_man.add_data_records(dt, num), wrapper_type)
419
463
 
420
- def create_models_with_data(self, wrapper_type: type[WrappedType], fields: list[FieldIdentifierMap]) \
421
- -> list[WrappedType]:
464
+ def create_models_with_data(self, wrapper_type: type[WrappedType] | str, fields: list[FieldIdentifierMap]) \
465
+ -> list[WrappedType] | list[PyRecordModel]:
422
466
  """
423
467
  Shorthand for creating new records via the data record manager with field data to initialize the records with
424
468
  and then returning them as wrapped record models. Useful in cases where your record model needs to have a valid
@@ -426,17 +470,20 @@ class RecordHandler:
426
470
 
427
471
  Makes a webservice call to create the data records.
428
472
 
429
- :param wrapper_type: The record model wrapper to use.
473
+ :param wrapper_type: The record model wrapper to use, or the data type name of the records.
430
474
  :param fields: The field map list to initialize the new data records with.
431
- :return: The newly created record models.
475
+ :return: The newly created record models. If a data type name was used instead of a model wrapper, then the
476
+ returned records will be PyRecordModels instead of WrappedRecordModels.
432
477
  """
433
- dt: str = wrapper_type.get_wrapper_data_type_name()
478
+ dt: str = AliasUtil.to_data_type_name(wrapper_type)
479
+ if isinstance(wrapper_type, str):
480
+ wrapper_type = None
434
481
  fields: list[FieldMap] = AliasUtil.to_data_field_names_list_dict(fields)
435
482
  return self.wrap_models(self.dr_man.add_data_records_with_data(dt, fields), wrapper_type)
436
483
 
437
- def find_or_create_model(self, wrapper_type: type[WrappedType], primary_identifier: FieldIdentifier,
484
+ def find_or_create_model(self, wrapper_type: type[WrappedType] | str, primary_identifier: FieldIdentifier,
438
485
  id_value: FieldValue, secondary_identifiers: FieldIdentifierMap | None = None) \
439
- -> WrappedType:
486
+ -> WrappedType | PyRecordModel:
440
487
  """
441
488
  Find a unique record that matches the given field values. If no such records exist, create one with the
442
489
  identifying fields set to the desired values. If more than one record with the identifying values exists,
@@ -448,12 +495,14 @@ class RecordHandler:
448
495
  Makes a webservice call to query for the existing record. Makes an additional webservice call if the record
449
496
  needs to be created.
450
497
 
451
- :param wrapper_type: The record model wrapper to use.
498
+ :param wrapper_type: The record model wrapper to use, or the data type name of the record.
452
499
  :param primary_identifier: The data field name of the field to search on.
453
500
  :param id_value: The value of the identifying field to search for.
454
501
  :param secondary_identifiers: Optional fields used to filter the records that are returned after searching on
455
502
  the primary identifier.
456
503
  :return: The record model with the identifying field value, either pulled from the system or newly created.
504
+ If a data type name was used instead of a model wrapper, then the returned record will be a PyRecordModel
505
+ instead of a WrappedRecordModel.
457
506
  """
458
507
  # PR-46335: Initialize the secondary identifiers parameter if None is provided to avoid an exception.
459
508
  # If no secondary identifiers were provided, use an empty dictionary.
@@ -475,7 +524,8 @@ class RecordHandler:
475
524
  return self.create_models_with_data(wrapper_type, [secondary_identifiers])[0]
476
525
 
477
526
  @staticmethod
478
- def map_to_parent(models: Iterable[RecordModel], parent_type: type[WrappedType]) -> dict[RecordModel, WrappedType]:
527
+ def map_to_parent(models: Iterable[WrappedRecordModel], parent_type: type[WrappedType])\
528
+ -> dict[WrappedRecordModel, WrappedType]:
479
529
  """
480
530
  Map a list of record models to a single parent of a given type. The parents must already be loaded.
481
531
 
@@ -484,14 +534,14 @@ class RecordHandler:
484
534
  :return: A dict[ModelType, ParentType]. If an input model doesn't have a parent of the given parent type, then
485
535
  it will map to None.
486
536
  """
487
- return_dict: dict[RecordModel, WrappedType] = {}
537
+ return_dict: dict[WrappedRecordModel, WrappedType] = {}
488
538
  for model in models:
489
539
  return_dict[model] = model.get_parent_of_type(parent_type)
490
540
  return return_dict
491
541
 
492
542
  @staticmethod
493
- def map_to_parents(models: Iterable[RecordModel], parent_type: type[WrappedType]) \
494
- -> dict[RecordModel, list[WrappedType]]:
543
+ def map_to_parents(models: Iterable[WrappedRecordModel], parent_type: type[WrappedType]) \
544
+ -> dict[WrappedRecordModel, list[WrappedType]]:
495
545
  """
496
546
  Map a list of record models to a list parents of a given type. The parents must already be loaded.
497
547
 
@@ -500,14 +550,14 @@ class RecordHandler:
500
550
  :return: A dict[ModelType, list[ParentType]]. If an input model doesn't have a parent of the given parent type,
501
551
  then it will map to an empty list.
502
552
  """
503
- return_dict: dict[RecordModel, list[WrappedType]] = {}
553
+ return_dict: dict[WrappedRecordModel, list[WrappedType]] = {}
504
554
  for model in models:
505
555
  return_dict[model] = model.get_parents_of_type(parent_type)
506
556
  return return_dict
507
557
 
508
558
  @staticmethod
509
- def map_by_parent(models: Iterable[RecordModel], parent_type: type[WrappedType]) \
510
- -> dict[WrappedType, RecordModel]:
559
+ def map_by_parent(models: Iterable[WrappedRecordModel], parent_type: type[WrappedType]) \
560
+ -> dict[WrappedType, WrappedRecordModel]:
511
561
  """
512
562
  Take a list of record models and map them by their parent. Essentially an inversion of map_to_parent.
513
563
  If two records share the same parent, an exception will be thrown. The parents must already be loaded.
@@ -517,8 +567,8 @@ class RecordHandler:
517
567
  :return: A dict[ParentType, ModelType]. If an input model doesn't have a parent of the given parent type,
518
568
  then it will not be in the resulting dictionary.
519
569
  """
520
- to_parent: dict[RecordModel, WrappedType] = RecordHandler.map_to_parent(models, parent_type)
521
- by_parent: dict[WrappedType, RecordModel] = {}
570
+ to_parent: dict[WrappedRecordModel, WrappedType] = RecordHandler.map_to_parent(models, parent_type)
571
+ by_parent: dict[WrappedType, WrappedRecordModel] = {}
522
572
  for record, parent in to_parent.items():
523
573
  if parent is None:
524
574
  continue
@@ -529,8 +579,8 @@ class RecordHandler:
529
579
  return by_parent
530
580
 
531
581
  @staticmethod
532
- def map_by_parents(models: Iterable[RecordModel], parent_type: type[WrappedType]) \
533
- -> dict[WrappedType, list[RecordModel]]:
582
+ def map_by_parents(models: Iterable[WrappedRecordModel], parent_type: type[WrappedType]) \
583
+ -> dict[WrappedType, list[WrappedRecordModel]]:
534
584
  """
535
585
  Take a list of record models and map them by their parents. Essentially an inversion of map_to_parents. Input
536
586
  models that share a parent will end up in the same list. The parents must already be loaded.
@@ -540,15 +590,16 @@ class RecordHandler:
540
590
  :return: A dict[ParentType, list[ModelType]]. If an input model doesn't have a parent of the given parent type,
541
591
  then it will not be in the resulting dictionary.
542
592
  """
543
- to_parents: dict[RecordModel, list[WrappedType]] = RecordHandler.map_to_parents(models, parent_type)
544
- by_parents: dict[WrappedType, list[RecordModel]] = {}
593
+ to_parents: dict[WrappedRecordModel, list[WrappedType]] = RecordHandler.map_to_parents(models, parent_type)
594
+ by_parents: dict[WrappedType, list[WrappedRecordModel]] = {}
545
595
  for record, parents in to_parents.items():
546
596
  for parent in parents:
547
597
  by_parents.setdefault(parent, []).append(record)
548
598
  return by_parents
549
599
 
550
600
  @staticmethod
551
- def map_to_child(models: Iterable[RecordModel], child_type: type[WrappedType]) -> dict[RecordModel, WrappedType]:
601
+ def map_to_child(models: Iterable[WrappedRecordModel], child_type: type[WrappedType])\
602
+ -> dict[WrappedRecordModel, WrappedType]:
552
603
  """
553
604
  Map a list of record models to a single child of a given type. The children must already be loaded.
554
605
 
@@ -557,14 +608,14 @@ class RecordHandler:
557
608
  :return: A dict[ModelType, ChildType]. If an input model doesn't have a child of the given child type, then
558
609
  it will map to None.
559
610
  """
560
- return_dict: dict[RecordModel, WrappedType] = {}
611
+ return_dict: dict[WrappedRecordModel, WrappedType] = {}
561
612
  for model in models:
562
613
  return_dict[model] = model.get_child_of_type(child_type)
563
614
  return return_dict
564
615
 
565
616
  @staticmethod
566
- def map_to_children(models: Iterable[RecordModel], child_type: type[WrappedType]) \
567
- -> dict[RecordModel, list[WrappedType]]:
617
+ def map_to_children(models: Iterable[WrappedRecordModel], child_type: type[WrappedType]) \
618
+ -> dict[WrappedRecordModel, list[WrappedType]]:
568
619
  """
569
620
  Map a list of record models to a list children of a given type. The children must already be loaded.
570
621
 
@@ -573,14 +624,14 @@ class RecordHandler:
573
624
  :return: A dict[ModelType, list[ChildType]]. If an input model doesn't have children of the given child type,
574
625
  then it will map to an empty list.
575
626
  """
576
- return_dict: dict[RecordModel, list[WrappedType]] = {}
627
+ return_dict: dict[WrappedRecordModel, list[WrappedType]] = {}
577
628
  for model in models:
578
629
  return_dict[model] = model.get_children_of_type(child_type)
579
630
  return return_dict
580
631
 
581
632
  @staticmethod
582
- def map_by_child(models: Iterable[RecordModel], child_type: type[WrappedType]) \
583
- -> dict[WrappedType, RecordModel]:
633
+ def map_by_child(models: Iterable[WrappedRecordModel], child_type: type[WrappedType]) \
634
+ -> dict[WrappedType, WrappedRecordModel]:
584
635
  """
585
636
  Take a list of record models and map them by their children. Essentially an inversion of map_to_child.
586
637
  If two records share the same child, an exception will be thrown. The children must already be loaded.
@@ -590,8 +641,8 @@ class RecordHandler:
590
641
  :return: A dict[ChildType, ModelType]. If an input model doesn't have a child of the given child type,
591
642
  then it will not be in the resulting dictionary.
592
643
  """
593
- to_child: dict[RecordModel, WrappedType] = RecordHandler.map_to_child(models, child_type)
594
- by_child: dict[WrappedType, RecordModel] = {}
644
+ to_child: dict[WrappedRecordModel, WrappedType] = RecordHandler.map_to_child(models, child_type)
645
+ by_child: dict[WrappedType, WrappedRecordModel] = {}
595
646
  for record, child in to_child.items():
596
647
  if child is None:
597
648
  continue
@@ -602,8 +653,8 @@ class RecordHandler:
602
653
  return by_child
603
654
 
604
655
  @staticmethod
605
- def map_by_children(models: Iterable[RecordModel], child_type: type[WrappedType]) \
606
- -> dict[WrappedType, list[RecordModel]]:
656
+ def map_by_children(models: Iterable[WrappedRecordModel], child_type: type[WrappedType]) \
657
+ -> dict[WrappedType, list[WrappedRecordModel]]:
607
658
  """
608
659
  Take a list of record models and map them by their children. Essentially an inversion of map_to_children. Input
609
660
  models that share a child will end up in the same list. The children must already be loaded.
@@ -613,8 +664,8 @@ class RecordHandler:
613
664
  :return: A dict[ChildType, list[ModelType]]. If an input model doesn't have children of the given child type,
614
665
  then it will not be in the resulting dictionary.
615
666
  """
616
- to_children: dict[RecordModel, list[WrappedType]] = RecordHandler.map_to_children(models, child_type)
617
- by_children: dict[WrappedType, list[RecordModel]] = {}
667
+ to_children: dict[WrappedRecordModel, list[WrappedType]] = RecordHandler.map_to_children(models, child_type)
668
+ by_children: dict[WrappedType, list[WrappedRecordModel]] = {}
618
669
  for record, children in to_children.items():
619
670
  for child in children:
620
671
  by_children.setdefault(child, []).append(record)
@@ -921,8 +972,9 @@ class RecordHandler:
921
972
 
922
973
  # FR-46155: Update relationship path traversing functions to be non-static and take in a wrapper type so that the
923
974
  # output can be wrapped instead of requiring the user to wrap the output.
924
- def get_linear_path(self, models: Iterable[RecordModel], path: RelationshipPath, wrapper_type: type[WrappedType]) \
925
- -> dict[RecordModel, WrappedType | None]:
975
+ def get_linear_path(self, models: Iterable[RecordModel], path: RelationshipPath,
976
+ wrapper_type: type[WrappedType] | None = None) \
977
+ -> dict[RecordModel, WrappedType | PyRecordModel | None]:
926
978
  """
927
979
  Given a relationship path, travel the path starting from the input models. Returns the record at the end of the
928
980
  path, if any. The hierarchy must be linear (1:1 relationship between data types at every step) and the
@@ -930,7 +982,8 @@ class RecordHandler:
930
982
 
931
983
  :param models: A list of record models.
932
984
  :param path: The relationship path to follow.
933
- :param wrapper_type: The record model wrapper to use.
985
+ :param wrapper_type: The record model wrapper to use on the record at the end of the path. If not provided,
986
+ the record will be a PyRecordModel instead of a WrappedRecordModel.
934
987
  :return: Each record model mapped to the record at the end of the path starting from itself. If the end of the
935
988
  path couldn't be reached, the record will map to None.
936
989
  """
@@ -978,11 +1031,12 @@ class RecordHandler:
978
1031
  current = reverse_links[0]
979
1032
  else:
980
1033
  raise SapioException("Unsupported path direction.")
981
- ret_dict.update({model: self.inst_man.wrap(current, wrapper_type) if current else None})
1034
+ ret_dict.update({model: self.wrap_model(current, wrapper_type) if current else None})
982
1035
  return ret_dict
983
1036
 
984
1037
  def get_branching_path(self, models: Iterable[RecordModel], path: RelationshipPath,
985
- wrapper_type: type[WrappedType]) -> dict[RecordModel, list[WrappedType]]:
1038
+ wrapper_type: type[WrappedType] | None = None)\
1039
+ -> dict[RecordModel, list[WrappedType] | list[PyRecordModel]]:
986
1040
  """
987
1041
  Given a relationship path, travel the path starting from the input models. Returns the record at the end of the
988
1042
  path, if any. The hierarchy may be non-linear (1:Many relationships between data types are allowed) and the
@@ -990,7 +1044,8 @@ class RecordHandler:
990
1044
 
991
1045
  :param models: A list of record models.
992
1046
  :param path: The relationship path to follow.
993
- :param wrapper_type: The record model wrapper to use.
1047
+ :param wrapper_type: The record model wrapper to use on the records at the end of the path. If not provided,
1048
+ the records will be PyRecordModels instead of WrappedRecordModels.
994
1049
  :return: Each record model mapped to the records at the end of the path starting from itself. If the end of the
995
1050
  path couldn't be reached, the record will map to an empty list.
996
1051
  """
@@ -1023,13 +1078,14 @@ class RecordHandler:
1023
1078
  raise SapioException("Unsupported path direction.")
1024
1079
  current_search = next_search
1025
1080
  next_search = set()
1026
- ret_dict.update({model: self.inst_man.wrap_list(list(current_search), wrapper_type)})
1081
+ ret_dict.update({model: self.wrap_models(current_search, wrapper_type)})
1027
1082
  return ret_dict
1028
1083
 
1029
1084
  # FR-46155: Create a relationship traversing function that returns a single function at the end of the path like
1030
1085
  # get_linear_path but can handle branching paths in the middle of the search like get_branching_path.
1031
- def get_flat_path(self, models: Iterable[RecordModel], path: RelationshipPath, wrapper_type: type[WrappedType]) \
1032
- -> dict[RecordModel, WrappedType | None]:
1086
+ def get_flat_path(self, models: Iterable[RecordModel], path: RelationshipPath,
1087
+ wrapper_type: type[WrappedType] | None = None) \
1088
+ -> dict[RecordModel, WrappedType | PyRecordModel | None]:
1033
1089
  """
1034
1090
  Given a relationship path, travel the path starting from the input models. Returns the record at the end of the
1035
1091
  path, if any. The hierarchy may be non-linear (1:Many relationships between data types are allowed) and the
@@ -1041,7 +1097,8 @@ class RecordHandler:
1041
1097
 
1042
1098
  :param models: A list of record models.
1043
1099
  :param path: The relationship path to follow.
1044
- :param wrapper_type: The record model wrapper to use.
1100
+ :param wrapper_type: The record model wrapper to use on the record at the end of the path. If not provided,
1101
+ the record will be a PyRecordModel instead of a WrappedRecordModel.
1045
1102
  :return: Each record model mapped to the record at the end of the path starting from itself. If the end of the
1046
1103
  path couldn't be reached, the record will map to None.
1047
1104
  """
@@ -1069,21 +1126,22 @@ class RecordHandler:
1069
1126
  current = current[0].get_reverse_side_link(data_type, node.data_field_name)
1070
1127
  else:
1071
1128
  raise SapioException("Unsupported path direction.")
1072
- ret_dict.update({model: self.inst_man.wrap(current[0], wrapper_type) if current else None})
1129
+ ret_dict.update({model: self.wrap_model(current[0], wrapper_type) if current else None})
1073
1130
  return ret_dict
1074
1131
 
1075
- def __find_model(self, wrapper_type: type[WrappedType], primary_identifier: str, id_value: FieldValue,
1076
- secondary_identifiers: FieldIdentifierMap | None = None) -> WrappedType | None:
1132
+ def __find_model(self, wrapper_type: type[WrappedType] | str, primary_identifier: str, id_value: FieldValue,
1133
+ secondary_identifiers: FieldIdentifierMap | None = None) -> WrappedType | PyRecordModel | None:
1077
1134
  """
1078
1135
  Find a record from the system that matches the given field values. The primary identifier and value is used
1079
1136
  to query for the record, then the secondary identifiers may be optionally provided to further filter the
1080
1137
  returned results. If no record is found with these filters, returns None.
1081
1138
  """
1082
1139
  # Query for all records that match the primary identifier.
1083
- results: list[WrappedType] = self.query_models(wrapper_type, primary_identifier, [id_value])
1140
+ results: list[WrappedType] | list[PyRecordModel] = self.query_models(wrapper_type, primary_identifier,
1141
+ [id_value])
1084
1142
 
1085
1143
  # Find the one record, if any, that matches the secondary identifiers.
1086
- unique_record: WrappedType | None = None
1144
+ unique_record: WrappedType | PyRecordModel | None = None
1087
1145
  for result in results:
1088
1146
  matches_all: bool = True
1089
1147
  for field, value in secondary_identifiers.items():
@@ -1093,22 +1151,21 @@ class RecordHandler:
1093
1151
  if matches_all:
1094
1152
  # If a previous record in the results already matched all identifiers, then throw an exception.
1095
1153
  if unique_record is not None:
1096
- raise SapioException(f"More than one record of type {wrapper_type.get_wrapper_data_type_name()} "
1154
+ raise SapioException(f"More than one record of type {AliasUtil.to_data_type_name(wrapper_type)} "
1097
1155
  f"encountered in system that matches all provided identifiers.")
1098
1156
  unique_record = result
1099
1157
  return unique_record
1100
1158
 
1101
1159
  @staticmethod
1102
- def __verify_data_type(records: Iterable[DataRecord], wrapper_type: type[WrappedType]) -> None:
1160
+ def __verify_data_type(record: DataRecord | PyRecordModel, wrapper_type: type[WrappedType]) -> None:
1103
1161
  """
1104
- Throw an exception if the data type of the given records and wrapper don't match.
1162
+ Throw an exception if the data type of the given record and wrapper don't match.
1105
1163
  """
1106
1164
  model_type: str = wrapper_type.get_wrapper_data_type_name()
1107
- for record in records:
1108
- record_type: str = record.data_type_name
1109
- # Account for ELN data type records.
1110
- if ElnBaseDataType.is_eln_type(record_type):
1111
- record_type = ElnBaseDataType.get_base_type(record_type).data_type_name
1112
- if record_type != model_type:
1113
- raise SapioException(f"Data record of type {record_type} cannot be wrapped by the record model wrapper "
1114
- f"of type {model_type}")
1165
+ record_type: str = AliasUtil.to_data_type_name(record)
1166
+ # Account for ELN data type records.
1167
+ if ElnBaseDataType.is_eln_type(record_type):
1168
+ record_type = ElnBaseDataType.get_base_type(record_type).data_type_name
1169
+ if record_type != model_type:
1170
+ raise SapioException(f"Data record of type {record_type} cannot be wrapped by the record model wrapper "
1171
+ f"of type {model_type}")