sapiopycommons 2024.8.19a305__py3-none-any.whl → 2024.8.26a307__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.

@@ -4,13 +4,14 @@ import urllib.parse
4
4
 
5
5
  from requests import Response
6
6
  from sapiopylib.rest.User import SapioUser
7
- from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
7
+
8
+ from sapiopycommons.general.aliases import UserIdentifier, AliasUtil
8
9
 
9
10
 
10
11
  # FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
11
12
  class FileBridge:
12
13
  @staticmethod
13
- def read_file(context: SapioWebhookContext | SapioUser, bridge_name: str, file_path: str,
14
+ def read_file(context: UserIdentifier, bridge_name: str, file_path: str,
14
15
  base64_decode: bool = True) -> bytes:
15
16
  """
16
17
  Read a file from FileBridge.
@@ -27,7 +28,7 @@ class FileBridge:
27
28
  params = {
28
29
  'Filepath': f"bridge://{bridge_name}/{file_path}"
29
30
  }
30
- user: SapioUser = context if isinstance(context, SapioUser) else context.user
31
+ user: SapioUser = AliasUtil.to_sapio_user(context)
31
32
  response = user.get(sub_path, params)
32
33
  user.raise_for_status(response)
33
34
 
@@ -37,7 +38,7 @@ class FileBridge:
37
38
  return ret_val
38
39
 
39
40
  @staticmethod
40
- def write_file(context: SapioWebhookContext | SapioUser, bridge_name: str, file_path: str,
41
+ def write_file(context: UserIdentifier, bridge_name: str, file_path: str,
41
42
  file_data: bytes | str) -> None:
42
43
  """
43
44
  Write a file to FileBridge.
@@ -53,13 +54,13 @@ class FileBridge:
53
54
  params = {
54
55
  'Filepath': f"bridge://{bridge_name}/{file_path}"
55
56
  }
56
- user: SapioUser = context if isinstance(context, SapioUser) else context.user
57
+ user: SapioUser = AliasUtil.to_sapio_user(context)
57
58
  with io.BytesIO(file_data.encode() if isinstance(file_data, str) else file_data) as data_stream:
58
59
  response = user.post_data_stream(sub_path, params=params, data_stream=data_stream)
59
60
  user.raise_for_status(response)
60
61
 
61
62
  @staticmethod
62
- def list_directory(context: SapioWebhookContext | SapioUser, bridge_name: str,
63
+ def list_directory(context: UserIdentifier, bridge_name: str,
63
64
  file_path: str | None = "") -> list[str]:
64
65
  """
65
66
  List the contents of a FileBridge directory.
@@ -74,7 +75,7 @@ class FileBridge:
74
75
  params = {
75
76
  'Filepath': f"bridge://{bridge_name}/{file_path}"
76
77
  }
77
- user: SapioUser = context if isinstance(context, SapioUser) else context.user
78
+ user: SapioUser = AliasUtil.to_sapio_user(context)
78
79
  response: Response = user.get(sub_path, params=params)
79
80
  user.raise_for_status(response)
80
81
 
@@ -83,7 +84,7 @@ class FileBridge:
83
84
  return [urllib.parse.unquote(value)[path_length:] for value in response_body]
84
85
 
85
86
  @staticmethod
86
- def create_directory(context: SapioWebhookContext | SapioUser, bridge_name: str, file_path: str) -> None:
87
+ def create_directory(context: UserIdentifier, bridge_name: str, file_path: str) -> None:
87
88
  """
88
89
  Create a new directory in FileBridge.
89
90
 
@@ -97,12 +98,12 @@ class FileBridge:
97
98
  params = {
98
99
  'Filepath': f"bridge://{bridge_name}/{file_path}"
99
100
  }
100
- user: SapioUser = context if isinstance(context, SapioUser) else context.user
101
+ user: SapioUser = AliasUtil.to_sapio_user(context)
101
102
  response = user.post(sub_path, params=params)
102
103
  user.raise_for_status(response)
103
104
 
104
105
  @staticmethod
105
- def delete_file(context: SapioWebhookContext | SapioUser, bridge_name: str, file_path: str) -> None:
106
+ def delete_file(context: UserIdentifier, bridge_name: str, file_path: str) -> None:
106
107
  """
107
108
  Delete an existing file in FileBridge.
108
109
 
@@ -115,12 +116,12 @@ class FileBridge:
115
116
  params = {
116
117
  'Filepath': f"bridge://{bridge_name}/{file_path}"
117
118
  }
118
- user: SapioUser = context if isinstance(context, SapioUser) else context.user
119
+ user: SapioUser = AliasUtil.to_sapio_user(context)
119
120
  response = user.delete(sub_path, params=params)
120
121
  user.raise_for_status(response)
121
122
 
122
123
  @staticmethod
123
- def delete_directory(context: SapioWebhookContext | SapioUser, bridge_name: str, file_path: str) -> None:
124
+ def delete_directory(context: UserIdentifier, bridge_name: str, file_path: str) -> None:
124
125
  """
125
126
  Delete an existing directory in FileBridge.
126
127
 
@@ -133,6 +134,6 @@ class FileBridge:
133
134
  params = {
134
135
  'Filepath': f"bridge://{bridge_name}/{file_path}"
135
136
  }
136
- user: SapioUser = context if isinstance(context, SapioUser) else context.user
137
+ user: SapioUser = AliasUtil.to_sapio_user(context)
137
138
  response = user.delete(sub_path, params=params)
138
139
  user.raise_for_status(response)
@@ -3,9 +3,10 @@ from __future__ import annotations
3
3
  from abc import abstractmethod, ABC
4
4
  from weakref import WeakValueDictionary
5
5
 
6
- from sapiopycommons.files.file_bridge import FileBridge
7
6
  from sapiopylib.rest.User import SapioUser
8
- from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
7
+
8
+ from sapiopycommons.files.file_bridge import FileBridge
9
+ from sapiopycommons.general.aliases import AliasUtil, UserIdentifier
9
10
 
10
11
 
11
12
  class FileBridgeHandler:
@@ -27,11 +28,11 @@ class FileBridgeHandler:
27
28
  __instances: WeakValueDictionary[str, FileBridgeHandler] = WeakValueDictionary()
28
29
  __initialized: bool
29
30
 
30
- def __new__(cls, context: SapioWebhookContext | SapioUser, bridge_name: str):
31
+ def __new__(cls, context: UserIdentifier, bridge_name: str):
31
32
  """
32
33
  :param context: The current webhook context or a user object to send requests from.
33
34
  """
34
- user = context if isinstance(context, SapioUser) else context.user
35
+ user = AliasUtil.to_sapio_user(context)
35
36
  key = f"{user.__hash__()}:{bridge_name}"
36
37
  obj = cls.__instances.get(key)
37
38
  if not obj:
@@ -40,7 +41,7 @@ class FileBridgeHandler:
40
41
  cls.__instances[key] = obj
41
42
  return obj
42
43
 
43
- def __init__(self, context: SapioWebhookContext | SapioUser, bridge_name: str):
44
+ def __init__(self, context: UserIdentifier, bridge_name: str):
44
45
  """
45
46
  :param context: The current webhook context or a user object to send requests from.
46
47
  :param bridge_name: The name of the bridge to communicate with. This is the "connection name" in the
@@ -50,7 +51,7 @@ class FileBridgeHandler:
50
51
  return
51
52
  self.__initialized = True
52
53
 
53
- self.user = context if isinstance(context, SapioUser) else context.user
54
+ self.user = AliasUtil.to_sapio_user(context)
54
55
  self.__bridge = bridge_name
55
56
  self.__file_cache = {}
56
57
  self.__files = {}
@@ -327,7 +328,7 @@ class Directory(FileBridgeObject):
327
328
  return {x: y for x, y in self.contents.items() if not y.is_file()}
328
329
 
329
330
 
330
- def split_path(file_path: str) -> (str, str):
331
+ def split_path(file_path: str) -> tuple[str, str]:
331
332
  """
332
333
  :param file_path: A file path where directories are separated the "/" characters.
333
334
  :return: A tuple of two strings that splits the path on its last slash. The first string is the name of the
@@ -1,13 +1,10 @@
1
1
  import re
2
2
  from typing import Any, Callable, Iterable
3
3
 
4
- from sapiopycommons.general.exceptions import SapioException
5
-
6
- from sapiopycommons.recordmodel.record_handler import RecordHandler
7
-
8
4
  from sapiopycommons.general.aliases import SapioRecord
9
-
5
+ from sapiopycommons.general.exceptions import SapioException
10
6
  from sapiopycommons.general.time_util import TimeUtil
7
+ from sapiopycommons.recordmodel.record_handler import RecordHandler
11
8
 
12
9
  FilterList = Iterable[int] | range | Callable[[int, dict[str, Any]], bool] | None
13
10
  """A FilterList is an object used to determine if a row in the file data should be skipped over. This can take the
@@ -7,10 +7,10 @@ from sapiopylib.rest.User import SapioUser
7
7
  from sapiopylib.rest.pojo.CustomReport import RawReportTerm, RawTermOperation
8
8
  from sapiopylib.rest.pojo.datatype.FieldDefinition import VeloxIntegerFieldDefinition, VeloxStringFieldDefinition, \
9
9
  AbstractVeloxFieldDefinition
10
- from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
11
10
 
12
11
  from sapiopycommons.callbacks.callback_util import CallbackUtil
13
12
  from sapiopycommons.files.file_data_handler import FileDataHandler, FilterList
13
+ from sapiopycommons.general.aliases import UserIdentifier, AliasUtil
14
14
  from sapiopycommons.general.custom_report_util import CustomReportUtil
15
15
  from sapiopycommons.general.exceptions import SapioUserCancelledException
16
16
  from sapiopycommons.general.time_util import TimeUtil
@@ -80,7 +80,7 @@ class FileValidator:
80
80
 
81
81
  return failed_rows
82
82
 
83
- def build_violation_report(self, context: SapioWebhookContext | SapioUser,
83
+ def build_violation_report(self, context: UserIdentifier,
84
84
  rule_violations: dict[int, list[ValidationRule]]) -> None:
85
85
  """
86
86
  Display a simple report of any rule violations in the file to the user as a table dialog.
@@ -125,7 +125,7 @@ class FileValidator:
125
125
  callback.table_dialog("Errors", "The following rule violations were encountered in the provided file.",
126
126
  columns, rows)
127
127
 
128
- def validate_and_report_errors(self, context: SapioWebhookContext | SapioUser) -> None:
128
+ def validate_and_report_errors(self, context: UserIdentifier) -> None:
129
129
  """
130
130
  Validate the file. If any rule violations are found, display a simple report of any rule violations in the file
131
131
  to the user as a table dialog and throw a SapioUserCancelled exception after the user acknowledges the dialog
@@ -509,7 +509,7 @@ class UniqueSystemValueRule(ColumnRule):
509
509
  data_type_name: str
510
510
  data_field_name: str
511
511
 
512
- def __init__(self, context: SapioWebhookContext | SapioUser, header: str, data_type_name: str,
512
+ def __init__(self, context: UserIdentifier, header: str, data_type_name: str,
513
513
  data_field_name: str):
514
514
  """
515
515
  :param context: The current webhook context or a user object to send requests from.
@@ -517,7 +517,7 @@ class UniqueSystemValueRule(ColumnRule):
517
517
  :param data_type_name: The data type name to search on.
518
518
  :param data_field_name: The data field name to search on. This is expected to be a string field.
519
519
  """
520
- self.user = context.user if isinstance(context, SapioWebhookContext) else context
520
+ self.user = AliasUtil.to_sapio_user(context)
521
521
  self.data_type_name = data_type_name
522
522
  self.data_field_name = data_field_name
523
523
  super().__init__(header, f"This value already exists in the system.")
@@ -543,7 +543,7 @@ class ExistingSystemValueRule(ColumnRule):
543
543
  data_type_name: str
544
544
  data_field_name: str
545
545
 
546
- def __init__(self, context: SapioWebhookContext | SapioUser, header: str, data_type_name: str,
546
+ def __init__(self, context: UserIdentifier, header: str, data_type_name: str,
547
547
  data_field_name: str):
548
548
  """
549
549
  :param context: The current webhook context or a user object to send requests from.
@@ -551,7 +551,7 @@ class ExistingSystemValueRule(ColumnRule):
551
551
  :param data_type_name: The data type name to search on.
552
552
  :param data_field_name: The data field name to search on. This is expected to be a string field.
553
553
  """
554
- self.user = context.user if isinstance(context, SapioWebhookContext) else context
554
+ self.user = AliasUtil.to_sapio_user(context)
555
555
  self.data_type_name = data_type_name
556
556
  self.data_field_name = data_field_name
557
557
  super().__init__(header, f"This value doesn't exist in the system.")
@@ -1,15 +1,19 @@
1
1
  from collections.abc import Iterable
2
2
  from typing import Any
3
3
 
4
+ from sapiopylib.rest.User import SapioUser
4
5
  from sapiopylib.rest.pojo.DataRecord import DataRecord
5
6
  from sapiopylib.rest.pojo.datatype.FieldDefinition import FieldType
6
7
  from sapiopylib.rest.pojo.eln.ElnExperiment import ElnExperiment
8
+ from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
7
9
  from sapiopylib.rest.utils.Protocols import ElnExperimentProtocol
8
10
  from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
9
11
  from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedRecordModel, WrappedType, WrapperField
10
12
 
11
13
  from sapiopycommons.general.exceptions import SapioException
12
14
 
15
+ FieldValue = int | float | str | bool | None
16
+ """Allowable values for fields in the system."""
13
17
  RecordModel = PyRecordModel | WrappedRecordModel | WrappedType
14
18
  """Different forms that a record model could take."""
15
19
  SapioRecord = DataRecord | RecordModel
@@ -21,14 +25,23 @@ DataTypeIdentifier = SapioRecord | type[WrappedType] | str
21
25
  FieldIdentifier = WrapperField | str | tuple[str, FieldType]
22
26
  """A FieldIdentifier is either wrapper field from a record model wrapper, a string, or a tuple of string
23
27
  and field type."""
28
+ FieldIdentifierKey = WrapperField | str
29
+ """A FieldIdentifierKey is a FieldIdentifier, except it can't be a tuple, s tuples can't be used as keys in
30
+ dictionaries.."""
24
31
  HasFieldWrappers = type[WrappedType] | WrappedRecordModel
25
32
  """An identifier for classes that have wrapper fields."""
26
33
  ExperimentIdentifier = ElnExperimentProtocol | ElnExperiment | int
27
34
  """An ExperimentIdentifier is either an experiment protocol, experiment, or an integer for te experiment's notebook
28
35
  ID."""
29
- FieldMap = dict[str, Any]
36
+ FieldMap = dict[str, FieldValue]
30
37
  """A field map is simply a dict of data field names to values. The purpose of aliasing this is to help distinguish
31
38
  any random dict in a webhook from one which is explicitly used for record fields."""
39
+ FieldIdentifierMap = dict[FieldIdentifierKey, FieldValue]
40
+ """A field identifier map is the same thing as a field map, except the keys can be field identifiers instead
41
+ of just strings. Note that although one of the allowed field identifiers is a tuple, you can't use tuples as
42
+ keys in a dictionary."""
43
+ UserIdentifier = SapioWebhookContext | SapioUser
44
+ """An identifier for classes from which a user object can be used for sending requests."""
32
45
 
33
46
 
34
47
  # FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
@@ -98,6 +111,21 @@ class AliasUtil:
98
111
  values = [AliasUtil.to_data_type_name(x) for x in values]
99
112
  return set(values) if return_set else values
100
113
 
114
+ @staticmethod
115
+ def to_singular_data_type_name(values: Iterable[DataTypeIdentifier]) -> str:
116
+ """
117
+ Convert a given iterable of values to a singular data type name that they share. Throws an exception if more
118
+ than one data type name exists in the provided list of identifiers.
119
+
120
+ :param values: An iterable of values which are strings, records, or record model types.
121
+ :return: The single data type name that the input vales share.
122
+ """
123
+ data_types: set[str] = AliasUtil.to_data_type_names(values, True)
124
+ if len(data_types) > 1:
125
+ raise SapioException(f"Provided values contain multiple data types: {data_types}. "
126
+ f"Only expecting a single data type.")
127
+ return data_types.pop()
128
+
101
129
  @staticmethod
102
130
  def to_data_field_name(value: FieldIdentifier) -> str:
103
131
  """
@@ -122,6 +150,26 @@ class AliasUtil:
122
150
  """
123
151
  return [AliasUtil.to_data_field_name(x) for x in values]
124
152
 
153
+ @staticmethod
154
+ def to_data_field_names_dict(values: dict[FieldIdentifierKey, Any]) -> dict[str, Any]:
155
+ """
156
+ Take a dictionary whose keys are field identifiers and convert them all to strings for the data field name.
157
+
158
+ :param values: A dictionary of field identifiers to field values.
159
+ :return: A dictionary of strings of the data field names to field values for the input values.
160
+ """
161
+ ret_dict: dict[str, FieldValue] = {}
162
+ for field, value in values.items():
163
+ ret_dict[AliasUtil.to_data_field_name(field)] = value
164
+ return ret_dict
165
+
166
+ @staticmethod
167
+ def to_data_field_names_list_dict(values: list[dict[FieldIdentifierKey, Any]]) -> list[dict[str, Any]]:
168
+ ret_list: list[dict[str, Any]] = []
169
+ for field_map in values:
170
+ ret_list.append(AliasUtil.to_data_field_names_dict(field_map))
171
+ return ret_list
172
+
125
173
  @staticmethod
126
174
  def to_field_type(field: FieldIdentifier, data_type: HasFieldWrappers | None = None) -> FieldType:
127
175
  """
@@ -154,6 +202,7 @@ class AliasUtil:
154
202
  field_map_list: list[FieldMap] = []
155
203
  for record in records:
156
204
  if isinstance(record, DataRecord):
205
+ # noinspection PyTypeChecker
157
206
  field_map_list.append(record.get_fields())
158
207
  else:
159
208
  field_map_list.append(record.fields.copy_to_dict())
@@ -171,3 +220,7 @@ class AliasUtil:
171
220
  if isinstance(experiment, ElnExperiment):
172
221
  return experiment.notebook_experiment_id
173
222
  return experiment.get_id()
223
+
224
+ @staticmethod
225
+ def to_sapio_user(context: UserIdentifier) -> SapioUser:
226
+ return context if isinstance(context, SapioUser) else context.user
@@ -1,14 +1,11 @@
1
1
  from enum import Enum
2
- from typing import Any
3
2
 
4
- from sapiopylib.rest.DataRecordManagerService import DataRecordManager
5
3
  from sapiopylib.rest.User import SapioUser
6
- from sapiopylib.rest.pojo.CustomReport import ReportColumn, RawReportTerm, CustomReportCriteria, RawTermOperation, \
7
- CompositeReportTerm, CompositeTermOperation
4
+ from sapiopylib.rest.pojo.CustomReport import ReportColumn, CustomReportCriteria
8
5
  from sapiopylib.rest.pojo.datatype.FieldDefinition import FieldType
9
- from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
10
6
 
11
- from sapiopycommons.general.aliases import RecordIdentifier, AliasUtil
7
+ from sapiopycommons.customreport.term_builder import TermBuilder
8
+ from sapiopycommons.general.aliases import RecordIdentifier, AliasUtil, UserIdentifier, FieldIdentifier, FieldValue
12
9
  from sapiopycommons.general.custom_report_util import CustomReportUtil
13
10
 
14
11
  EVENTTYPE_COLUMN = "EVENTTYPE"
@@ -99,7 +96,7 @@ class AuditLogEntry:
99
96
  def new_value(self) -> str:
100
97
  return self.__new_value
101
98
 
102
- def __init__(self, report_row: dict[str, Any]):
99
+ def __init__(self, report_row: dict[str, FieldValue]):
103
100
  self.__event_type = EventType((report_row[EVENTTYPE_COLUMN]))
104
101
  self.__date = report_row[TIMESTAMP_COLUMN]
105
102
  self.__data_type_name = report_row[DATATYPENAME_COLUMN]
@@ -131,11 +128,12 @@ class AuditLog:
131
128
  DATA_RECORD_NAME, DATA_FIELD_NAME, ORIGINAL_VALUE, NEW_VALUE]
132
129
  user: SapioUser
133
130
 
134
- def __init__(self, context: SapioWebhookContext | SapioUser):
135
- self.user = context if isinstance(context, SapioUser) else context.user
131
+ def __init__(self, context: UserIdentifier):
132
+ self.user = AliasUtil.to_sapio_user(context)
136
133
 
137
134
  @staticmethod
138
- def create_data_record_audit_log_report(records: list[RecordIdentifier], fields: list[str] | None = None) -> CustomReportCriteria:
135
+ def create_data_record_audit_log_report(records: list[RecordIdentifier],
136
+ fields: list[FieldIdentifier] | None = None) -> CustomReportCriteria:
139
137
  """
140
138
  This method creates a CustomReportCriteria object for running an audit log query based on data records.
141
139
 
@@ -147,25 +145,22 @@ class AuditLog:
147
145
  :param fields: The data field names to include changes for.
148
146
  :return: The constructed CustomReportCriteria object, which can be used to run a report on the audit log.
149
147
  """
150
- # We need to compile the record ids from these record identifiers as "str" variables, so they can be
151
- # concatenated.
148
+ # Build the raw report term querying for any entry with a matching record ID value to the record ID's
149
+ # passed in.
152
150
  record_ids = AliasUtil.to_record_ids(records)
153
- id_strs = [str(id_int) for id_int in record_ids]
154
-
155
- # Next we'll build the raw report term querying for any entry with a matching record id value to the record ID's
156
- # passed in
157
- root_term = RawReportTerm(AuditLog.AUDIT_LOG_PSEUDO_DATATYPE, RECORDID_COLUMN,
158
- RawTermOperation.EQUAL_TO_OPERATOR, "{" + ",".join(id_strs) + "}")
151
+ root_term = TermBuilder.is_term(AuditLog.AUDIT_LOG_PSEUDO_DATATYPE, RECORDID_COLUMN, record_ids)
159
152
 
160
153
  # If the user passed in any specific fields, then we should limit the query to those fields.
161
154
  if fields:
162
- field_term = RawReportTerm(AuditLog.AUDIT_LOG_PSEUDO_DATATYPE, DATAFIELDNAME_COLUMN,
163
- RawTermOperation.EQUAL_TO_OPERATOR, "{" + ",".join(fields) + "}")
164
- root_term = CompositeReportTerm(root_term, CompositeTermOperation.AND_OPERATOR, field_term)
155
+ fields: list[str] = AliasUtil.to_data_field_names(fields)
156
+ field_term = TermBuilder.is_term(AuditLog.AUDIT_LOG_PSEUDO_DATATYPE, DATAFIELDNAME_COLUMN, fields)
157
+ root_term = TermBuilder.and_terms(root_term, field_term)
165
158
 
166
159
  return CustomReportCriteria(AuditLog.AUDIT_LOG_COLUMNS, root_term)
167
160
 
168
- def run_data_record_audit_log_report(self, records: list[RecordIdentifier], fields: list[str] | None = None) -> dict[RecordIdentifier, list[AuditLogEntry]]:
161
+ def run_data_record_audit_log_report(self, records: list[RecordIdentifier],
162
+ fields: list[FieldIdentifier] | None = None) \
163
+ -> dict[RecordIdentifier, list[AuditLogEntry]]:
169
164
  """
170
165
  This method runs a custom report for changes made to the given data records using the audit log.
171
166
  See "create_data_record_audit_log_report" for more details about the data record audit log report.
@@ -175,11 +170,12 @@ class AuditLog:
175
170
  :return: A dictionary where the keys are the record identifiers passed in, and the values are a list of
176
171
  AuditLogEntry objects which match the record id value of those records.
177
172
  """
173
+ fields: list[str] = AliasUtil.to_data_field_names(fields)
178
174
  # First, we must build our report criteria for running the Custom Report.
179
175
  criteria = AuditLog.create_data_record_audit_log_report(records, fields)
180
176
 
181
177
  # Then we must run the custom report using that criteria.
182
- raw_report_data: list[dict[str, Any]] = CustomReportUtil.run_custom_report(self.user, criteria)
178
+ raw_report_data: list[dict[str, FieldValue]] = CustomReportUtil.run_custom_report(self.user, criteria)
183
179
 
184
180
  # This section will prepare a map matching the original RecordIdentifier by record id.
185
181
  # This is because the audit log entries will have record ids, but we want the keys in our result map
@@ -1,21 +1,21 @@
1
1
  from collections.abc import Iterable
2
- from typing import Any
3
2
 
4
3
  from sapiopylib.rest.DataMgmtService import DataMgmtServer
5
4
  from sapiopylib.rest.User import SapioUser
6
5
  from sapiopylib.rest.pojo.CustomReport import ReportColumn, CustomReport, CustomReportCriteria, RawReportTerm
7
- from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
6
+
7
+ from sapiopycommons.general.aliases import UserIdentifier, FieldValue, AliasUtil, FieldIdentifierKey
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: SapioWebhookContext | SapioUser,
13
+ def run_system_report(context: UserIdentifier,
14
14
  report_name: str,
15
- filters: dict[str, Iterable[Any]] | None = None,
15
+ filters: dict[FieldIdentifierKey, Iterable[FieldValue]] | None = None,
16
16
  page_limit: int | None = None,
17
17
  page_size: int | None = None,
18
- page_number: int | None = None) -> list[dict[str, Any]]:
18
+ page_number: int | None = None) -> list[dict[str, FieldValue]]:
19
19
  """
20
20
  Run a system report and return the results of that report as a list of dictionaries for the values of each
21
21
  column in each row.
@@ -41,16 +41,16 @@ class CustomReportUtil:
41
41
  results: tuple = CustomReportUtil.__exhaust_system_report(context, report_name, page_limit,
42
42
  page_size, page_number)
43
43
  columns: list[ReportColumn] = results[0]
44
- rows: list[list[Any]] = results[1]
44
+ rows: list[list[FieldValue]] = results[1]
45
45
  return CustomReportUtil.__process_results(rows, columns, filters)
46
46
 
47
47
  @staticmethod
48
- def run_custom_report(context: SapioWebhookContext | SapioUser,
48
+ def run_custom_report(context: UserIdentifier,
49
49
  report_criteria: CustomReportCriteria,
50
- filters: dict[str, Iterable[Any]] | None = None,
50
+ filters: dict[FieldIdentifierKey, Iterable[FieldValue]] | None = None,
51
51
  page_limit: int | None = None,
52
52
  page_size: int | None = None,
53
- page_number: int | None = None) -> list[dict[str, Any]]:
53
+ page_number: int | None = None) -> list[dict[str, FieldValue]]:
54
54
  """
55
55
  Run a custom report and return the results of that report as a list of dictionaries for the values of each
56
56
  column in each row.
@@ -82,16 +82,16 @@ class CustomReportUtil:
82
82
  results: tuple = CustomReportUtil.__exhaust_custom_report(context, report_criteria, page_limit,
83
83
  page_size, page_number)
84
84
  columns: list[ReportColumn] = results[0]
85
- rows: list[list[Any]] = results[1]
85
+ rows: list[list[FieldValue]] = results[1]
86
86
  return CustomReportUtil.__process_results(rows, columns, filters)
87
87
 
88
88
  @staticmethod
89
- def run_quick_report(context: SapioWebhookContext | SapioUser,
89
+ def run_quick_report(context: UserIdentifier,
90
90
  report_term: RawReportTerm,
91
- filters: dict[str, Iterable[Any]] | None = None,
91
+ filters: dict[FieldIdentifierKey, Iterable[FieldValue]] | None = None,
92
92
  page_limit: int | None = None,
93
93
  page_size: int | None = None,
94
- page_number: int | None = None) -> list[dict[str, Any]]:
94
+ page_number: int | None = None) -> list[dict[str, FieldValue]]:
95
95
  """
96
96
  Run a quick report and return the results of that report as a list of dictionaries for the values of each
97
97
  column in each row.
@@ -115,11 +115,11 @@ class CustomReportUtil:
115
115
  results: tuple = CustomReportUtil.__exhaust_quick_report(context, report_term, page_limit,
116
116
  page_size, page_number)
117
117
  columns: list[ReportColumn] = results[0]
118
- rows: list[list[Any]] = results[1]
118
+ rows: list[list[FieldValue]] = results[1]
119
119
  return CustomReportUtil.__process_results(rows, columns, filters)
120
120
 
121
121
  @staticmethod
122
- def get_system_report_criteria(context: SapioWebhookContext | SapioUser, report_name: str) -> CustomReport:
122
+ def get_system_report_criteria(context: UserIdentifier, report_name: str) -> CustomReport:
123
123
  """
124
124
  Retrieve a custom report from the system given the name of the report. This works by querying the system report
125
125
  with a page number and size of 1 to minimize the amount of data transfer needed to retrieve the report's config.
@@ -134,27 +134,27 @@ class CustomReportUtil:
134
134
  :param report_name: The name of the system report to run.
135
135
  :return: The CustomReport object for the given system report name.
136
136
  """
137
- user: SapioUser = context if isinstance(context, SapioUser) else context.user
137
+ user: SapioUser = AliasUtil.to_sapio_user(context)
138
138
  report_man = DataMgmtServer.get_custom_report_manager(user)
139
139
  return report_man.run_system_report_by_name(report_name, 1, 1)
140
140
 
141
141
  @staticmethod
142
- def __exhaust_system_report(context: SapioWebhookContext | SapioUser,
142
+ def __exhaust_system_report(context: UserIdentifier,
143
143
  report_name: str,
144
144
  page_limit: int | None,
145
145
  page_size: int | None,
146
146
  page_number: int | None) \
147
- -> tuple[list[ReportColumn], list[list[Any]]]:
147
+ -> tuple[list[ReportColumn], list[list[FieldValue]]]:
148
148
  """
149
149
  Given a system report, iterate over every page of the report and collect the results
150
150
  until there are no remaining pages.
151
151
  """
152
- user: SapioUser = context if isinstance(context, SapioUser) else context.user
152
+ user: SapioUser = AliasUtil.to_sapio_user(context)
153
153
  report_man = DataMgmtServer.get_custom_report_manager(user)
154
154
 
155
155
  result = None
156
156
  has_next_page: bool = True
157
- rows: list[list[Any]] = []
157
+ rows: list[list[FieldValue]] = []
158
158
  cur_page: int = 1
159
159
  while has_next_page and (not page_limit or cur_page <= page_limit):
160
160
  result = report_man.run_system_report_by_name(report_name, page_size, page_number)
@@ -166,17 +166,17 @@ class CustomReportUtil:
166
166
  return result.column_list, rows
167
167
 
168
168
  @staticmethod
169
- def __exhaust_custom_report(context: SapioWebhookContext | SapioUser,
169
+ def __exhaust_custom_report(context: UserIdentifier,
170
170
  report: CustomReportCriteria,
171
171
  page_limit: int | None,
172
172
  page_size: int | None,
173
173
  page_number: int | None) \
174
- -> tuple[list[ReportColumn], list[list[Any]]]:
174
+ -> tuple[list[ReportColumn], list[list[FieldValue]]]:
175
175
  """
176
176
  Given a custom report, iterate over every page of the report and collect the results
177
177
  until there are no remaining pages.
178
178
  """
179
- user: SapioUser = context if isinstance(context, SapioUser) else context.user
179
+ user: SapioUser = AliasUtil.to_sapio_user(context)
180
180
  report_man = DataMgmtServer.get_custom_report_manager(user)
181
181
 
182
182
  result = None
@@ -185,7 +185,7 @@ class CustomReportUtil:
185
185
  if page_number is not None:
186
186
  report.page_number = page_number
187
187
  has_next_page: bool = True
188
- rows: list[list[Any]] = []
188
+ rows: list[list[FieldValue]] = []
189
189
  cur_page: int = 1
190
190
  while has_next_page and (not page_limit or cur_page <= page_limit):
191
191
  result = report_man.run_custom_report(report)
@@ -197,22 +197,22 @@ class CustomReportUtil:
197
197
  return result.column_list, rows
198
198
 
199
199
  @staticmethod
200
- def __exhaust_quick_report(context: SapioWebhookContext | SapioUser,
200
+ def __exhaust_quick_report(context: UserIdentifier,
201
201
  report_term: RawReportTerm,
202
202
  page_limit: int | None,
203
203
  page_size: int | None,
204
204
  page_number: int | None) \
205
- -> tuple[list[ReportColumn], list[list[Any]]]:
205
+ -> tuple[list[ReportColumn], list[list[FieldValue]]]:
206
206
  """
207
207
  Given a quick report, iterate over every page of the report and collect the results
208
208
  until there are no remaining pages.
209
209
  """
210
- user: SapioUser = context if isinstance(context, SapioUser) else context.user
210
+ user: SapioUser = AliasUtil.to_sapio_user(context)
211
211
  report_man = DataMgmtServer.get_custom_report_manager(user)
212
212
 
213
213
  result = None
214
214
  has_next_page: bool = True
215
- rows: list[list[Any]] = []
215
+ rows: list[list[FieldValue]] = []
216
216
  cur_page: int = 1
217
217
  while has_next_page and (not page_limit or cur_page <= page_limit):
218
218
  result = report_man.run_quick_report(report_term, page_size, page_number)
@@ -224,8 +224,8 @@ class CustomReportUtil:
224
224
  return result.column_list, rows
225
225
 
226
226
  @staticmethod
227
- def __process_results(rows: list[list[Any]], columns: list[ReportColumn],
228
- filters: dict[str, Iterable[Any]] | None) -> list[dict[str, Any]]:
227
+ def __process_results(rows: list[list[FieldValue]], columns: list[ReportColumn],
228
+ filters: dict[FieldIdentifierKey, Iterable[FieldValue]] | None) -> list[dict[str, FieldValue]]:
229
229
  """
230
230
  Given the results of a report as a list of row values and the report's columns, combine these lists to
231
231
  result in a singular list of dictionaries for each row in the results.
@@ -243,9 +243,11 @@ class CustomReportUtil:
243
243
  else:
244
244
  encountered_names.append(field_name)
245
245
 
246
- ret: list[dict[str, Any]] = []
246
+ filters: dict[str, Iterable[FieldValue]] = AliasUtil.to_data_field_names_dict(filters)
247
+
248
+ ret: list[dict[str, FieldValue]] = []
247
249
  for row in rows:
248
- row_data: dict[str, Any] = {}
250
+ row_data: dict[str, FieldValue] = {}
249
251
  filter_row: bool = False
250
252
  for value, column in zip(row, columns):
251
253
  header: str = column.data_field_name