sapiopycommons 2024.3.19a157__py3-none-any.whl → 2025.1.17a402__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 (52) hide show
  1. sapiopycommons/callbacks/__init__.py +0 -0
  2. sapiopycommons/callbacks/callback_util.py +2041 -0
  3. sapiopycommons/callbacks/field_builder.py +545 -0
  4. sapiopycommons/chem/IndigoMolecules.py +46 -1
  5. sapiopycommons/chem/Molecules.py +100 -21
  6. sapiopycommons/customreport/__init__.py +0 -0
  7. sapiopycommons/customreport/column_builder.py +60 -0
  8. sapiopycommons/customreport/custom_report_builder.py +137 -0
  9. sapiopycommons/customreport/term_builder.py +315 -0
  10. sapiopycommons/datatype/attachment_util.py +14 -15
  11. sapiopycommons/datatype/data_fields.py +61 -0
  12. sapiopycommons/datatype/pseudo_data_types.py +440 -0
  13. sapiopycommons/eln/experiment_handler.py +355 -91
  14. sapiopycommons/eln/experiment_report_util.py +649 -0
  15. sapiopycommons/eln/plate_designer.py +152 -0
  16. sapiopycommons/files/complex_data_loader.py +31 -0
  17. sapiopycommons/files/file_bridge.py +149 -25
  18. sapiopycommons/files/file_bridge_handler.py +555 -0
  19. sapiopycommons/files/file_data_handler.py +633 -0
  20. sapiopycommons/files/file_util.py +263 -163
  21. sapiopycommons/files/file_validator.py +569 -0
  22. sapiopycommons/files/file_writer.py +377 -0
  23. sapiopycommons/flowcyto/flow_cyto.py +77 -0
  24. sapiopycommons/flowcyto/flowcyto_data.py +75 -0
  25. sapiopycommons/general/accession_service.py +375 -0
  26. sapiopycommons/general/aliases.py +250 -15
  27. sapiopycommons/general/audit_log.py +185 -0
  28. sapiopycommons/general/custom_report_util.py +251 -31
  29. sapiopycommons/general/directive_util.py +86 -0
  30. sapiopycommons/general/exceptions.py +69 -7
  31. sapiopycommons/general/popup_util.py +59 -7
  32. sapiopycommons/general/sapio_links.py +50 -0
  33. sapiopycommons/general/storage_util.py +148 -0
  34. sapiopycommons/general/time_util.py +91 -7
  35. sapiopycommons/multimodal/multimodal.py +146 -0
  36. sapiopycommons/multimodal/multimodal_data.py +490 -0
  37. sapiopycommons/processtracking/__init__.py +0 -0
  38. sapiopycommons/processtracking/custom_workflow_handler.py +406 -0
  39. sapiopycommons/processtracking/endpoints.py +192 -0
  40. sapiopycommons/recordmodel/record_handler.py +621 -148
  41. sapiopycommons/rules/eln_rule_handler.py +87 -8
  42. sapiopycommons/rules/on_save_rule_handler.py +87 -12
  43. sapiopycommons/sftpconnect/__init__.py +0 -0
  44. sapiopycommons/sftpconnect/sftp_builder.py +70 -0
  45. sapiopycommons/webhook/webhook_context.py +39 -0
  46. sapiopycommons/webhook/webhook_handlers.py +614 -71
  47. sapiopycommons/webhook/webservice_handlers.py +317 -0
  48. {sapiopycommons-2024.3.19a157.dist-info → sapiopycommons-2025.1.17a402.dist-info}/METADATA +5 -4
  49. sapiopycommons-2025.1.17a402.dist-info/RECORD +60 -0
  50. {sapiopycommons-2024.3.19a157.dist-info → sapiopycommons-2025.1.17a402.dist-info}/WHEEL +1 -1
  51. sapiopycommons-2024.3.19a157.dist-info/RECORD +0 -28
  52. {sapiopycommons-2024.3.19a157.dist-info → sapiopycommons-2025.1.17a402.dist-info}/licenses/LICENSE +0 -0
@@ -1,8 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ from weakref import WeakValueDictionary
4
+
5
+ from sapiopylib.rest.User import SapioUser
1
6
  from sapiopylib.rest.pojo.DataRecord import DataRecord
7
+ from sapiopylib.rest.pojo.eln.SapioELNEnums import ElnBaseDataType
2
8
  from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
3
9
  from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelManager, RecordModelInstanceManager
4
10
  from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
5
11
 
12
+ from sapiopycommons.general.aliases import FieldMap, AliasUtil, DataTypeIdentifier
6
13
  from sapiopycommons.general.exceptions import SapioException
7
14
 
8
15
 
@@ -10,7 +17,6 @@ from sapiopycommons.general.exceptions import SapioException
10
17
  class ElnRuleHandler:
11
18
  """
12
19
  A class which helps with the parsing and navigation of the ELN rule result map of a webhook context.
13
- TODO: Add functionality around the VeloxRuleType of the rule results.
14
20
  """
15
21
  __context: SapioWebhookContext
16
22
  """The context that this handler is working from."""
@@ -24,8 +30,33 @@ class ElnRuleHandler:
24
30
  __entry_to_records: dict[str, dict[str, set[DataRecord]]]
25
31
  """A mapping of entry name to the sets of data records for that entry, each set of records being mapped by its
26
32
  data type."""
33
+ __field_maps: dict[str, dict[int, FieldMap]]
34
+ """A mapping of data type to the field maps from the context that match that data type. In order to prevent
35
+ duplicate field maps, each field map is in a dict keyed by the RecordId field in the field map, since field maps
36
+ are just dictionaries and dictionaries aren't hashable and therefore can't go in a set."""
37
+ __entry_to_field_maps: dict[str, dict[str, dict[int, FieldMap]]]
38
+ """A mapping of entry name to the lists of field maps for that entry, each grouping of field maps being mapped by
39
+ its data type."""
40
+
41
+ __instances: WeakValueDictionary[SapioUser, ElnRuleHandler] = WeakValueDictionary()
42
+ __initialized: bool
43
+
44
+ def __new__(cls, context: SapioWebhookContext):
45
+ if context.velox_eln_rule_result_map is None:
46
+ raise SapioException("No Velox ELN rule result map in context for ElnRuleHandler to parse.")
47
+ user = context if isinstance(context, SapioUser) else context.user
48
+ obj = cls.__instances.get(user)
49
+ if not obj:
50
+ obj = object.__new__(cls)
51
+ obj.__initialized = False
52
+ cls.__instances[user] = obj
53
+ return obj
27
54
 
28
55
  def __init__(self, context: SapioWebhookContext):
56
+ if self.__initialized:
57
+ return
58
+ self.__initialized = True
59
+
29
60
  if context.velox_eln_rule_result_map is None:
30
61
  raise SapioException("No Velox ELN rule result map in context for ElnRuleHandler to parse.")
31
62
  self.__context = context
@@ -55,12 +86,8 @@ class ElnRuleHandler:
55
86
  # Get the data type of this record. If this is an ELN type, ignore the digits.
56
87
  data_type: str = record.data_type_name
57
88
  # PR-46331: Ensure that all ELN types are converted to their base data type name.
58
- if data_type.startswith("ELNExperiment_"):
59
- data_type = "ELNExperiment"
60
- elif data_type.startswith("ELNExperimentDetail_"):
61
- data_type = "ELNExperimentDetail"
62
- elif data_type.startswith("ELNSampleDetail_"):
63
- data_type = "ELNSampleDetail"
89
+ if ElnBaseDataType.is_eln_type(data_type):
90
+ data_type = ElnBaseDataType.get_base_type(data_type).data_type_name
64
91
  # Update the list of records of this type that exist so far globally.
65
92
  self.__records.setdefault(data_type, set()).add(record)
66
93
  # Do the same for the list of records of this type for this specific entry.
@@ -68,7 +95,37 @@ class ElnRuleHandler:
68
95
  # Update the records for this entry.
69
96
  self.__entry_to_records.update({entry: entry_dict})
70
97
 
71
- def get_records(self, data_type: str, entry: str | None = None) -> list[DataRecord]:
98
+ self.__field_maps = {}
99
+ self.__entry_to_field_maps = {}
100
+ # Repeat the same thing for the field map results.
101
+ for entry, entry_results in self.__context.velox_eln_rule_field_map_result_map.items():
102
+ entry_dict: dict[str, dict[int, FieldMap]] = {}
103
+ for record_result in entry_results:
104
+ for result in record_result.velox_type_rule_field_map_result_list:
105
+ data_type: str = result.velox_type_pojo.data_type_name
106
+ if ElnBaseDataType.is_eln_type(data_type):
107
+ data_type = ElnBaseDataType.get_base_type(data_type).data_type_name
108
+ for field_map in result.field_map_list:
109
+ rec_id: int = field_map.get("RecordId")
110
+ self.__field_maps.setdefault(data_type, {}).update({rec_id: field_map})
111
+ entry_dict.setdefault(data_type, {}).update({rec_id: field_map})
112
+ self.__entry_to_field_maps.update({entry: entry_dict})
113
+
114
+ def get_entry_names(self) -> list[str]:
115
+ """
116
+ :return: A list of the entry names that may be used with the get_records and get_models functions. These are the
117
+ entries from the experiment that the records in the rule context originate from.
118
+ """
119
+ return list(self.__entry_to_records.keys())
120
+
121
+ def get_field_maps_entry_names(self) -> list[str]:
122
+ """
123
+ :return: A list of the entry names that may be used with the get_field_maps function. These are the
124
+ entries from the experiment that the field maps in the rule context originate from.
125
+ """
126
+ return list(self.__entry_to_field_maps.keys())
127
+
128
+ def get_records(self, data_type: DataTypeIdentifier, entry: str | None = None) -> list[DataRecord]:
72
129
  """
73
130
  Get records from the cached context with the given data type. Capable of being filtered to searching within
74
131
  the context of an entry name. If the given data type or entry does not exist in the context,
@@ -79,9 +136,31 @@ class ElnRuleHandler:
79
136
  type from every entry. If an entry is provided, but it does not exist in the context, returns an empty list.
80
137
  :return: The records from the context that match the input parameters.
81
138
  """
139
+ data_type: str = AliasUtil.to_data_type_name(data_type)
82
140
  records: dict[str, set[DataRecord]] = self.__entry_to_records.get(entry, {}) if entry else self.__records
83
141
  return list(records.get(data_type, []))
84
142
 
143
+ # FR-46701: Add functions to the rule handlers for accessing the field maps of inaccessible records in the context.
144
+ def get_field_maps(self, data_type: DataTypeIdentifier, entry: str | None = None) -> list[FieldMap]:
145
+ """
146
+ Get field maps from the cached context with the given data type. Capable of being filtered to searching within
147
+ the context of an entry name. If the given data type or entry does not exist in the context,
148
+ returns an empty list.
149
+
150
+ Field maps will only exist in the context if the data record that the fields are from is no longer accessible
151
+ to the user. This can occur because the data record was deleted, or because the user does not have access to the
152
+ record due to ACL.
153
+
154
+ :param data_type: The data type of the field maps to return.
155
+ :param entry: The name of the entry to grab the field maps from. If None, returns the field maps that match the
156
+ data type from every entry. If an entry is provided, but it does not exist in the context, returns an empty
157
+ list.
158
+ :return: The field maps from the context that match the input parameters.
159
+ """
160
+ data_type: str = AliasUtil.to_data_type_name(data_type)
161
+ field_maps: dict[str, dict[int, FieldMap]] = self.__entry_to_field_maps.get(entry, {}) if entry else self.__field_maps
162
+ return list(field_maps.get(data_type, {}).values())
163
+
85
164
  def get_models(self, wrapper_type: type[WrappedType], entry: str | None = None) -> list[WrappedType]:
86
165
  """
87
166
  Get record models from the cached context with the given data type. Capable of being filtered to searching
@@ -1,8 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ from weakref import WeakValueDictionary
4
+
5
+ from sapiopylib.rest.User import SapioUser
1
6
  from sapiopylib.rest.pojo.DataRecord import DataRecord
7
+ from sapiopylib.rest.pojo.eln.SapioELNEnums import ElnBaseDataType
2
8
  from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
3
9
  from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelManager, RecordModelInstanceManager
4
10
  from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
5
11
 
12
+ from sapiopycommons.general.aliases import FieldMap, DataTypeIdentifier, AliasUtil
6
13
  from sapiopycommons.general.exceptions import SapioException
7
14
 
8
15
 
@@ -10,7 +17,6 @@ from sapiopycommons.general.exceptions import SapioException
10
17
  class OnSaveRuleHandler:
11
18
  """
12
19
  A class which helps with the parsing and navigation of the on save rule result map of a webhook context.
13
- TODO: Add functionality around the VeloxRuleType of the rule results.
14
20
  """
15
21
  __context: SapioWebhookContext
16
22
  """The context that this handler is working from."""
@@ -24,8 +30,33 @@ class OnSaveRuleHandler:
24
30
  __base_id_to_records: dict[int, dict[str, set[DataRecord]]]
25
31
  """A mapping of record IDs of records in the context.data_record_list to the sets of data records related to that
26
32
  record, each set of records being mapped by its data type."""
33
+ __field_maps: dict[str, dict[int, FieldMap]]
34
+ """A mapping of data type to the field maps from the context that match that data type. In order to prevent
35
+ duplicate field maps, each field map is in a dict keyed by the RecordId field in the field map, since field maps
36
+ are just dictionaries and dictionaries aren't hashable and therefore can't go in a set."""
37
+ __base_id_to_field_maps: dict[int, dict[str, dict[int, FieldMap]]]
38
+ """A mapping of record IDs of records in the context.data_record_list to the field maps related to that
39
+ record, each grouping of field maps being mapped by its data type."""
40
+
41
+ __instances: WeakValueDictionary[SapioUser, OnSaveRuleHandler] = WeakValueDictionary()
42
+ __initialized: bool
43
+
44
+ def __new__(cls, context: SapioWebhookContext):
45
+ if context.velox_on_save_result_map is None:
46
+ raise SapioException("No Velox on save rule result map in context for OnSaveRuleHandler to parse.")
47
+ user = context if isinstance(context, SapioUser) else context.user
48
+ obj = cls.__instances.get(user)
49
+ if not obj:
50
+ obj = object.__new__(cls)
51
+ obj.__initialized = False
52
+ cls.__instances[user] = obj
53
+ return obj
27
54
 
28
55
  def __init__(self, context: SapioWebhookContext):
56
+ if self.__initialized:
57
+ return
58
+ self.__initialized = True
59
+
29
60
  if context.velox_on_save_result_map is None:
30
61
  raise SapioException("No Velox on save rule result map in context for OnSaveRuleHandler to parse.")
31
62
  self.__context = context
@@ -42,9 +73,6 @@ class OnSaveRuleHandler:
42
73
  self.__base_id_to_records = {}
43
74
  # Each record ID in the context has a list of results for that record.
44
75
  for record_id, rule_results in self.__context.velox_on_save_result_map.items():
45
- # TODO: Record IDs are currently being stored in the map as strings instead of ints. This can be removed
46
- # once sapiopylib is fixed.
47
- record_id = int(record_id)
48
76
  # Keep track of the records for this specific record ID.
49
77
  id_dict: dict[str, set[DataRecord]] = {}
50
78
  # The list of results for a record consist of a list of data records and a VeloxType that specifies
@@ -55,12 +83,8 @@ class OnSaveRuleHandler:
55
83
  # Get the data type of this record. If this is an ELN type, ignore the digits.
56
84
  data_type: str = record.data_type_name
57
85
  # PR-46331: Ensure that all ELN types are converted to their base data type name.
58
- if data_type.startswith("ELNExperiment_"):
59
- data_type = "ELNExperiment"
60
- elif data_type.startswith("ELNExperimentDetail_"):
61
- data_type = "ELNExperimentDetail"
62
- elif data_type.startswith("ELNSampleDetail_"):
63
- data_type = "ELNSampleDetail"
86
+ if ElnBaseDataType.is_eln_type(data_type):
87
+ data_type = ElnBaseDataType.get_base_type(data_type).data_type_name
64
88
  # Update the list of records of this type that exist so far globally.
65
89
  self.__records.setdefault(data_type, set()).add(record)
66
90
  # Do the same for the list of records of this type that relate to this record ID.
@@ -68,7 +92,36 @@ class OnSaveRuleHandler:
68
92
  # Update the related records for this record ID.
69
93
  self.__base_id_to_records.update({record_id: id_dict})
70
94
 
71
- def get_records(self, data_type: str, record_id: int | None = None) -> list[DataRecord]:
95
+ self.__field_maps = {}
96
+ self.__base_id_to_field_maps = {}
97
+ # Repeat the same thing for the field map results.
98
+ for record_id, rule_results in self.__context.velox_on_save_field_map_result_map.items():
99
+ id_dict: dict[str, dict[int, FieldMap]] = {}
100
+ for record_result in rule_results:
101
+ data_type: str = record_result.velox_type_pojo.data_type_name
102
+ if ElnBaseDataType.is_eln_type(data_type):
103
+ data_type = ElnBaseDataType.get_base_type(data_type).data_type_name
104
+ for field_map in record_result.field_map_list:
105
+ rec_id: int = field_map.get("RecordId")
106
+ self.__field_maps.setdefault(data_type, {}).update({rec_id: field_map})
107
+ id_dict.setdefault(data_type, {}).update({rec_id: field_map})
108
+ self.__base_id_to_field_maps.update({record_id: id_dict})
109
+
110
+ def get_base_record_ids(self) -> list[int]:
111
+ """
112
+ :return: A list of the record IDs that may be used with the get_records and get_models functions. These are the
113
+ record IDs to the records that caused the rule to trigger.
114
+ """
115
+ return list(self.__base_id_to_records.keys())
116
+
117
+ def get_field_maps_base_record_ids(self) -> list[int]:
118
+ """
119
+ :return: A list of the record IDs that may be used with the get_field_maps function. These are the
120
+ record IDs to the records that caused the rule to trigger.
121
+ """
122
+ return list(self.__base_id_to_field_maps.keys())
123
+
124
+ def get_records(self, data_type: DataTypeIdentifier, record_id: int | None = None) -> list[DataRecord]:
72
125
  """
73
126
  Get records from the cached context with the given data type. Capable of being filtered to searching within
74
127
  the context of a record ID. If the given data type or record ID does not exist in the context,
@@ -76,12 +129,34 @@ class OnSaveRuleHandler:
76
129
 
77
130
  :param data_type: The data type of the records to return.
78
131
  :param record_id: The record ID of the base record to search from. If None, returns the records that match the
79
- data type from ID. If an ID is provided, but it does not exist in the context, returns an empty list.
132
+ data type from every ID. If an ID is provided, but it does not exist in the context, returns an empty list.
80
133
  :return: The records from the context that match the input parameters.
81
134
  """
135
+ data_type: str = AliasUtil.to_data_type_name(data_type)
82
136
  records: dict[str, set[DataRecord]] = self.__base_id_to_records.get(record_id, {}) if record_id else self.__records
83
137
  return list(records.get(data_type, []))
84
138
 
139
+ # FR-46701: Add functions to the rule handlers for accessing the field maps of inaccessible records in the context.
140
+ def get_field_maps(self, data_type: DataTypeIdentifier, record_id: int | None = None) -> list[FieldMap]:
141
+ """
142
+ Get field maps from the cached context with the given data type. Capable of being filtered to searching within
143
+ the context of a record ID. If the given data type or record ID does not exist in the context,
144
+ returns an empty list.
145
+
146
+ Field maps will only exist in the context if the data record that the fields are from is no longer accessible
147
+ to the user. This can occur because the data record was deleted, or because the user does not have access to the
148
+ record due to ACL.
149
+
150
+ :param data_type: The data type of the field maps to return.
151
+ :param record_id: The record ID of the base record to search from. If None, returns the field maps that match
152
+ the data type from every ID. If an ID is provided, but it does not exist in the context, returns an empty
153
+ list.
154
+ :return: The field maps from the context that match the input parameters.
155
+ """
156
+ data_type: str = AliasUtil.to_data_type_name(data_type)
157
+ field_maps: dict[str, dict[int, FieldMap]] = self.__base_id_to_field_maps.get(record_id, {}) if record_id else self.__field_maps
158
+ return list(field_maps.get(data_type, {}).values())
159
+
85
160
  def get_models(self, wrapper_type: type[WrappedType], record_id: int | None = None) -> list[WrappedType]:
86
161
  """
87
162
  Get records from the cached context with the given data type. Capable of being filtered to searching within
File without changes
@@ -0,0 +1,70 @@
1
+ import io
2
+ from enum import Enum
3
+
4
+ import paramiko
5
+ from paramiko import pkey
6
+ from paramiko.sftp_client import SFTPClient
7
+
8
+ from sapiopycommons.general.exceptions import SapioException
9
+
10
+
11
+ class SFTPAuthMethod(Enum):
12
+ """
13
+ An enum being used to specify connection type to the target server.
14
+ """
15
+ PASSWORD = 0
16
+ """Connection is being done via Password."""
17
+ FILEPATH = 1
18
+ """Connection is being done using a private key file in the codebase."""
19
+ KEY_STRING = 2
20
+ """Connection is being done using a private key in string form."""
21
+
22
+
23
+ class SFTPBuilder:
24
+ """
25
+ A class for making SFTP connections.
26
+ """
27
+
28
+ @staticmethod
29
+ def open_sftp(username: str, host: str, port: int, authentication: str,
30
+ connection_type: SFTPAuthMethod = SFTPAuthMethod.PASSWORD) -> SFTPClient:
31
+ """
32
+ Builds a SFTP client from user input.
33
+
34
+ :param username: The username of the individual trying to connect to the target server.
35
+ :param host: The hostname/IP address of the target server.
36
+ :param port: The port number used to connect to the target server.
37
+ :param authentication: The string used to connect to the target server. This could hold a filepath, a password
38
+ or a private key in string form depending on the connection_type parameter.
39
+
40
+ If authentication is a private key string, they are generally formated like this:
41
+ -----BEGIN OPENSSH PRIVATE KEY-----\n
42
+ asdfh;hjadfh;jghajdg54646+5fasdfadlajklgajd'gj'ajg654564\n
43
+ asdkjfhj;kghj;ahj;wh41234hjadjkhhdsgadshjkdghjshdlsds468\n
44
+ ....
45
+
46
+ :param connection_type: This enum is used to specify how the connection to the target server is being made.
47
+ The options are:
48
+ (0) PASSWORD: This means that the authentication parameter contains a password that will be used to connect to the server
49
+ (1) FILEPATH: This means that the authentication parameter contains a filepath leading to a private key file stored in the codebase
50
+ (2) KEY_STRING: This means that the authentication parameter contains the private key in string form
51
+
52
+ """
53
+
54
+ client = paramiko.SSHClient()
55
+ client.set_missing_host_key_policy(paramiko.MissingHostKeyPolicy)
56
+
57
+ if connection_type == SFTPAuthMethod.FILEPATH:
58
+ client.connect(host, username=username, port=port, key_filename=authentication)
59
+ return client.open_sftp()
60
+
61
+ if connection_type == SFTPAuthMethod.KEY_STRING:
62
+ private_key: pkey = paramiko.RSAKey.from_private_key(io.StringIO(authentication))
63
+ client.connect(host, username=username, port=port, pkey=private_key)
64
+ return client.open_sftp()
65
+
66
+ if connection_type == SFTPAuthMethod.PASSWORD:
67
+ client.connect(host, username=username, password=authentication, port=port)
68
+ return client.open_sftp()
69
+
70
+ raise SapioException("The SFTPAuthMethod enumerator was not properly specified.")
@@ -0,0 +1,39 @@
1
+ import json
2
+ from typing import Any
3
+
4
+ from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
5
+
6
+
7
+ class CustomWebhookContext(SapioWebhookContext):
8
+ """
9
+ CustomWebhookContext is a wrapper for the default SapioWebhookContext to be used by custom invocation types to
10
+ convert the context_data that the server sends from a JSON string to usable parameters. This class works as a
11
+ middleman to cleanly convert a SapioWebhookContext object into a CustomWebhookContext by copying all the parameters
12
+ of the given context into the custom context.
13
+ """
14
+ def __init__(self, context: SapioWebhookContext):
15
+ # Use __dict__ so that we don't need t maintain this class due to future changes to SapioWebhookContext.
16
+ self.__dict__ = context.__dict__
17
+ super().__init__(self.user, self.end_point_type)
18
+
19
+
20
+ class ProcessQueueContext(CustomWebhookContext):
21
+ """
22
+ When a custom process queue endpoint is invoked, the context from the queue is sent in a context_data parameter
23
+ on the SapioWebhookContext object, stored as a JSON string. This class parses that JSON into fields for the
24
+ caller to make use of.
25
+ """
26
+ process_name: str
27
+ """The name of the process that the user invoked this webhook from."""
28
+ step_name: str
29
+ """The name of the step in the process that the user invoked this webhook from."""
30
+ process_queue_item_record_ids: list[int]
31
+ """The record IDs of the process queue items related to the records that were selected by the user when this
32
+ webhook was invoked."""
33
+
34
+ def __init__(self, context: SapioWebhookContext):
35
+ super().__init__(context)
36
+ context_data: dict[str, Any] = json.loads(self.context_data)
37
+ self.process_name = context_data["processName"]
38
+ self.step_name = context_data["workflowName"]
39
+ self.process_queue_item_record_ids = context_data["processQueueItemRecordIds"]