sapiopycommons 2024.11.8a355__py3-none-any.whl → 2024.11.8a359__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 (47) hide show
  1. sapiopycommons/callbacks/callback_util.py +83 -532
  2. sapiopycommons/chem/IndigoMolecules.py +0 -2
  3. sapiopycommons/chem/Molecules.py +18 -77
  4. sapiopycommons/datatype/attachment_util.py +10 -11
  5. sapiopycommons/eln/experiment_handler.py +70 -272
  6. sapiopycommons/files/complex_data_loader.py +4 -5
  7. sapiopycommons/files/file_bridge.py +24 -31
  8. sapiopycommons/files/file_data_handler.py +5 -2
  9. sapiopycommons/files/file_util.py +9 -59
  10. sapiopycommons/files/file_validator.py +6 -92
  11. sapiopycommons/files/file_writer.py +15 -44
  12. sapiopycommons/general/aliases.py +6 -207
  13. sapiopycommons/general/custom_report_util.py +37 -212
  14. sapiopycommons/general/exceptions.py +8 -21
  15. sapiopycommons/general/popup_util.py +0 -21
  16. sapiopycommons/general/time_util.py +2 -8
  17. sapiopycommons/processtracking/endpoints.py +22 -22
  18. sapiopycommons/recordmodel/record_handler.py +97 -481
  19. sapiopycommons/rules/eln_rule_handler.py +25 -34
  20. sapiopycommons/rules/on_save_rule_handler.py +31 -34
  21. sapiopycommons/webhook/webhook_handlers.py +42 -201
  22. {sapiopycommons-2024.11.8a355.dist-info → sapiopycommons-2024.11.8a359.dist-info}/METADATA +2 -4
  23. sapiopycommons-2024.11.8a359.dist-info/RECORD +38 -0
  24. sapiopycommons/callbacks/field_builder.py +0 -537
  25. sapiopycommons/customreport/__init__.py +0 -0
  26. sapiopycommons/customreport/column_builder.py +0 -60
  27. sapiopycommons/customreport/custom_report_builder.py +0 -130
  28. sapiopycommons/customreport/term_builder.py +0 -299
  29. sapiopycommons/datatype/data_fields.py +0 -61
  30. sapiopycommons/datatype/pseudo_data_types.py +0 -440
  31. sapiopycommons/eln/experiment_report_util.py +0 -653
  32. sapiopycommons/files/file_bridge_handler.py +0 -340
  33. sapiopycommons/flowcyto/flow_cyto.py +0 -77
  34. sapiopycommons/flowcyto/flowcyto_data.py +0 -75
  35. sapiopycommons/general/accession_service.py +0 -375
  36. sapiopycommons/general/audit_log.py +0 -189
  37. sapiopycommons/general/sapio_links.py +0 -50
  38. sapiopycommons/multimodal/multimodal.py +0 -146
  39. sapiopycommons/multimodal/multimodal_data.py +0 -489
  40. sapiopycommons/processtracking/custom_workflow_handler.py +0 -406
  41. sapiopycommons/sftpconnect/__init__.py +0 -0
  42. sapiopycommons/sftpconnect/sftp_builder.py +0 -69
  43. sapiopycommons/webhook/webhook_context.py +0 -39
  44. sapiopycommons/webhook/webservice_handlers.py +0 -67
  45. sapiopycommons-2024.11.8a355.dist-info/RECORD +0 -59
  46. {sapiopycommons-2024.11.8a355.dist-info → sapiopycommons-2024.11.8a359.dist-info}/WHEEL +0 -0
  47. {sapiopycommons-2024.11.8a355.dist-info → sapiopycommons-2024.11.8a359.dist-info}/licenses/LICENSE +0 -0
@@ -1,51 +1,24 @@
1
1
  from collections.abc import Iterable
2
2
  from typing import Any
3
3
 
4
- from sapiopylib.rest.User import SapioUser
5
4
  from sapiopylib.rest.pojo.DataRecord import DataRecord
6
- from sapiopylib.rest.pojo.datatype.FieldDefinition import FieldType
7
5
  from sapiopylib.rest.pojo.eln.ElnExperiment import ElnExperiment
8
- from sapiopylib.rest.pojo.eln.ExperimentEntry import ExperimentEntry
9
- from sapiopylib.rest.pojo.eln.SapioELNEnums import ElnBaseDataType
10
- from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
11
- from sapiopylib.rest.utils.Protocols import ElnExperimentProtocol, ElnEntryStep
6
+ from sapiopylib.rest.utils.Protocols import ElnExperimentProtocol
12
7
  from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
13
- from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedRecordModel, WrappedType, WrapperField
8
+ from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedRecordModel, WrappedType
14
9
 
15
- from sapiopycommons.general.exceptions import SapioException
16
-
17
- FieldValue = int | float | str | bool | None
18
- """Allowable values for fields in the system."""
19
- RecordModel = PyRecordModel | WrappedRecordModel
10
+ RecordModel = PyRecordModel | WrappedRecordModel | WrappedType
20
11
  """Different forms that a record model could take."""
21
12
  SapioRecord = DataRecord | RecordModel
22
13
  """A record could be provided as either a DataRecord, PyRecordModel, or WrappedRecordModel (WrappedType)."""
23
14
  RecordIdentifier = SapioRecord | int
24
15
  """A RecordIdentifier is either a record type or an integer for the record's record ID."""
25
- DataTypeIdentifier = SapioRecord | type[WrappedType] | str
26
- """A DataTypeIdentifier is either a SapioRecord, a record model wrapper type, or a string."""
27
- FieldIdentifier = WrapperField | str | tuple[str, FieldType]
28
- """A FieldIdentifier is either wrapper field from a record model wrapper, a string, or a tuple of string
29
- and field type."""
30
- FieldIdentifierKey = WrapperField | str
31
- """A FieldIdentifierKey is a FieldIdentifier, except it can't be a tuple, s tuples can't be used as keys in
32
- dictionaries.."""
33
- HasFieldWrappers = type[WrappedType] | WrappedRecordModel
34
- """An identifier for classes that have wrapper fields."""
35
16
  ExperimentIdentifier = ElnExperimentProtocol | ElnExperiment | int
36
- """An ExperimentIdentifier is either an experiment protocol, experiment, or an integer for the experiment's notebook
17
+ """An ExperimentIdentifier is either an experiment protocol, experiment, or an integer for te experiment's notebook
37
18
  ID."""
38
- ExperimentEntryIdentifier = ElnEntryStep | ExperimentEntry | int
39
- """An ExperimentEntryIdentifier is either an ELN entry step, experiment entry, or an integer for the entry's ID."""
40
- FieldMap = dict[str, FieldValue]
19
+ FieldMap = dict[str, Any]
41
20
  """A field map is simply a dict of data field names to values. The purpose of aliasing this is to help distinguish
42
21
  any random dict in a webhook from one which is explicitly used for record fields."""
43
- FieldIdentifierMap = dict[FieldIdentifierKey, FieldValue]
44
- """A field identifier map is the same thing as a field map, except the keys can be field identifiers instead
45
- of just strings. Note that although one of the allowed field identifiers is a tuple, you can't use tuples as
46
- keys in a dictionary."""
47
- UserIdentifier = SapioWebhookContext | SapioUser
48
- """An identifier for classes from which a user object can be used for sending requests."""
49
22
 
50
23
 
51
24
  # FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
@@ -77,132 +50,7 @@ class AliasUtil:
77
50
 
78
51
  :return: A list of record IDs for the input records.
79
52
  """
80
- return [(AliasUtil.to_record_id(x)) for x in records]
81
-
82
- @staticmethod
83
- def to_record_id(record: RecordIdentifier):
84
- """
85
- Convert a single variable that could be either an integer, DataRecord, PyRecordModel,
86
- or WrappedRecordModel to just an integer (taking the record ID from the record).
87
-
88
- :return: A record ID for the input record.
89
- """
90
- return record if isinstance(record, int) else record.record_id
91
-
92
- @staticmethod
93
- def to_data_type_name(value: DataTypeIdentifier, convert_eln_dts: bool = True) -> str:
94
- """
95
- Convert a given value to a data type name.
96
-
97
- :param value: A value which is a string, record, or record model type.
98
- :param convert_eln_dts: If true, convert ELN data types to their base data type name.
99
- :return: A string of the data type name of the input value.
100
- """
101
- if isinstance(value, SapioRecord):
102
- value = value.data_type_name
103
- elif not isinstance(value, str):
104
- value = value.get_wrapper_data_type_name()
105
- if convert_eln_dts and ElnBaseDataType.is_eln_type(value):
106
- return ElnBaseDataType.get_base_type(value).data_type_name
107
- return value
108
-
109
- @staticmethod
110
- def to_data_type_names(values: Iterable[DataTypeIdentifier], return_set: bool = False,
111
- convert_eln_dts: bool = True) -> list[str] | set[str]:
112
- """
113
- Convert a given iterable of values to a list or set of data type names.
114
-
115
- :param values: An iterable of values which are strings, records, or record model types.
116
- :param return_set: If true, return a set instead of a list.
117
- :param convert_eln_dts: If true, convert ELN data types to their base data type name.
118
- :return: A list or set of strings of the data type name of the input value.
119
- """
120
- values = [AliasUtil.to_data_type_name(x, convert_eln_dts) for x in values]
121
- return set(values) if return_set else values
122
-
123
- @staticmethod
124
- def to_singular_data_type_name(values: Iterable[DataTypeIdentifier], convert_eln_dts: bool = True) -> str:
125
- """
126
- Convert a given iterable of values to a singular data type name that they share. Throws an exception if more
127
- than one data type name exists in the provided list of identifiers.
128
-
129
- :param values: An iterable of values which are strings, records, or record model types.
130
- :param convert_eln_dts: If true, convert ELN data types to their base data type name.
131
- :return: The single data type name that the input vales share. Returns an empty string if an empty iterable
132
- was provided.
133
- """
134
- if not values:
135
- return ""
136
- data_types: set[str] = AliasUtil.to_data_type_names(values, True, convert_eln_dts)
137
- if len(data_types) > 1:
138
- raise SapioException(f"Provided values contain multiple data types: {data_types}. "
139
- f"Only expecting a single data type.")
140
- return data_types.pop()
141
-
142
- @staticmethod
143
- def to_data_field_name(value: FieldIdentifier) -> str:
144
- """
145
- Convert a string or WrapperField to a data field name string.
146
-
147
- :param value: A string or WrapperField.
148
- :return: A string of the data field name of the input value.
149
- """
150
- if isinstance(value, tuple):
151
- return value[0]
152
- if isinstance(value, WrapperField):
153
- return value.field_name
154
- return value
155
-
156
- @staticmethod
157
- def to_data_field_names(values: Iterable[FieldIdentifier]) -> list[str]:
158
- """
159
- Convert an iterable of strings or WrapperFields to a list of data field name strings.
160
-
161
- :param values: An iterable of strings or WrapperFields.
162
- :return: A list of strings of the data field names of the input values.
163
- """
164
- return [AliasUtil.to_data_field_name(x) for x in values]
165
-
166
- @staticmethod
167
- def to_data_field_names_dict(values: dict[FieldIdentifierKey, Any]) -> dict[str, Any]:
168
- """
169
- Take a dictionary whose keys are field identifiers and convert them all to strings for the data field name.
170
-
171
- :param values: A dictionary of field identifiers to field values.
172
- :return: A dictionary of strings of the data field names to field values for the input values.
173
- """
174
- ret_dict: dict[str, FieldValue] = {}
175
- for field, value in values.items():
176
- ret_dict[AliasUtil.to_data_field_name(field)] = value
177
- return ret_dict
178
-
179
- @staticmethod
180
- def to_data_field_names_list_dict(values: list[dict[FieldIdentifierKey, Any]]) -> list[dict[str, Any]]:
181
- ret_list: list[dict[str, Any]] = []
182
- for field_map in values:
183
- ret_list.append(AliasUtil.to_data_field_names_dict(field_map))
184
- return ret_list
185
-
186
- @staticmethod
187
- def to_field_type(field: FieldIdentifier, data_type: HasFieldWrappers | None = None) -> FieldType:
188
- """
189
- Convert a given field identifier to the field type for that field.
190
-
191
- :param field: A string or WrapperField.
192
- :param data_type: If the field is provided as a string, then a record model wrapper or wrapped record model
193
- must be provided to determine the field type.
194
- :return: The field type of the given field.
195
- """
196
- if isinstance(field, tuple):
197
- return field[1]
198
- if isinstance(field, WrapperField):
199
- return field.field_type
200
- for var in dir(data_type):
201
- attr = getattr(data_type, var)
202
- if isinstance(attr, WrapperField) and attr.field_name == field:
203
- return attr.field_type
204
- raise SapioException(f"The wrapper of data type \"{data_type.get_wrapper_data_type_name()}\" doesn't have a "
205
- f"field with the name \"{field}\",")
53
+ return [(x if isinstance(x, int) else x.record_id) for x in records]
206
54
 
207
55
  @staticmethod
208
56
  def to_field_map_lists(records: Iterable[SapioRecord]) -> list[FieldMap]:
@@ -215,7 +63,6 @@ class AliasUtil:
215
63
  field_map_list: list[FieldMap] = []
216
64
  for record in records:
217
65
  if isinstance(record, DataRecord):
218
- # noinspection PyTypeChecker
219
66
  field_map_list.append(record.get_fields())
220
67
  else:
221
68
  field_map_list.append(record.fields.copy_to_dict())
@@ -233,51 +80,3 @@ class AliasUtil:
233
80
  if isinstance(experiment, ElnExperiment):
234
81
  return experiment.notebook_experiment_id
235
82
  return experiment.get_id()
236
-
237
- @staticmethod
238
- def to_notebook_ids(experiments: list[ExperimentIdentifier]) -> list[int]:
239
- """
240
- Convert a list of objects that identify ELN experiments to their notebook IDs.
241
-
242
- :return: The list of notebook IDs for the experiment identifiers.
243
- """
244
- notebook_ids: list[int] = []
245
- for experiment in experiments:
246
- notebook_ids.append(AliasUtil.to_notebook_id(experiment))
247
- return notebook_ids
248
-
249
- @staticmethod
250
- def to_entry_id(entry: ExperimentEntryIdentifier) -> int:
251
- """
252
- Convert an object that identifies an experiment entry to its entry ID.
253
-
254
- :return: The entry ID for the entry identifier.
255
- """
256
- if isinstance(entry, int):
257
- return entry
258
- elif isinstance(entry, ExperimentEntry):
259
- return entry.entry_id
260
- elif isinstance(entry, ElnEntryStep):
261
- return entry.get_id()
262
- raise SapioException(f"Unrecognized entry identifier of type {type(entry)}")
263
-
264
- @staticmethod
265
- def to_entry_ids(entries: list[ExperimentEntryIdentifier]) -> list[int]:
266
- """
267
- Convert a list of objects that identify experiment entries to their entry IDs.
268
-
269
- :return: The list of entry IDs for the entry identifiers.
270
- """
271
- entry_ids: list[int] = []
272
- for entry in entries:
273
- entry_ids.append(AliasUtil.to_entry_id(entry))
274
- return entry_ids
275
-
276
- @staticmethod
277
- def to_sapio_user(context: UserIdentifier) -> SapioUser:
278
- """
279
- Convert an object that could be either a SapioUser or SapioWebhookContext to just a SapioUser.
280
-
281
- :return: A SapioUser object.
282
- """
283
- return context if isinstance(context, SapioUser) else context.user
@@ -1,21 +1,19 @@
1
1
  from collections.abc import Iterable
2
+ from typing import Any
2
3
 
3
4
  from sapiopylib.rest.DataMgmtService import DataMgmtServer
4
5
  from sapiopylib.rest.User import SapioUser
5
- from sapiopylib.rest.pojo.CustomReport import ReportColumn, CustomReport, CustomReportCriteria, RawReportTerm
6
-
7
- from sapiopycommons.general.aliases import UserIdentifier, FieldValue, AliasUtil, FieldIdentifierKey
6
+ from sapiopylib.rest.pojo.CustomReport import ReportColumn, CustomReport
7
+ from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
8
8
 
9
9
 
10
10
  # FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
11
11
  class CustomReportUtil:
12
12
  @staticmethod
13
- def run_system_report(context: UserIdentifier,
13
+ def run_system_report(context: SapioWebhookContext | SapioUser,
14
14
  report_name: str,
15
- filters: dict[FieldIdentifierKey, Iterable[FieldValue]] | None = None,
16
- page_limit: int | None = None,
17
- page_size: int | None = None,
18
- page_number: int | None = None) -> list[dict[str, FieldValue]]:
15
+ filters: dict[str, Iterable[Any]] | None = None,
16
+ page_limit: int | None = None) -> list[dict[str, Any]]:
19
17
  """
20
18
  Run a system report and return the results of that report as a list of dictionaries for the values of each
21
19
  column in each row.
@@ -29,97 +27,29 @@ class CustomReportUtil:
29
27
  filter on. Only those headers that both the filters and the custom report share will take effect. That is,
30
28
  any filters that have a header name that isn't in the custom report will be ignored.
31
29
  :param page_limit: The maximum number of pages to query. If None, exhausts all possible pages.
32
- :param page_size: The size of each page of results in the search. If None, the page size is set by the server.
33
- :param page_number: The page number to start the search from, If None, starts on the first page.
34
- :return: The results of the report listed row by row, mapping each cell to the header it is under. The header
35
- values in the dicts are the data field names of the columns.
36
- If two columns in the search have the same data field name but differing data type names, then the
37
- dictionary key to the value in the column will be "DataTypeName.DataFieldName". For example, if you
38
- had a Sample column with a data field name of Identifier and a Request column with the same data field name,
39
- then the dictionary keys for these columns would be Sample.Identifier and Request.Identifier respectively.
40
- """
41
- results: tuple = CustomReportUtil.__exhaust_system_report(context, report_name, page_limit,
42
- page_size, page_number)
43
- columns: list[ReportColumn] = results[0]
44
- rows: list[list[FieldValue]] = results[1]
45
- return CustomReportUtil.__process_results(rows, columns, filters)
46
-
47
- @staticmethod
48
- def run_custom_report(context: UserIdentifier,
49
- report_criteria: CustomReportCriteria,
50
- filters: dict[FieldIdentifierKey, Iterable[FieldValue]] | None = None,
51
- page_limit: int | None = None,
52
- page_size: int | None = None,
53
- page_number: int | None = None) -> list[dict[str, FieldValue]]:
54
- """
55
- Run a custom report and return the results of that report as a list of dictionaries for the values of each
56
- column in each row.
57
-
58
- Custom reports are constructed by the caller, specifying the report terms and the columns that will be in the
59
- results. They are like advanced or predefined searches from the system, except they are constructed from
60
- within the webhook instead of from within the system.
61
-
62
- :param context: The current webhook context or a user object to send requests from.
63
- :param report_criteria: The custom report criteria to run.
64
- :param filters: If provided, filter the results of the report using the given mapping of headers to values to
65
- filter on. Only those headers that both the filters and the custom report share will take effect. That is,
66
- any filters that have a header name that isn't in the custom report will be ignored.
67
- Note that this parameter is only provided for parity with the other run report functions. If you need to
68
- filter the results of a search, it would likely be more beneficial to have just added a new term to the
69
- input report criteria that corresponds to the filter.
70
- :param page_limit: The maximum number of pages to query. If None, exhausts all possible pages.
71
- :param page_size: The size of each page of results in the search. If None, uses the value from the given report
72
- criteria. If not None, overwrites the value from the given report criteria.
73
- :param page_number: The page number to start the search from, If None, uses the value from the given report
74
- criteria. If not None, overwrites the value from the given report criteria.
75
30
  :return: The results of the report listed row by row, mapping each cell to the header it is under. The header
76
31
  values in the dicts are the data field names of the columns.
77
- If two columns in the search have the same data field name but differing data type names, then the
78
- dictionary key to the value in the column will be "DataTypeName.DataFieldName". For example, if you
79
- had a Sample column with a data field name of Identifier and a Request column with the same data field name,
80
- then the dictionary keys for these columns would be Sample.Identifier and Request.Identifier respectively.
81
32
  """
82
- results: tuple = CustomReportUtil.__exhaust_custom_report(context, report_criteria, page_limit,
83
- page_size, page_number)
33
+ results = CustomReportUtil.__exhaust_system_report(context, report_name, page_limit)
84
34
  columns: list[ReportColumn] = results[0]
85
- rows: list[list[FieldValue]] = results[1]
86
- return CustomReportUtil.__process_results(rows, columns, filters)
87
-
88
- @staticmethod
89
- def run_quick_report(context: UserIdentifier,
90
- report_term: RawReportTerm,
91
- filters: dict[FieldIdentifierKey, Iterable[FieldValue]] | None = None,
92
- page_limit: int | None = None,
93
- page_size: int | None = None,
94
- page_number: int | None = None) -> list[dict[str, FieldValue]]:
95
- """
96
- Run a quick report and return the results of that report as a list of dictionaries for the values of each
97
- column in each row.
35
+ rows: list[list[Any]] = results[1]
98
36
 
99
- Quick reports are helpful for cases where you need to query record field values in a more complex manner than
100
- the data record manager allows, but still simpler than a full-blown custom report. The columns that are returned
101
- in a quick search are every visible field from the data type that corresponds to the given report term. (Fields
102
- which are not marked as visible in the data designer will be excluded.)
103
-
104
- :param context: The current webhook context or a user object to send requests from.
105
- :param report_term: The raw report term to use for the quick report.
106
- :param filters: If provided, filter the results of the report using the given mapping of headers to values to
107
- filter on. Only those headers that both the filters and the custom report share will take effect. That is,
108
- any filters that have a header name that isn't in the custom report will be ignored.
109
- :param page_limit: The maximum number of pages to query. If None, exhausts all possible pages.
110
- :param page_size: The size of each page of results in the search. If None, the page size is set by the server.
111
- :param page_number: The page number to start the search from, If None, starts on the first page.
112
- :return: The results of the report listed row by row, mapping each cell to the header it is under. The header
113
- values in the dicts are the data field names of the columns.
114
- """
115
- results: tuple = CustomReportUtil.__exhaust_quick_report(context, report_term, page_limit,
116
- page_size, page_number)
117
- columns: list[ReportColumn] = results[0]
118
- rows: list[list[FieldValue]] = results[1]
119
- return CustomReportUtil.__process_results(rows, columns, filters)
37
+ ret: list[dict[str, Any]] = []
38
+ for row in rows:
39
+ row_data: dict[str, Any] = {}
40
+ filter_row: bool = False
41
+ for value, column in zip(row, columns):
42
+ header: str = column.data_field_name
43
+ if filters is not None and header in filters and value not in filters.get(header):
44
+ filter_row = True
45
+ break
46
+ row_data.update({header: value})
47
+ if filter_row is False:
48
+ ret.append(row_data)
49
+ return ret
120
50
 
121
51
  @staticmethod
122
- def get_system_report_criteria(context: UserIdentifier, report_name: str) -> CustomReport:
52
+ def get_system_report_criteria(context: SapioWebhookContext | SapioUser, report_name: str) -> CustomReport:
123
53
  """
124
54
  Retrieve a custom report from the system given the name of the report. This works by querying the system report
125
55
  with a page number and size of 1 to minimize the amount of data transfer needed to retrieve the report's config.
@@ -134,132 +64,27 @@ class CustomReportUtil:
134
64
  :param report_name: The name of the system report to run.
135
65
  :return: The CustomReport object for the given system report name.
136
66
  """
137
- user: SapioUser = AliasUtil.to_sapio_user(context)
67
+ user: SapioUser = context if isinstance(context, SapioUser) else context.user
138
68
  report_man = DataMgmtServer.get_custom_report_manager(user)
139
69
  return report_man.run_system_report_by_name(report_name, 1, 1)
140
70
 
141
71
  @staticmethod
142
- def __exhaust_system_report(context: UserIdentifier,
143
- report_name: str,
144
- page_limit: int | None,
145
- page_size: int | None,
146
- page_number: int | None) \
147
- -> tuple[list[ReportColumn], list[list[FieldValue]]]:
148
- """
149
- Given a system report, iterate over every page of the report and collect the results
150
- until there are no remaining pages.
151
- """
152
- user: SapioUser = AliasUtil.to_sapio_user(context)
153
- report_man = DataMgmtServer.get_custom_report_manager(user)
154
-
155
- result = None
156
- has_next_page: bool = True
157
- rows: list[list[FieldValue]] = []
158
- cur_page: int = 1
159
- while has_next_page and (not page_limit or cur_page <= page_limit):
160
- result = report_man.run_system_report_by_name(report_name, page_size, page_number)
161
- page_size = result.page_size
162
- page_number = result.page_number
163
- has_next_page = result.has_next_page
164
- rows.extend(result.result_table)
165
- cur_page += 1
166
- return result.column_list, rows
167
-
168
- @staticmethod
169
- def __exhaust_custom_report(context: UserIdentifier,
170
- report: CustomReportCriteria,
171
- page_limit: int | None,
172
- page_size: int | None,
173
- page_number: int | None) \
174
- -> tuple[list[ReportColumn], list[list[FieldValue]]]:
175
- """
176
- Given a custom report, iterate over every page of the report and collect the results
177
- until there are no remaining pages.
178
- """
179
- user: SapioUser = AliasUtil.to_sapio_user(context)
72
+ def __exhaust_system_report(context: SapioWebhookContext | SapioUser, report_name: str, page_limit: int | None = None) \
73
+ -> tuple[list[ReportColumn], list[list[Any]]]:
74
+ user: SapioUser = context if isinstance(context, SapioUser) else context.user
180
75
  report_man = DataMgmtServer.get_custom_report_manager(user)
181
76
 
182
- result = None
183
- if page_size is not None:
184
- report.page_size = page_size
185
- if page_number is not None:
186
- report.page_number = page_number
77
+ report = None
78
+ page_size: int | None = None
79
+ page_number: int | None = None
187
80
  has_next_page: bool = True
188
- rows: list[list[FieldValue]] = []
81
+ rows: list[list[Any]] = []
189
82
  cur_page: int = 1
190
- while has_next_page and (not page_limit or cur_page <= page_limit):
191
- result = report_man.run_custom_report(report)
192
- report.page_size = result.page_size
193
- report.page_number = result.page_number
194
- has_next_page = result.has_next_page
195
- rows.extend(result.result_table)
83
+ while has_next_page and (not page_limit or cur_page < page_limit):
84
+ report = report_man.run_system_report_by_name(report_name, page_size, page_number)
85
+ page_size = report.page_size
86
+ page_number = report.page_number
87
+ has_next_page = report.has_next_page
88
+ rows.extend(report.result_table)
196
89
  cur_page += 1
197
- return result.column_list, rows
198
-
199
- @staticmethod
200
- def __exhaust_quick_report(context: UserIdentifier,
201
- report_term: RawReportTerm,
202
- page_limit: int | None,
203
- page_size: int | None,
204
- page_number: int | None) \
205
- -> tuple[list[ReportColumn], list[list[FieldValue]]]:
206
- """
207
- Given a quick report, iterate over every page of the report and collect the results
208
- until there are no remaining pages.
209
- """
210
- user: SapioUser = AliasUtil.to_sapio_user(context)
211
- report_man = DataMgmtServer.get_custom_report_manager(user)
212
-
213
- result = None
214
- has_next_page: bool = True
215
- rows: list[list[FieldValue]] = []
216
- cur_page: int = 1
217
- while has_next_page and (not page_limit or cur_page <= page_limit):
218
- result = report_man.run_quick_report(report_term, page_size, page_number)
219
- page_size = result.page_size
220
- page_number = result.page_number
221
- has_next_page = result.has_next_page
222
- rows.extend(result.result_table)
223
- cur_page += 1
224
- return result.column_list, rows
225
-
226
- @staticmethod
227
- def __process_results(rows: list[list[FieldValue]], columns: list[ReportColumn],
228
- filters: dict[FieldIdentifierKey, Iterable[FieldValue]] | None) -> list[dict[str, FieldValue]]:
229
- """
230
- Given the results of a report as a list of row values and the report's columns, combine these lists to
231
- result in a singular list of dictionaries for each row in the results.
232
-
233
- If any filter criteria has been provided, also use that to filter the row.
234
- """
235
- # It may be the case that two columns have the same data field name but differing data type names.
236
- # If this occurs, then we need to be able to differentiate these columns in the resulting dictionary.
237
- prepend_dt: set[str] = set()
238
- encountered_names: list[str] = []
239
- for column in columns:
240
- field_name: str = column.data_field_name
241
- if field_name in encountered_names:
242
- prepend_dt.add(field_name)
243
- else:
244
- encountered_names.append(field_name)
245
-
246
- if filters:
247
- filters: dict[str, Iterable[FieldValue]] = AliasUtil.to_data_field_names_dict(filters)
248
-
249
- ret: list[dict[str, FieldValue]] = []
250
- for row in rows:
251
- row_data: dict[str, FieldValue] = {}
252
- filter_row: bool = False
253
- for value, column in zip(row, columns):
254
- header: str = column.data_field_name
255
- # If two columns share the same data field name, prepend the data type name of the column to the
256
- # data field name.
257
- if header in prepend_dt:
258
- header = column.data_type_name + "." + header
259
- if filters is not None and header in filters and value not in filters.get(header):
260
- filter_row = True
261
- break
262
- row_data.update({header: value})
263
- if filter_row is False:
264
- ret.append(row_data)
265
- return ret
90
+ return report.column_list, rows
@@ -3,47 +3,34 @@ class SapioException(Exception):
3
3
  """
4
4
  A generic exception thrown by sapiopycommons methods. Typically caused by programmer error, but may also be from
5
5
  extremely edge case user errors. For expected user errors, use SapioUserErrorException.
6
-
7
- CommonsWebhookHandler's default behavior for this and any other exception that doesn't extend SapioException is
8
- to return a generic toaster message saying that an unexpected error has occurred.
9
6
  """
10
7
  pass
11
8
 
12
9
 
10
+ # CommonsWebhookHandler catches this exception and returns "User Cancelled."
13
11
  class SapioUserCancelledException(SapioException):
14
12
  """
15
13
  An exception thrown when the user cancels a client callback.
16
-
17
- CommonsWebhookHandler's default behavior is to simply end the webhook session with a true result without logging
18
- the exception.
19
- """
20
- pass
21
-
22
-
23
- class SapioDialogTimeoutException(SapioException):
24
- """
25
- An exception thrown when the user leaves a client callback open for too long.
26
-
27
- CommonsWebhookHandler's default behavior is to display an OK popup notifying the user that the dialog has timed out.
28
14
  """
29
15
  pass
30
16
 
31
17
 
18
+ # CommonsWebhookHandler catches this exception and returns the text to the user as display text in a webhook result.
32
19
  class SapioUserErrorException(SapioException):
33
20
  """
34
21
  An exception caused by user error (e.g. user provided a CSV when an XLSX was expected), which promises to return a
35
- user-friendly message explaining the error that should be displayed to the user.
36
-
37
- CommonsWebhookHandler's default behavior is to return the error message in a toaster popup.
22
+ user-friendly message explaining the error that should be displayed to the user. It is the responsibility of the
23
+ programmer to catch any such exceptions and return the value in e.args[0] as text for the user to see (such as
24
+ through the display text of a webhook result).
38
25
  """
39
26
  pass
40
27
 
41
28
 
29
+ # CommonsWebhookHandler catches this exception and returns the text in a display_error client callback.
42
30
  class SapioCriticalErrorException(SapioException):
43
31
  """
44
32
  A critical exception caused by user error, which promises to return a user-friendly message explaining the error
45
- that should be displayed to the user.
46
-
47
- CommonsWebhookHandler's default behavior is to return the error message in a display_error callback.
33
+ that should be displayed to the user. It is the responsibility of the programmer to catch any such exceptions and
34
+ return the value in e.args[0] as text for the user to see (such as through a dialog form client callback request).
48
35
  """
49
36
  pass