sapiopycommons 2024.7.29a300__tar.gz → 2024.8.2a301__tar.gz

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 (54) hide show
  1. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/PKG-INFO +1 -1
  2. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/pyproject.toml +1 -1
  3. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/general/aliases.py +11 -1
  4. sapiopycommons-2024.8.2a301/src/sapiopycommons/general/sapio_links.py +48 -0
  5. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/webhook/webhook_handlers.py +58 -23
  6. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/.gitignore +0 -0
  7. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/LICENSE +0 -0
  8. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/README.md +0 -0
  9. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/__init__.py +0 -0
  10. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/callbacks/__init__.py +0 -0
  11. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/callbacks/callback_util.py +0 -0
  12. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/chem/IndigoMolecules.py +0 -0
  13. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/chem/Molecules.py +0 -0
  14. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/chem/__init__.py +0 -0
  15. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/datatype/__init__.py +0 -0
  16. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/datatype/attachment_util.py +0 -0
  17. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/eln/__init__.py +0 -0
  18. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/eln/experiment_handler.py +0 -0
  19. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/eln/experiment_report_util.py +0 -0
  20. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/eln/plate_designer.py +0 -0
  21. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/files/__init__.py +0 -0
  22. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/files/complex_data_loader.py +0 -0
  23. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/files/file_bridge.py +0 -0
  24. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/files/file_bridge_handler.py +0 -0
  25. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/files/file_data_handler.py +0 -0
  26. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/files/file_util.py +0 -0
  27. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/files/file_validator.py +0 -0
  28. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/files/file_writer.py +0 -0
  29. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/general/__init__.py +0 -0
  30. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/general/accession_service.py +0 -0
  31. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/general/audit_log.py +0 -0
  32. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/general/custom_report_util.py +0 -0
  33. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/general/exceptions.py +0 -0
  34. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/general/popup_util.py +0 -0
  35. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/general/storage_util.py +0 -0
  36. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/general/time_util.py +0 -0
  37. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/multimodal/multimodal.py +0 -0
  38. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/multimodal/multimodal_data.py +0 -0
  39. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/processtracking/__init__.py +0 -0
  40. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/processtracking/endpoints.py +0 -0
  41. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/recordmodel/__init__.py +0 -0
  42. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/recordmodel/record_handler.py +0 -0
  43. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/rules/__init__.py +0 -0
  44. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/rules/eln_rule_handler.py +0 -0
  45. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/rules/on_save_rule_handler.py +0 -0
  46. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/src/sapiopycommons/webhook/__init__.py +0 -0
  47. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/tests/_do_not_add_init_py_here +0 -0
  48. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/tests/accession_test.py +0 -0
  49. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/tests/bio_reg_test.py +0 -0
  50. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/tests/chem_test.py +0 -0
  51. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/tests/data_type_models.py +0 -0
  52. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/tests/kappa.chains.fasta +0 -0
  53. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/tests/mafft_test.py +0 -0
  54. {sapiopycommons-2024.7.29a300 → sapiopycommons-2024.8.2a301}/tests/test.gb +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sapiopycommons
3
- Version: 2024.7.29a300
3
+ Version: 2024.8.2a301
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>
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sapiopycommons"
7
- version='2024.07.29a300'
7
+ version='2024.08.02a301'
8
8
  authors = [
9
9
  { name="Jonathan Steck", email="jsteck@sapiosciences.com" },
10
10
  { name="Yechen Qiao", email="yqiao@sapiosciences.com" },
@@ -58,10 +58,20 @@ class AliasUtil:
58
58
  Convert a single variable that could be either an integer, DataRecord, PyRecordModel,
59
59
  or WrappedRecordModel to just an integer (taking the record ID from the record).
60
60
 
61
- :return: A record id for the input record.
61
+ :return: A record ID for the input record.
62
62
  """
63
63
  return record if isinstance(record, int) else record.record_id
64
64
 
65
+ @staticmethod
66
+ def to_data_type_name(record: SapioRecord) -> str:
67
+ """
68
+ Convert a single variable that could be either a Data Record, PyRecordModel, or WrappedRecordModel to the
69
+ data type name for that record.
70
+
71
+ :return: The data type name for the input record.
72
+ """
73
+ return record.data_type_name
74
+
65
75
  @staticmethod
66
76
  def to_field_map_lists(records: Iterable[SapioRecord]) -> list[FieldMap]:
67
77
  """
@@ -0,0 +1,48 @@
1
+ from sapiopylib.rest.User import SapioUser
2
+ from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
3
+
4
+ from sapiopycommons.general.aliases import RecordIdentifier, ExperimentIdentifier, AliasUtil
5
+ from sapiopycommons.general.exceptions import SapioException
6
+
7
+
8
+ class SapioNavigationLinker:
9
+ """
10
+ Given a URL to a system's webservice API (example: https://company.exemplareln.com/webservice/api), construct
11
+ URLs for navigation links to various locations in the system.
12
+ """
13
+ base_url: str
14
+
15
+ def __init__(self, url: str | SapioUser | SapioWebhookContext):
16
+ """
17
+ :param url: A user or context object that is being used to send requests to a Sapio system, or a URL to a
18
+ system's webservice API.
19
+ """
20
+ if isinstance(url, SapioWebhookContext):
21
+ url = url.user.url
22
+ elif isinstance(url, SapioUser):
23
+ url = url.url
24
+ self.base_url = url.rstrip("/").replace('webservice/api', 'veloxClient')
25
+
26
+ def data_record(self, record_identifier: RecordIdentifier, data_type_name: str | None = None) -> str:
27
+ """
28
+ :param record_identifier: An object that can be used to identify a record in the system, be that a record ID,
29
+ a data record, or a record model.
30
+ :param data_type_name: If the provided record identifier is a record ID, then the data type name of the record
31
+ must be provided in this parameter. Otherwise, this parameter is ignored.
32
+ :return: A URL for navigating to the input record.
33
+ """
34
+ record_id: int = AliasUtil.to_record_id(record_identifier)
35
+ if not isinstance(record_identifier, int):
36
+ data_type_name = AliasUtil.to_data_type_name(record_identifier)
37
+ if not data_type_name:
38
+ raise SapioException("Unable to create a data record link without a data type name. "
39
+ "Only a record ID was provided.")
40
+ return self.base_url + f"/#dataType={data_type_name};recordId={record_id};view=dataRecord"
41
+
42
+ def experiment(self, experiment: ExperimentIdentifier) -> str:
43
+ """
44
+ :param experiment: An object that can be used to identify an experiment in the system, be that an experiment
45
+ object, experiment protocol, or a notebook ID.
46
+ :return: A URL for navigating to the input experiment.
47
+ """
48
+ return self.base_url + f"/#notebookExperimentId={AliasUtil.to_notebook_id(experiment)};view=eln"
@@ -4,7 +4,9 @@ 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
7
8
  from sapiopylib.rest.WebhookService import AbstractWebhookHandler
9
+ from sapiopylib.rest.pojo.Message import VeloxLogMessage, VeloxLogLevel
8
10
  from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
9
11
  from sapiopylib.rest.pojo.webhook.WebhookEnums import WebhookEndpointType
10
12
  from sapiopylib.rest.pojo.webhook.WebhookResult import SapioWebhookResult
@@ -14,6 +16,7 @@ from sapiopylib.rest.utils.recordmodel.ancestry import RecordModelAncestorManage
14
16
 
15
17
  from sapiopycommons.general.exceptions import SapioUserErrorException, SapioCriticalErrorException, \
16
18
  SapioUserCancelledException
19
+ from sapiopycommons.general.sapio_links import SapioNavigationLinker
17
20
 
18
21
 
19
22
  # FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
@@ -25,6 +28,7 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
25
28
  """
26
29
  logger: Logger
27
30
 
31
+ user: SapioUser
28
32
  context: SapioWebhookContext
29
33
 
30
34
  dr_man: DataRecordManager
@@ -35,11 +39,13 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
35
39
  an_man: RecordModelAncestorManager
36
40
 
37
41
  def run(self, context: SapioWebhookContext) -> SapioWebhookResult:
42
+ self.user = context.user
38
43
  self.context = context
39
- self.logger = context.user.logger
44
+
45
+ self.logger = self.user.logger
40
46
 
41
47
  self.dr_man = context.data_record_manager
42
- self.rec_man = RecordModelManager(context.user)
48
+ self.rec_man = RecordModelManager(self.user)
43
49
  self.inst_man = self.rec_man.instance_manager
44
50
  self.rel_man = self.rec_man.relationship_manager
45
51
  self.an_man = RecordModelAncestorManager(self.rec_man)
@@ -100,7 +106,7 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
100
106
  return result
101
107
  self.log_error(traceback.format_exc())
102
108
  if self.can_send_client_callback():
103
- DataMgmtServer.get_client_callback(self.context.user).display_error(e.args[0])
109
+ DataMgmtServer.get_client_callback(self.user).display_error(e.args[0])
104
110
  return SapioWebhookResult(False)
105
111
 
106
112
  def handle_unexpected_exception(self, e: Exception) -> SapioWebhookResult:
@@ -114,7 +120,10 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
114
120
  result: SapioWebhookResult | None = self.handle_any_exception(e)
115
121
  if result is not None:
116
122
  return result
117
- self.log_error(traceback.format_exc())
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)
118
127
  return SapioWebhookResult(False, display_text="Unexpected error occurred during webhook execution. "
119
128
  "Please contact Sapio support.")
120
129
 
@@ -145,32 +154,58 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
145
154
 
146
155
  def log_info(self, msg: str) -> None:
147
156
  """
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.
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.
150
159
  """
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}")
160
+ self.logger.info(self._format_log(msg, "log_info call"))
159
161
 
160
162
  def log_error(self, msg: str) -> None:
161
163
  """
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.
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.
164
186
  """
165
- exp_id = None
187
+ # If we're able to, provide a link to the location that the error occurred at.
188
+ navigator = SapioNavigationLinker(self.context)
166
189
  if self.context.eln_experiment is not None:
167
- exp_id = self.context.eln_experiment.notebook_experiment_id
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"
168
203
  # CR-46333: Add the user's group to the logging 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}")
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
174
209
 
175
210
  def is_main_toolbar(self) -> bool:
176
211
  """