sapiopycommons 2024.8.2a301__py3-none-any.whl → 2024.8.15a304__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.

@@ -7,7 +7,6 @@ from sapiopylib.rest.pojo.CustomReport import CustomReportCriteria, RawReportTer
7
7
  from sapiopylib.rest.pojo.DataRecord import DataRecord
8
8
  from sapiopylib.rest.pojo.DataRecordPaging import DataRecordPojoPageCriteria
9
9
  from sapiopylib.rest.pojo.datatype.FieldDefinition import FieldType
10
- from sapiopylib.rest.pojo.eln.SapioELNEnums import ElnBaseDataType
11
10
  from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
12
11
  from sapiopylib.rest.utils.autopaging import QueryDataRecordsAutoPager, QueryDataRecordByIdListAutoPager, \
13
12
  QueryAllRecordsOfTypeAutoPager
@@ -17,7 +16,6 @@ from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelMana
17
16
  from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType, WrappedRecordModel
18
17
  from sapiopylib.rest.utils.recordmodel.RelationshipPath import RelationshipPath, RelationshipNode, \
19
18
  RelationshipNodeType
20
- from sapiopylib.rest.utils.recordmodel.ancestry import RecordModelAncestorManager
21
19
 
22
20
  from sapiopycommons.general.aliases import RecordModel, SapioRecord, FieldMap
23
21
  from sapiopycommons.general.custom_report_util import CustomReportUtil
@@ -34,7 +32,6 @@ class RecordHandler:
34
32
  rec_man: RecordModelManager
35
33
  inst_man: RecordModelInstanceManager
36
34
  rel_man: RecordModelRelationshipManager
37
- an_man: RecordModelAncestorManager
38
35
 
39
36
  def __init__(self, context: SapioWebhookContext | SapioUser):
40
37
  """
@@ -45,7 +42,6 @@ class RecordHandler:
45
42
  self.rec_man = RecordModelManager(self.user)
46
43
  self.inst_man = self.rec_man.instance_manager
47
44
  self.rel_man = self.rec_man.relationship_manager
48
- self.an_man = RecordModelAncestorManager(self.rec_man)
49
45
 
50
46
  def wrap_model(self, record: DataRecord, wrapper_type: type[WrappedType]) -> WrappedType:
51
47
  """
@@ -55,7 +51,6 @@ class RecordHandler:
55
51
  :param wrapper_type: The record model wrapper to use.
56
52
  :return: The record model for the input.
57
53
  """
58
- self.__verify_data_type([record], wrapper_type)
59
54
  return self.inst_man.add_existing_record_of_type(record, wrapper_type)
60
55
 
61
56
  def wrap_models(self, records: Iterable[DataRecord], wrapper_type: type[WrappedType]) -> list[WrappedType]:
@@ -66,7 +61,6 @@ class RecordHandler:
66
61
  :param wrapper_type: The record model wrapper to use.
67
62
  :return: The record models for the input.
68
63
  """
69
- self.__verify_data_type(records, wrapper_type)
70
64
  return self.inst_man.add_existing_records_of_type(list(records), wrapper_type)
71
65
 
72
66
  def query_models(self, wrapper_type: type[WrappedType], field: str, value_list: Iterable[Any],
@@ -837,6 +831,8 @@ class RecordHandler:
837
831
  path, if any. The hierarchy must be linear (1:1 relationship between data types at every step) and the
838
832
  relationship path must already be loaded.
839
833
 
834
+ Currently, the relationship path may only contain parent/child nodes.
835
+
840
836
  :param models: A list of record models.
841
837
  :param path: The relationship path to follow.
842
838
  :param wrapper_type: The record model wrapper to use.
@@ -847,44 +843,15 @@ class RecordHandler:
847
843
  # PR-46832: Update path traversal to account for changes to RelationshipPath in Sapiopylib.
848
844
  path: list[RelationshipNode] = path.path
849
845
  for model in models:
850
- current: PyRecordModel | None = model if isinstance(model, PyRecordModel) else model.backing_model
846
+ current: PyRecordModel = model if isinstance(model, PyRecordModel) else model.backing_model
851
847
  for node in path:
852
- data_type: str = node.data_type_name
853
- direction: RelationshipNodeType = node.direction
848
+ direction = node.direction
854
849
  if current is None:
855
850
  break
856
851
  if direction == RelationshipNodeType.CHILD:
857
- current = current.get_child_of_type(data_type)
852
+ current = current.get_child_of_type(node.data_type_name)
858
853
  elif direction == RelationshipNodeType.PARENT:
859
- current = current.get_parent_of_type(data_type)
860
- elif direction == RelationshipNodeType.ANCESTOR:
861
- ancestors: list[PyRecordModel] = list(self.an_man.get_ancestors_of_type(current, data_type))
862
- if not ancestors:
863
- current = None
864
- elif len(ancestors) > 1:
865
- raise SapioException(f"Hierarchy contains multiple ancestors of type {data_type}.")
866
- else:
867
- current = ancestors[0]
868
- elif direction == RelationshipNodeType.DESCENDANT:
869
- descendants: list[PyRecordModel] = list(self.an_man.get_descendant_of_type(current, data_type))
870
- if not descendants:
871
- current = None
872
- elif len(descendants) > 1:
873
- raise SapioException(f"Hierarchy contains multiple descendants of type {data_type}.")
874
- else:
875
- current = descendants[0]
876
- elif direction == RelationshipNodeType.FORWARD_SIDE_LINK:
877
- current = current.get_forward_side_link(node.data_field_name)
878
- elif direction == RelationshipNodeType.REVERSE_SIDE_LINK:
879
- field_name: str = node.data_field_name
880
- reverse_links: list[PyRecordModel] = current.get_reverse_side_link(field_name, data_type)
881
- if not reverse_links:
882
- current = None
883
- elif len(reverse_links) > 1:
884
- raise SapioException(f"Hierarchy contains multiple reverse links of type {data_type} on field "
885
- f"{field_name}.")
886
- else:
887
- current = reverse_links[0]
854
+ current = current.get_parent_of_type(node.data_type_name)
888
855
  else:
889
856
  raise SapioException("Unsupported path direction.")
890
857
  ret_dict.update({model: self.inst_man.wrap(current, wrapper_type) if current else None})
@@ -897,6 +864,8 @@ class RecordHandler:
897
864
  path, if any. The hierarchy may be non-linear (1:Many relationships between data types are allowed) and the
898
865
  relationship path must already be loaded.
899
866
 
867
+ Currently, the relationship path may only contain parent/child nodes.
868
+
900
869
  :param models: A list of record models.
901
870
  :param path: The relationship path to follow.
902
871
  :param wrapper_type: The record model wrapper to use.
@@ -911,23 +880,14 @@ class RecordHandler:
911
880
  next_search: set[PyRecordModel] = set()
912
881
  # Exhaust the records at each step in the path, then use those records for the next step.
913
882
  for node in path:
914
- data_type: str = node.data_type_name
915
- direction: RelationshipNodeType = node.direction
883
+ direction = node.direction
916
884
  if len(current_search) == 0:
917
885
  break
918
886
  for search in current_search:
919
887
  if direction == RelationshipNodeType.CHILD:
920
- next_search.update(search.get_children_of_type(data_type))
888
+ next_search.update(search.get_children_of_type(node.data_type_name))
921
889
  elif direction == RelationshipNodeType.PARENT:
922
- next_search.update(search.get_parents_of_type(data_type))
923
- elif direction == RelationshipNodeType.ANCESTOR:
924
- next_search.update(self.an_man.get_ancestors_of_type(search, data_type))
925
- elif direction == RelationshipNodeType.DESCENDANT:
926
- next_search.update(self.an_man.get_descendant_of_type(search, data_type))
927
- elif direction == RelationshipNodeType.FORWARD_SIDE_LINK:
928
- next_search.add(search.get_forward_side_link(node.data_field_name))
929
- elif direction == RelationshipNodeType.REVERSE_SIDE_LINK:
930
- next_search.update(search.get_reverse_side_link(node.data_field_name, data_type))
890
+ next_search.update(search.get_parents_of_type(node.data_type_name))
931
891
  else:
932
892
  raise SapioException("Unsupported path direction.")
933
893
  current_search = next_search
@@ -948,6 +908,8 @@ class RecordHandler:
948
908
  relationships (e.g. a sample which is aliquoted to a number of samples, then those aliquots are pooled back
949
909
  together into a single sample).
950
910
 
911
+ Currently, the relationship path may only contain parent/child nodes.
912
+
951
913
  :param models: A list of record models.
952
914
  :param path: The relationship path to follow.
953
915
  :param wrapper_type: The record model wrapper to use.
@@ -960,22 +922,13 @@ class RecordHandler:
960
922
  for model in models:
961
923
  current: list[PyRecordModel] = [model if isinstance(model, PyRecordModel) else model.backing_model]
962
924
  for node in path:
963
- data_type: str = node.data_type_name
964
- direction: RelationshipNodeType = node.direction
925
+ direction = node.direction
965
926
  if len(current) == 0:
966
927
  break
967
928
  if direction == RelationshipNodeType.CHILD:
968
- current = current[0].get_children_of_type(data_type)
929
+ current = current[0].get_children_of_type(node.data_type_name)
969
930
  elif direction == RelationshipNodeType.PARENT:
970
- current = current[0].get_parents_of_type(data_type)
971
- elif direction == RelationshipNodeType.ANCESTOR:
972
- current = list(self.an_man.get_ancestors_of_type(current[0], data_type))
973
- elif direction == RelationshipNodeType.DESCENDANT:
974
- current = list(self.an_man.get_descendant_of_type(current[0], data_type))
975
- elif direction == RelationshipNodeType.FORWARD_SIDE_LINK:
976
- current = [current[0].get_forward_side_link(node.data_field_name)]
977
- elif direction == RelationshipNodeType.REVERSE_SIDE_LINK:
978
- current = current[0].get_reverse_side_link(node.data_field_name, data_type)
931
+ current = current[0].get_parents_of_type(node.data_type_name)
979
932
  else:
980
933
  raise SapioException("Unsupported path direction.")
981
934
  ret_dict.update({model: self.inst_man.wrap(current[0], wrapper_type) if current else None})
@@ -1006,18 +959,3 @@ class RecordHandler:
1006
959
  f"encountered in system that matches all provided identifiers.")
1007
960
  unique_record = result
1008
961
  return unique_record
1009
-
1010
- @staticmethod
1011
- def __verify_data_type(records: Iterable[DataRecord], wrapper_type: type[WrappedType]) -> None:
1012
- """
1013
- Throw an exception if the data type of the given records and wrapper don't match.
1014
- """
1015
- model_type: str = wrapper_type.get_wrapper_data_type_name()
1016
- for record in records:
1017
- record_type: str = record.data_type_name
1018
- # Account for ELN data type records.
1019
- if ElnBaseDataType.is_eln_type(record_type):
1020
- record_type = ElnBaseDataType.get_base_type(record_type).data_type_name
1021
- if record_type != model_type:
1022
- raise SapioException(f"Data record of type {record_type} cannot be wrapped by the record model wrapper "
1023
- f"of type {model_type}")
@@ -1,5 +1,5 @@
1
1
  from sapiopylib.rest.pojo.DataRecord import DataRecord
2
- from sapiopylib.rest.pojo.eln.SapioELNEnums import ElnBaseDataType
2
+ from sapiopylib.rest.pojo.webhook.VeloxRules import VeloxRuleType, VeloxRuleParser
3
3
  from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
4
4
  from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelManager, RecordModelInstanceManager
5
5
  from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
@@ -12,6 +12,7 @@ from sapiopycommons.general.exceptions import SapioException
12
12
  class ElnRuleHandler:
13
13
  """
14
14
  A class which helps with the parsing and navigation of the ELN rule result map of a webhook context.
15
+ TODO: Add functionality around the VeloxRuleType of the rule results.
15
16
  """
16
17
  __context: SapioWebhookContext
17
18
  """The context that this handler is working from."""
@@ -63,8 +64,13 @@ class ElnRuleHandler:
63
64
  # Get the data type of this record. If this is an ELN type, ignore the digits.
64
65
  data_type: str = record.data_type_name
65
66
  # PR-46331: Ensure that all ELN types are converted to their base data type name.
66
- if ElnBaseDataType.is_eln_type(data_type):
67
- data_type = ElnBaseDataType.get_base_type(data_type).data_type_name
67
+ # TODO: Use ElnBaseDataType.is_eln_type when it is no longer bugged in sapiopylib.
68
+ if data_type.startswith("ELNExperiment_"):
69
+ data_type = "ELNExperiment"
70
+ elif data_type.startswith("ELNExperimentDetail_"):
71
+ data_type = "ELNExperimentDetail"
72
+ elif data_type.startswith("ELNSampleDetail_"):
73
+ data_type = "ELNSampleDetail"
68
74
  # Update the list of records of this type that exist so far globally.
69
75
  self.__records.setdefault(data_type, set()).add(record)
70
76
  # Do the same for the list of records of this type for this specific entry.
@@ -79,9 +85,19 @@ class ElnRuleHandler:
79
85
  entry_dict: dict[str, dict[int, FieldMap]] = {}
80
86
  for record_result in entry_results:
81
87
  for result in record_result.velox_type_rule_field_map_result_list:
82
- data_type: str = result.velox_type_pojo.data_type_name
83
- if ElnBaseDataType.is_eln_type(data_type):
84
- data_type = ElnBaseDataType.get_base_type(data_type).data_type_name
88
+ # TODO: sapiopylib currently has a bug where this velox_type_pojo variable is stored as a dict instead
89
+ # of as the intended VeloxRuleType object. Parse that dict as a VeloxRuleType before use.
90
+ velox_type: VeloxRuleType | dict = result.velox_type_pojo
91
+ if isinstance(velox_type, dict):
92
+ velox_type: VeloxRuleType = VeloxRuleParser.parse_velox_rule_type(velox_type)
93
+ data_type: str = velox_type.data_type_name
94
+ # TODO: Use ElnBaseDataType.is_eln_type when it is no longer bugged in sapiopylib.
95
+ if data_type.startswith("ELNExperiment_"):
96
+ data_type = "ELNExperiment"
97
+ elif data_type.startswith("ELNExperimentDetail_"):
98
+ data_type = "ELNExperimentDetail"
99
+ elif data_type.startswith("ELNSampleDetail_"):
100
+ data_type = "ELNSampleDetail"
85
101
  for field_map in result.field_map_list:
86
102
  rec_id: int = field_map.get("RecordId")
87
103
  self.__field_maps.setdefault(data_type, {}).update({rec_id: field_map})
@@ -1,5 +1,5 @@
1
1
  from sapiopylib.rest.pojo.DataRecord import DataRecord
2
- from sapiopylib.rest.pojo.eln.SapioELNEnums import ElnBaseDataType
2
+ from sapiopylib.rest.pojo.webhook.VeloxRules import VeloxRuleType, VeloxRuleParser
3
3
  from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
4
4
  from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelManager, RecordModelInstanceManager
5
5
  from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
@@ -12,6 +12,7 @@ from sapiopycommons.general.exceptions import SapioException
12
12
  class OnSaveRuleHandler:
13
13
  """
14
14
  A class which helps with the parsing and navigation of the on save rule result map of a webhook context.
15
+ TODO: Add functionality around the VeloxRuleType of the rule results.
15
16
  """
16
17
  __context: SapioWebhookContext
17
18
  """The context that this handler is working from."""
@@ -50,6 +51,9 @@ class OnSaveRuleHandler:
50
51
  self.__base_id_to_records = {}
51
52
  # Each record ID in the context has a list of results for that record.
52
53
  for record_id, rule_results in self.__context.velox_on_save_result_map.items():
54
+ # TODO: Record IDs are currently being stored in the map as strings instead of ints. This can be removed
55
+ # once sapiopylib is fixed.
56
+ record_id = int(record_id)
53
57
  # Keep track of the records for this specific record ID.
54
58
  id_dict: dict[str, set[DataRecord]] = {}
55
59
  # The list of results for a record consist of a list of data records and a VeloxType that specifies
@@ -60,8 +64,13 @@ class OnSaveRuleHandler:
60
64
  # Get the data type of this record. If this is an ELN type, ignore the digits.
61
65
  data_type: str = record.data_type_name
62
66
  # PR-46331: Ensure that all ELN types are converted to their base data type name.
63
- if ElnBaseDataType.is_eln_type(data_type):
64
- data_type = ElnBaseDataType.get_base_type(data_type).data_type_name
67
+ # TODO: Use ElnBaseDataType.is_eln_type when it is no longer bugged in sapiopylib.
68
+ if data_type.startswith("ELNExperiment_"):
69
+ data_type = "ELNExperiment"
70
+ elif data_type.startswith("ELNExperimentDetail_"):
71
+ data_type = "ELNExperimentDetail"
72
+ elif data_type.startswith("ELNSampleDetail_"):
73
+ data_type = "ELNSampleDetail"
65
74
  # Update the list of records of this type that exist so far globally.
66
75
  self.__records.setdefault(data_type, set()).add(record)
67
76
  # Do the same for the list of records of this type that relate to this record ID.
@@ -73,11 +82,24 @@ class OnSaveRuleHandler:
73
82
  self.__base_id_to_field_maps = {}
74
83
  # Repeat the same thing for the field map results.
75
84
  for record_id, rule_results in self.__context.velox_on_save_field_map_result_map.items():
85
+ # TODO: Record IDs are currently being stored in the map as strings instead of ints. This can be removed
86
+ # once sapiopylib is fixed.
87
+ record_id = int(record_id)
76
88
  id_dict: dict[str, dict[int, FieldMap]] = {}
77
89
  for record_result in rule_results:
78
- data_type: str = record_result.velox_type_pojo.data_type_name
79
- if ElnBaseDataType.is_eln_type(data_type):
80
- data_type = ElnBaseDataType.get_base_type(data_type).data_type_name
90
+ # TODO: sapiopylib currently has a bug where this velox_type_pojo variable is stored as a dict instead
91
+ # of as the intended VeloxRuleType object. Parse that dict as a VeloxRuleType before use.
92
+ velox_type: VeloxRuleType | dict = record_result.velox_type_pojo
93
+ if isinstance(velox_type, dict):
94
+ velox_type: VeloxRuleType = VeloxRuleParser.parse_velox_rule_type(velox_type)
95
+ data_type: str = velox_type.data_type_name
96
+ # TODO: Use ElnBaseDataType.is_eln_type when it is no longer bugged in sapiopylib.
97
+ if data_type.startswith("ELNExperiment_"):
98
+ data_type = "ELNExperiment"
99
+ elif data_type.startswith("ELNExperimentDetail_"):
100
+ data_type = "ELNExperimentDetail"
101
+ elif data_type.startswith("ELNSampleDetail_"):
102
+ data_type = "ELNSampleDetail"
81
103
  for field_map in record_result.field_map_list:
82
104
  rec_id: int = field_map.get("RecordId")
83
105
  self.__field_maps.setdefault(data_type, {}).update({rec_id: field_map})
@@ -4,9 +4,7 @@ from logging import Logger
4
4
 
5
5
  from sapiopylib.rest.DataMgmtService import DataMgmtServer
6
6
  from sapiopylib.rest.DataRecordManagerService import DataRecordManager
7
- from sapiopylib.rest.User import SapioUser
8
7
  from sapiopylib.rest.WebhookService import AbstractWebhookHandler
9
- from sapiopylib.rest.pojo.Message import VeloxLogMessage, VeloxLogLevel
10
8
  from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
11
9
  from sapiopylib.rest.pojo.webhook.WebhookEnums import WebhookEndpointType
12
10
  from sapiopylib.rest.pojo.webhook.WebhookResult import SapioWebhookResult
@@ -16,7 +14,6 @@ from sapiopylib.rest.utils.recordmodel.ancestry import RecordModelAncestorManage
16
14
 
17
15
  from sapiopycommons.general.exceptions import SapioUserErrorException, SapioCriticalErrorException, \
18
16
  SapioUserCancelledException
19
- from sapiopycommons.general.sapio_links import SapioNavigationLinker
20
17
 
21
18
 
22
19
  # FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
@@ -28,7 +25,6 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
28
25
  """
29
26
  logger: Logger
30
27
 
31
- user: SapioUser
32
28
  context: SapioWebhookContext
33
29
 
34
30
  dr_man: DataRecordManager
@@ -39,13 +35,11 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
39
35
  an_man: RecordModelAncestorManager
40
36
 
41
37
  def run(self, context: SapioWebhookContext) -> SapioWebhookResult:
42
- self.user = context.user
43
38
  self.context = context
44
-
45
- self.logger = self.user.logger
39
+ self.logger = context.user.logger
46
40
 
47
41
  self.dr_man = context.data_record_manager
48
- self.rec_man = RecordModelManager(self.user)
42
+ self.rec_man = RecordModelManager(context.user)
49
43
  self.inst_man = self.rec_man.instance_manager
50
44
  self.rel_man = self.rec_man.relationship_manager
51
45
  self.an_man = RecordModelAncestorManager(self.rec_man)
@@ -106,7 +100,7 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
106
100
  return result
107
101
  self.log_error(traceback.format_exc())
108
102
  if self.can_send_client_callback():
109
- DataMgmtServer.get_client_callback(self.user).display_error(e.args[0])
103
+ DataMgmtServer.get_client_callback(self.context.user).display_error(e.args[0])
110
104
  return SapioWebhookResult(False)
111
105
 
112
106
  def handle_unexpected_exception(self, e: Exception) -> SapioWebhookResult:
@@ -120,10 +114,7 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
120
114
  result: SapioWebhookResult | None = self.handle_any_exception(e)
121
115
  if result is not None:
122
116
  return result
123
- msg: str = traceback.format_exc()
124
- self.log_error(msg)
125
- # FR-47079: Also log all unexpected exception messages to the webhook execution log within the platform.
126
- self.log_error_to_webhook_execution_log(msg)
117
+ self.log_error(traceback.format_exc())
127
118
  return SapioWebhookResult(False, display_text="Unexpected error occurred during webhook execution. "
128
119
  "Please contact Sapio support.")
129
120
 
@@ -154,58 +145,32 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
154
145
 
155
146
  def log_info(self, msg: str) -> None:
156
147
  """
157
- Write an info message to the webhook server log. Log destination is stdout. This message will be prepended with
158
- the user's username and the experiment ID of the experiment they are in, if any.
148
+ Write an info message to the log. Log destination is stdout. This message will be prepended with the user's
149
+ username and the experiment ID of the experiment they are in, if any.
159
150
  """
160
- self.logger.info(self._format_log(msg, "log_info call"))
151
+ exp_id = None
152
+ if self.context.eln_experiment is not None:
153
+ exp_id = self.context.eln_experiment.notebook_experiment_id
154
+ # CR-46333: Add the user's group to the logging message.
155
+ user = self.context.user
156
+ username = user.username
157
+ group_name = user.session_additional_data.current_group_name
158
+ self.logger.info(f"(User: {username}, Group: {group_name}, Experiment: {exp_id}):\n{msg}")
161
159
 
162
160
  def log_error(self, msg: str) -> None:
163
161
  """
164
- Write an error message to the webhook server log. Log destination is stderr. This message will be prepended with
165
- the user's username and the experiment ID of the experiment they are in, if any.
166
- """
167
- # PR-46209: Use logger.error instead of logger.info when logging errors.
168
- self.logger.error(self._format_log(msg, "log_error call"))
169
-
170
- def log_error_to_webhook_execution_log(self, msg: str) -> None:
171
- """
172
- Write an error message to the platform's webhook execution log. This can be reviewed by navigating to the
173
- webhook configuration where the webhook that called this function is defined and clicking the "View Log"
174
- button. From there, select one of the rows for the webhook executions and click "Download Log" from the right
175
- side table.
176
- """
177
- messenger = DataMgmtServer.get_messenger(self.user)
178
- messenger.log_message(VeloxLogMessage(message=self._format_log(msg, "Error occurred during webhook execution."),
179
- log_level=VeloxLogLevel.ERROR,
180
- originating_class=self.__class__.__name__))
181
-
182
- def _format_log(self, msg: str, prefix: str | None = None) -> str:
183
- """
184
- Given a message to log, populate it with some metadata about this particular webhook execution, including
185
- the group of the user and the invocation type of the webhook call.
162
+ Write an error message to the log. Log destination is stderr. This message will be prepended with the user's
163
+ username and the experiment ID of the experiment they are in, if any.
186
164
  """
187
- # If we're able to, provide a link to the location that the error occurred at.
188
- navigator = SapioNavigationLinker(self.context)
165
+ exp_id = None
189
166
  if self.context.eln_experiment is not None:
190
- link = navigator.experiment(self.context.eln_experiment)
191
- elif self.context.data_record and not self.context.data_record_list:
192
- link = navigator.data_record(self.context.data_record)
193
- elif self.context.base_data_record:
194
- link = navigator.data_record(self.context.base_data_record)
195
- else:
196
- link = None
197
-
198
- message: str = ""
199
- if prefix:
200
- message += prefix + "\n"
201
- message += f"Webhook invocation type: {self.context.end_point_type.display_name}\n"
202
- message += f"Username: {self.user.username}\n"
167
+ exp_id = self.context.eln_experiment.notebook_experiment_id
203
168
  # CR-46333: Add the user's group to the logging message.
204
- message += f"User group: {self.user.session_additional_data.current_group_name}\n"
205
- if link:
206
- message += f"User location: {link}\n"
207
- message += msg
208
- return message
169
+ user = self.context.user
170
+ username = user.username
171
+ group_name = user.session_additional_data.current_group_name
172
+ # PR-46209: Use logger.error instead of logger.info when logging errors.
173
+ self.logger.error(f"(User: {username}, Group: {group_name}, Experiment: {exp_id}):\n{msg}")
209
174
 
210
175
  def is_main_toolbar(self) -> bool:
211
176
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sapiopycommons
3
- Version: 2024.8.2a301
3
+ Version: 2024.8.15a304
4
4
  Summary: Official Sapio Python API Utilities Package
5
5
  Project-URL: Homepage, https://github.com/sapiosciences
6
6
  Author-email: Jonathan Steck <jsteck@sapiosciences.com>, Yechen Qiao <yqiao@sapiosciences.com>
@@ -1,31 +1,29 @@
1
1
  sapiopycommons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  sapiopycommons/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- sapiopycommons/callbacks/callback_util.py,sha256=YdaN1iNnJ51EauvKfEOAUwahu1dt_YVvr4Zia29g9qc,61123
3
+ sapiopycommons/callbacks/callback_util.py,sha256=caeIWCHvK33jDs3TRskpJv0kDe7W8NPK4MyJPjgztwo,58012
4
4
  sapiopycommons/chem/IndigoMolecules.py,sha256=QqFDi9CKERj6sn_ZwVcS2xZq4imlkaTeCrpq1iNcEJA,1992
5
5
  sapiopycommons/chem/Molecules.py,sha256=t80IsQBPJ9mwE8ZxnWomAGrZDhdsOuPvLaTPb_N6jGU,8639
6
6
  sapiopycommons/chem/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  sapiopycommons/datatype/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- sapiopycommons/datatype/attachment_util.py,sha256=23JQ4avSmBBJdCv95LVj31x8rUCclzB_DYFBijH0NII,3708
8
+ sapiopycommons/datatype/attachment_util.py,sha256=YlnMprj5IGBbAZDLG2khS1P7JIYTw_NYfpJAfRZfP3M,3219
9
9
  sapiopycommons/eln/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- sapiopycommons/eln/experiment_handler.py,sha256=RspUDmPmBQIG7l7q_luFP8yeRljdU3E64066NH5CtdI,59817
10
+ sapiopycommons/eln/experiment_handler.py,sha256=v1pG4qtZb8OSNWfKtFo6NjnEkReqnu5R9i_hqWh_xxg,57198
11
11
  sapiopycommons/eln/experiment_report_util.py,sha256=FTLw-6SLAMeoWTOO-qhGROE9g54pZdyoQJIhiIzlwGw,7848
12
12
  sapiopycommons/eln/plate_designer.py,sha256=FYJfhhNq8hdfuXgDYOYHy6g0m2zNwQXZWF_MTPzElDg,7184
13
13
  sapiopycommons/files/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- sapiopycommons/files/complex_data_loader.py,sha256=8jgYF5iGDD6Abw8JRWLYxUWVwj4s4pH5HylyxEGFZSU,1471
15
- sapiopycommons/files/file_bridge.py,sha256=njx_5Z3tvQUNW4mPazQerL8lopLAFStIByHWHJ7m5ug,6220
14
+ sapiopycommons/files/complex_data_loader.py,sha256=XSJOl676mIklJo78v07-70u1b015a5DI4sqZPI3C-Tw,1475
15
+ sapiopycommons/files/file_bridge.py,sha256=GI3-gWFzcL0q0c8jKOxTevbzJqtUpiElmkXfTnMsaOo,6224
16
16
  sapiopycommons/files/file_bridge_handler.py,sha256=MU2wZR4VY606yx6Bnv8-LzG3mGCeuXeRBn914WNRFCo,13601
17
17
  sapiopycommons/files/file_data_handler.py,sha256=3-guAdhJdeJWAFq1a27ijspkO7uMMZ6CapMCD_6o4jA,36746
18
- sapiopycommons/files/file_util.py,sha256=ronTlJimQ6ttJN6Y9qfAzEWoeWnDoUbohkdObNK5mJQ,27042
19
- sapiopycommons/files/file_validator.py,sha256=EqPCXfVCiilgnOb2G-yZg8XWeDqYp9iVCg8AaWznvf4,28040
18
+ sapiopycommons/files/file_util.py,sha256=44mzhn3M_QltoncBB-ooX7_yO6u5k-XU_bzUXHGxUiw,26299
19
+ sapiopycommons/files/file_validator.py,sha256=BhXB2XnoNEzdBXuwul1s2RNoj-3ZoiMmephUCU_0o3Y,28113
20
20
  sapiopycommons/files/file_writer.py,sha256=5u_iZXTQvuUU7ceHZr8Q001_tvgJhOqBwAnB_pxcAbQ,16027
21
21
  sapiopycommons/general/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  sapiopycommons/general/accession_service.py,sha256=HYgyOsH_UaoRnoury-c2yTW8SeG4OtjLemdpCzoV4R8,13484
23
- sapiopycommons/general/aliases.py,sha256=UddtYCGqM-wkLXT0LAUqMlFGHZSyOPjA6hzQHvjlDcQ,4324
24
- sapiopycommons/general/audit_log.py,sha256=LNpBCpZ5mz47-HzHel8PUz1TYGCnxTY6dnerZu-cwOo,9076
23
+ sapiopycommons/general/aliases.py,sha256=i6af5o2oVFGNcyk7GkvTWXQs0H9xTbFKc_GIah8NKVU,3594
25
24
  sapiopycommons/general/custom_report_util.py,sha256=cLgIR5Fn3M9uyAtgfTYRv3JRk2SKNevnsb_R5zidSYs,15557
26
25
  sapiopycommons/general/exceptions.py,sha256=DOlLKnpCatxQF-lVCToa8ryJgusWLvip6N_1ALN00QE,1679
27
26
  sapiopycommons/general/popup_util.py,sha256=-mN5IgYPrLrOEHJ4CHPi2rec4_WAN6X0yMxHwD5h3Bs,30126
28
- sapiopycommons/general/sapio_links.py,sha256=UlqB09wmgDgbQiB8d3mEj7rxW_GMIXz3j3RlvADNt_A,2475
29
27
  sapiopycommons/general/storage_util.py,sha256=ovmK_jN7v09BoX07XxwShpBUC5WYQOM7dbKV_VeLXJU,8892
30
28
  sapiopycommons/general/time_util.py,sha256=jiJUh7jc1ZRCOem880S3HaLPZ4RboBtSl4_U9sqAQuM,7290
31
29
  sapiopycommons/multimodal/multimodal.py,sha256=A1QsC8QTPmgZyPr7KtMbPRedn2Ie4WIErodUvQ9otgU,6724
@@ -33,13 +31,13 @@ sapiopycommons/multimodal/multimodal_data.py,sha256=zqgYHO-ULaPKV0POFWZVY9N-Sfm1
33
31
  sapiopycommons/processtracking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
32
  sapiopycommons/processtracking/endpoints.py,sha256=g5h_uCVByqacYm9zWAz8TyAdRsGfaO2o0b5RSJdOaSA,10926
35
33
  sapiopycommons/recordmodel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
- sapiopycommons/recordmodel/record_handler.py,sha256=qqsHeGNyqQeFKuZ4U2v2MD278AlH4n1HMvSTOY0hbyo,58636
34
+ sapiopycommons/recordmodel/record_handler.py,sha256=AyK1H3x-g1eu1Mt9XD1h57yRrZp_TJjZlEaQ2kPP4Dc,54432
37
35
  sapiopycommons/rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
- sapiopycommons/rules/eln_rule_handler.py,sha256=rz9E1PQLShCZM09JafMl_ylUqBbMs-et0FsOG41tQL0,9480
39
- sapiopycommons/rules/on_save_rule_handler.py,sha256=I06HwsNvulyU0avSXOU0itBDAmiOxsyW8KGk_gH0FHc,9238
36
+ sapiopycommons/rules/eln_rule_handler.py,sha256=qfkBZtck0KK1i9s9Xe2UZqkzQOgPCzDxRkhxE8Si1xk,10671
37
+ sapiopycommons/rules/on_save_rule_handler.py,sha256=JY9F30IcHwFVdgPAMQtTYuRastV1jeezhVktyrzNASU,10763
40
38
  sapiopycommons/webhook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
- sapiopycommons/webhook/webhook_handlers.py,sha256=1uM_epEaEB8w14padEdASXqF3BNalNiShP51a_hU-Ac,15267
42
- sapiopycommons-2024.8.2a301.dist-info/METADATA,sha256=EVt23yIYhHMJPakRR5Q-RMUi7-1AnxZSKtMrN6hHwwM,3175
43
- sapiopycommons-2024.8.2a301.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
44
- sapiopycommons-2024.8.2a301.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
45
- sapiopycommons-2024.8.2a301.dist-info/RECORD,,
39
+ sapiopycommons/webhook/webhook_handlers.py,sha256=ibpBY3Sk3Eij919bIdW0awzlogYoQSWYDDOg--NwsQE,13431
40
+ sapiopycommons-2024.8.15a304.dist-info/METADATA,sha256=MAySm4oWvEe5RSyNWOoiu0F3qnD-zTzWVUvqulqlKpQ,3176
41
+ sapiopycommons-2024.8.15a304.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
42
+ sapiopycommons-2024.8.15a304.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
43
+ sapiopycommons-2024.8.15a304.dist-info/RECORD,,