sapiopycommons 2024.11.10a363__py3-none-any.whl → 2024.11.12a365__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 (45) hide show
  1. sapiopycommons/callbacks/callback_util.py +532 -83
  2. sapiopycommons/callbacks/field_builder.py +537 -0
  3. sapiopycommons/chem/IndigoMolecules.py +1 -0
  4. sapiopycommons/chem/Molecules.py +1 -0
  5. sapiopycommons/customreport/__init__.py +0 -0
  6. sapiopycommons/customreport/column_builder.py +60 -0
  7. sapiopycommons/customreport/custom_report_builder.py +130 -0
  8. sapiopycommons/customreport/term_builder.py +299 -0
  9. sapiopycommons/datatype/attachment_util.py +11 -10
  10. sapiopycommons/datatype/data_fields.py +61 -0
  11. sapiopycommons/datatype/pseudo_data_types.py +440 -0
  12. sapiopycommons/eln/experiment_handler.py +272 -70
  13. sapiopycommons/eln/experiment_report_util.py +653 -0
  14. sapiopycommons/files/complex_data_loader.py +5 -4
  15. sapiopycommons/files/file_bridge.py +31 -24
  16. sapiopycommons/files/file_bridge_handler.py +340 -0
  17. sapiopycommons/files/file_data_handler.py +2 -5
  18. sapiopycommons/files/file_util.py +59 -9
  19. sapiopycommons/files/file_validator.py +92 -6
  20. sapiopycommons/files/file_writer.py +44 -15
  21. sapiopycommons/general/accession_service.py +375 -0
  22. sapiopycommons/general/aliases.py +207 -6
  23. sapiopycommons/general/audit_log.py +189 -0
  24. sapiopycommons/general/custom_report_util.py +212 -37
  25. sapiopycommons/general/exceptions.py +21 -8
  26. sapiopycommons/general/popup_util.py +21 -0
  27. sapiopycommons/general/sapio_links.py +50 -0
  28. sapiopycommons/general/time_util.py +8 -2
  29. sapiopycommons/multimodal/multimodal.py +146 -0
  30. sapiopycommons/multimodal/multimodal_data.py +486 -0
  31. sapiopycommons/processtracking/custom_workflow_handler.py +406 -0
  32. sapiopycommons/processtracking/endpoints.py +22 -22
  33. sapiopycommons/recordmodel/record_handler.py +481 -97
  34. sapiopycommons/rules/eln_rule_handler.py +34 -25
  35. sapiopycommons/rules/on_save_rule_handler.py +34 -31
  36. sapiopycommons/sftpconnect/__init__.py +0 -0
  37. sapiopycommons/sftpconnect/sftp_builder.py +69 -0
  38. sapiopycommons/webhook/webhook_context.py +39 -0
  39. sapiopycommons/webhook/webhook_handlers.py +201 -42
  40. sapiopycommons/webhook/webservice_handlers.py +67 -0
  41. {sapiopycommons-2024.11.10a363.dist-info → sapiopycommons-2024.11.12a365.dist-info}/METADATA +4 -2
  42. sapiopycommons-2024.11.12a365.dist-info/RECORD +57 -0
  43. sapiopycommons-2024.11.10a363.dist-info/RECORD +0 -38
  44. {sapiopycommons-2024.11.10a363.dist-info → sapiopycommons-2024.11.12a365.dist-info}/WHEEL +0 -0
  45. {sapiopycommons-2024.11.10a363.dist-info → sapiopycommons-2024.11.12a365.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,50 @@
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, DataTypeIdentifier
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: DataTypeIdentifier | 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 data_type_name:
36
+ data_type_name = AliasUtil.to_data_type_name(data_type_name)
37
+ if not isinstance(record_identifier, int):
38
+ data_type_name = AliasUtil.to_data_type_name(record_identifier)
39
+ if not data_type_name:
40
+ raise SapioException("Unable to create a data record link without a data type name. "
41
+ "Only a record ID was provided.")
42
+ return self.base_url + f"/#dataType={data_type_name};recordId={record_id};view=dataRecord"
43
+
44
+ def experiment(self, experiment: ExperimentIdentifier) -> str:
45
+ """
46
+ :param experiment: An object that can be used to identify an experiment in the system, be that an experiment
47
+ object, experiment protocol, or a notebook ID.
48
+ :return: A URL for navigating to the input experiment.
49
+ """
50
+ return self.base_url + f"/#notebookExperimentId={AliasUtil.to_notebook_id(experiment)};view=eln"
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import time
2
4
  from datetime import datetime
3
5
 
@@ -92,9 +94,10 @@ class TimeUtil:
92
94
  return TimeUtil.current_time(timezone).strftime(time_format)
93
95
 
94
96
  @staticmethod
95
- def millis_to_format(millis: int, time_format: str, timezone: str | int = None) -> str:
97
+ def millis_to_format(millis: int, time_format: str, timezone: str | int = None) -> str | None:
96
98
  """
97
- Convert the input time in milliseconds to the provided format.
99
+ Convert the input time in milliseconds to the provided format. If None is passed to the millis parameter,
100
+ None will be returned
98
101
 
99
102
  :param millis: The time in milliseconds to convert from.
100
103
  :param time_format: The format to display the input time in. Documentation for how the time formatting works
@@ -103,6 +106,9 @@ class TimeUtil:
103
106
  timezone variable set by the TimeUtil. A list of valid timezones can be found at
104
107
  https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. May also accept a UTC offset in seconds.
105
108
  """
109
+ if millis is None:
110
+ return None
111
+
106
112
  tz = TimeUtil.__to_tz(timezone)
107
113
  return datetime.fromtimestamp(millis / 1000, tz).strftime(time_format)
108
114
 
@@ -0,0 +1,146 @@
1
+ # Multimodal registration client
2
+ from __future__ import annotations
3
+
4
+ import io
5
+ from weakref import WeakValueDictionary
6
+
7
+ from databind.json import dumps, loads
8
+ from sapiopylib.rest.User import SapioUser
9
+
10
+ from sapiopycommons.general.exceptions import SapioException
11
+ from sapiopycommons.multimodal.multimodal_data import *
12
+
13
+
14
+ class MultiModalManager:
15
+ _user: SapioUser
16
+
17
+ __instances: WeakValueDictionary[SapioUser, MultiModalManager] = WeakValueDictionary()
18
+ __initialized: bool
19
+
20
+ def __new__(cls, user: SapioUser):
21
+ """
22
+ Observes singleton pattern per record model manager object.
23
+
24
+ :param user: The user that will make the webservice request to the application.
25
+ """
26
+ obj = cls.__instances.get(user)
27
+ if not obj:
28
+ obj = object.__new__(cls)
29
+ obj.__initialized = False
30
+ cls.__instances[user] = obj
31
+ return obj
32
+
33
+ def __init__(self, user:SapioUser):
34
+ if self.__initialized:
35
+ return
36
+ self._user = user
37
+ self.__initialized = True
38
+
39
+ def load_image_data(self, request: ImageDataRequestPojo) -> list[str]:
40
+ """
41
+ Loading of image data of a compound or a reaction in Sapio's unified drawing format.
42
+ :param request:
43
+ :return:
44
+ """
45
+ payload = dumps(request, ImageDataRequestPojo)
46
+ response = self._user.plugin_post("chemistry/request_image_data",
47
+ payload=payload, is_payload_plain_text=True)
48
+ self._user.raise_for_status(response)
49
+ return response.json()
50
+
51
+ def load_compounds(self, request: CompoundLoadRequestPojo):
52
+ """
53
+ Load compounds from the provided data here.
54
+ The compounds will not be registered but returned to you "the script".
55
+ To complete registration, you need to call register_compounds method after obtaining result.
56
+ """
57
+ payload = dumps(request, CompoundLoadRequestPojo)
58
+ response = self._user.plugin_post("chemistry/load",
59
+ payload=payload, is_payload_plain_text=True)
60
+ self._user.raise_for_status(response)
61
+ return loads(response.text, PyMoleculeLoaderResult)
62
+
63
+ def register_compounds(self, request: ChemRegisterRequestPojo) -> ChemCompleteImportPojo:
64
+ """
65
+ Register the filled compounds that are previously loaded via load_compounds operation.
66
+ """
67
+ payload = dumps(request, ChemRegisterRequestPojo)
68
+ response = self._user.plugin_post("chemistry/register",
69
+ payload=payload, is_payload_plain_text=True)
70
+ self._user.raise_for_status(response)
71
+ return loads(response.text, ChemCompleteImportPojo)
72
+
73
+ def load_reactions(self, reaction_str: str) -> PyIndigoReactionPojo:
74
+ """
75
+ Load a reaction and return the loaded reaction result.
76
+ :param reaction_str: A reaction string, in format of mrv, rxn, or smiles.
77
+ """
78
+ response = self._user.plugin_post("chemistry/reaction/load",
79
+ payload=reaction_str, is_payload_plain_text=True)
80
+ self._user.raise_for_status(response)
81
+ return loads(response.text, PyIndigoReactionPojo)
82
+
83
+ def register_reactions(self, reaction_str: str) -> DataRecord:
84
+ """
85
+ Register a single reaction provided.
86
+ Note: if the rxn has already specified a 2D coordinate, it may not be recomputed when generating record image.
87
+ :param reaction_str: The rxn of a reaction.
88
+ :return: The registered data record. This can be a record that already exists or new.
89
+ """
90
+ response = self._user.plugin_post("chemistry/reaction/register",
91
+ payload=reaction_str, is_payload_plain_text=True)
92
+ self._user.raise_for_status(response)
93
+ return loads(response.text, DataRecord)
94
+
95
+ def search_structures(self, request: ChemSearchRequestPojo) -> ChemSearchResponsePojo:
96
+ """
97
+ Perform structure search against the Sapio registries.
98
+ An error can be thrown as exception if search is structurally invalid.
99
+ :param request: The request object containing the detailed context of this search.
100
+ :return: The response object of the result.
101
+ """
102
+ payload = dumps(request, ChemSearchRequestPojo)
103
+ response = self._user.plugin_post("chemistry/search",
104
+ payload=payload, is_payload_plain_text=True)
105
+ self._user.raise_for_status(response)
106
+ return loads(response.text, ChemSearchResponsePojo)
107
+
108
+ def run_multi_sequence_alignment(self, request: MultiSequenceAlignmentRequestPojo) -> list[MultiSequenceAlignmentSeqPojo]:
109
+ """
110
+ Run a multi-sequence alignment using the specified tool and strategy.
111
+ :param request: The request object containing the sequences and alignment parameters. The parameters inside it can be the pojo dict of one of the options.
112
+ :return: The result of the multi-sequence alignment.
113
+ """
114
+ payload = dumps(request, MultiSequenceAlignmentRequestPojo)
115
+ response = self._user.plugin_post("bio/multisequencealignment",
116
+ payload=payload, is_payload_plain_text=True)
117
+ self._user.raise_for_status(response)
118
+ return loads(response.text, list[MultiSequenceAlignmentSeqPojo])
119
+
120
+ def register_bio(self, request: BioFileRegistrationRequest) -> BioFileRegistrationResponse:
121
+ """
122
+ Register to bioregistry of a file.
123
+ """
124
+ payload = dumps(request, BioFileRegistrationRequest)
125
+ response = self._user.plugin_post("bio/register/file", payload=payload, is_payload_plain_text=True)
126
+ self._user.raise_for_status(response)
127
+ return loads(response.text, BioFileRegistrationResponse)
128
+
129
+ def export_to_sdf(self, request: ChemExportSDFRequest) -> str:
130
+ """
131
+ Export the SDF files
132
+ :param request: The request for exporting SDF file.
133
+ :return: the SDF plain text data.
134
+ """
135
+ payload = dumps(request, ChemExportSDFRequest)
136
+ response = self._user.plugin_post("chemistry/export_sdf", payload=payload, is_payload_plain_text=True)
137
+ self._user.raise_for_status(response)
138
+ gzip_base64: str = response.text
139
+ if not gzip_base64:
140
+ raise SapioException("Returning data from server is blank for export SDF.")
141
+ decoded_bytes = base64.b64decode(gzip_base64)
142
+ with io.BytesIO(decoded_bytes) as bytes_io:
143
+ import gzip
144
+ with gzip.GzipFile(fileobj=bytes_io, mode='rb') as f:
145
+ ret: str = f.read().decode()
146
+ return ret