sapiopycommons 2025.8.22a715__py3-none-any.whl → 2025.8.22a716__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.
- sapiopycommons/ai/tool_of_tools.py +917 -0
- sapiopycommons/callbacks/callback_util.py +26 -16
- sapiopycommons/chem/IndigoMolecules.py +12 -10
- sapiopycommons/chem/ps_commons.py +773 -0
- sapiopycommons/files/assay_plate_reader.py +93 -0
- sapiopycommons/files/file_text_converter.py +207 -0
- sapiopycommons/flowcyto/flow_cyto.py +2 -24
- sapiopycommons/general/accession_service.py +2 -28
- sapiopycommons/multimodal/multimodal.py +2 -24
- sapiopycommons/webhook/webservice_handlers.py +1 -1
- {sapiopycommons-2025.8.22a715.dist-info → sapiopycommons-2025.8.22a716.dist-info}/METADATA +2 -2
- {sapiopycommons-2025.8.22a715.dist-info → sapiopycommons-2025.8.22a716.dist-info}/RECORD +14 -45
- sapiopycommons/ai/converter_service_base.py +0 -131
- sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2.py +0 -43
- sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2.pyi +0 -31
- sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2_grpc.py +0 -24
- sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2.py +0 -123
- sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2.pyi +0 -598
- sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2_grpc.py +0 -24
- sapiopycommons/ai/protoapi/plan/converter/converter_pb2.py +0 -51
- sapiopycommons/ai/protoapi/plan/converter/converter_pb2.pyi +0 -63
- sapiopycommons/ai/protoapi/plan/converter/converter_pb2_grpc.py +0 -149
- sapiopycommons/ai/protoapi/plan/item/item_container_pb2.py +0 -55
- sapiopycommons/ai/protoapi/plan/item/item_container_pb2.pyi +0 -88
- sapiopycommons/ai/protoapi/plan/item/item_container_pb2_grpc.py +0 -24
- sapiopycommons/ai/protoapi/plan/script/script_pb2.py +0 -59
- sapiopycommons/ai/protoapi/plan/script/script_pb2.pyi +0 -102
- sapiopycommons/ai/protoapi/plan/script/script_pb2_grpc.py +0 -153
- sapiopycommons/ai/protoapi/plan/step_output_pb2.py +0 -45
- sapiopycommons/ai/protoapi/plan/step_output_pb2.pyi +0 -42
- sapiopycommons/ai/protoapi/plan/step_output_pb2_grpc.py +0 -24
- sapiopycommons/ai/protoapi/plan/step_pb2.py +0 -43
- sapiopycommons/ai/protoapi/plan/step_pb2.pyi +0 -43
- sapiopycommons/ai/protoapi/plan/step_pb2_grpc.py +0 -24
- sapiopycommons/ai/protoapi/plan/tool/entry_pb2.py +0 -41
- sapiopycommons/ai/protoapi/plan/tool/entry_pb2.pyi +0 -35
- sapiopycommons/ai/protoapi/plan/tool/entry_pb2_grpc.py +0 -24
- sapiopycommons/ai/protoapi/plan/tool/tool_pb2.py +0 -75
- sapiopycommons/ai/protoapi/plan/tool/tool_pb2.pyi +0 -237
- sapiopycommons/ai/protoapi/plan/tool/tool_pb2_grpc.py +0 -154
- sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2.py +0 -39
- sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2.pyi +0 -32
- sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2_grpc.py +0 -24
- sapiopycommons/ai/protobuf_utils.py +0 -504
- sapiopycommons/ai/server.py +0 -104
- sapiopycommons/ai/test_client.py +0 -356
- sapiopycommons/ai/tool_service_base.py +0 -922
- {sapiopycommons-2025.8.22a715.dist-info → sapiopycommons-2025.8.22a716.dist-info}/WHEEL +0 -0
- {sapiopycommons-2025.8.22a715.dist-info → sapiopycommons-2025.8.22a716.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import dataclasses
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from databind.core.dataclasses import dataclass
|
|
6
|
+
from databind.json import loads
|
|
7
|
+
from sapiopylib.rest.utils.singletons import SapioContextManager
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclasses.dataclass
|
|
11
|
+
class ProcessAssayPlateRequest:
|
|
12
|
+
"""
|
|
13
|
+
A request to process the results of assay plate reader with a configuration set in Sapio.
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
num_rows (int): The number of rows in the plate.
|
|
17
|
+
num_columns (int): The number of columns in the plate.
|
|
18
|
+
plate_ids_in_context (list[str]): List of plate IDs that are in context for this request.
|
|
19
|
+
filename (str): The name of the file containing the assay data.
|
|
20
|
+
file_data (bytes): The binary content of the file.
|
|
21
|
+
plate_reader_config_name (str): The name of the plate reader configuration to use.
|
|
22
|
+
"""
|
|
23
|
+
num_rows: int
|
|
24
|
+
num_columns: int
|
|
25
|
+
plate_ids_in_context: list[str] | None
|
|
26
|
+
filename: str
|
|
27
|
+
file_data: bytes
|
|
28
|
+
plate_reader_config_name: str
|
|
29
|
+
|
|
30
|
+
def to_json(self) -> dict[str, Any]:
|
|
31
|
+
return {
|
|
32
|
+
"numRows": self.num_rows,
|
|
33
|
+
"numCols": self.num_columns,
|
|
34
|
+
"plateIdsInContext": self.plate_ids_in_context,
|
|
35
|
+
"fileName": self.filename,
|
|
36
|
+
"fileDataBase64": base64.b64encode(self.file_data).decode('utf-8'),
|
|
37
|
+
"plateReaderName": self.plate_reader_config_name
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class AssayPlateResultIdent:
|
|
43
|
+
plateId: str
|
|
44
|
+
channelIdOrBlock: str
|
|
45
|
+
kineticAssaySeconds: float | None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class AssayResultDatum:
|
|
50
|
+
"""
|
|
51
|
+
Describes the data received from an assay plate reader.
|
|
52
|
+
Most of the time, the data is a single value, but sometimes it can be multiple values, especially for kinetic data.
|
|
53
|
+
"""
|
|
54
|
+
DEFAULT_PROPERTY_NAME: str = "read"
|
|
55
|
+
rowPosition: str
|
|
56
|
+
columnPosition: str
|
|
57
|
+
valueByPropertyName: dict[str, float]
|
|
58
|
+
textValueByPropertyName: dict[str, str]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class AssayPlateResult:
|
|
63
|
+
"""
|
|
64
|
+
Assay plate load result for a single plate in a file. A file can have more than one of this result if it has multiple plate of data in a single file.
|
|
65
|
+
"""
|
|
66
|
+
resultIdent: AssayPlateResultIdent
|
|
67
|
+
numRows: int
|
|
68
|
+
numColumns: int
|
|
69
|
+
resultDatum: list[AssayResultDatum]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class AssayFileLoadResult:
|
|
74
|
+
"""
|
|
75
|
+
The entire top-level file loading result for an assay plate reader file.
|
|
76
|
+
"""
|
|
77
|
+
filename: str
|
|
78
|
+
plateResultList: list[AssayPlateResult]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class AssayPlateReader(SapioContextManager):
|
|
82
|
+
"""
|
|
83
|
+
This class contains services for Sapio Assay Plate Reader.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def process_plate_reader_data(self, request: ProcessAssayPlateRequest) -> AssayFileLoadResult:
|
|
87
|
+
"""
|
|
88
|
+
Processes the assay plate reader data using provided request into a structured result using configuration defined in Sapio.
|
|
89
|
+
"""
|
|
90
|
+
payload = request.to_json()
|
|
91
|
+
response = self.user.plugin_post("assayplatereader/process", payload=payload)
|
|
92
|
+
self.user.raise_for_status(response)
|
|
93
|
+
return loads(response.text, AssayFileLoadResult)
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import os
|
|
3
|
+
import tempfile
|
|
4
|
+
from enum import Enum, auto
|
|
5
|
+
|
|
6
|
+
class FileType(Enum):
|
|
7
|
+
"""Supported file types for conversion."""
|
|
8
|
+
TXT = auto()
|
|
9
|
+
MD = auto()
|
|
10
|
+
CSV = auto()
|
|
11
|
+
DOC = auto()
|
|
12
|
+
DOCX = auto()
|
|
13
|
+
XLS = auto()
|
|
14
|
+
XLSX = auto()
|
|
15
|
+
PPT = auto()
|
|
16
|
+
PPTX = auto()
|
|
17
|
+
PDF = auto()
|
|
18
|
+
UNKNOWN = auto()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class FileToTextConverter:
|
|
22
|
+
"""
|
|
23
|
+
A class for converting various file types to raw text.
|
|
24
|
+
"""
|
|
25
|
+
@staticmethod
|
|
26
|
+
def mime_type_to_enum(mime_type: str) -> FileType:
|
|
27
|
+
"""
|
|
28
|
+
Converts a MIME type to a FileType enum.
|
|
29
|
+
|
|
30
|
+
:param mime_type: The MIME type string to convert.
|
|
31
|
+
:return: The corresponding FileType enum, or UNKNOWN if not recognized.
|
|
32
|
+
"""
|
|
33
|
+
if not mime_type or not mime_type.strip():
|
|
34
|
+
return FileType.UNKNOWN
|
|
35
|
+
|
|
36
|
+
mime_map = {
|
|
37
|
+
"text/plain": FileType.TXT,
|
|
38
|
+
"text/markdown": FileType.MD,
|
|
39
|
+
"text/csv": FileType.CSV,
|
|
40
|
+
"application/msword": FileType.DOC,
|
|
41
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": FileType.DOCX,
|
|
42
|
+
"application/vnd.ms-excel": FileType.XLS,
|
|
43
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": FileType.XLSX,
|
|
44
|
+
"application/vnd.ms-powerpoint": FileType.PPT,
|
|
45
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation": FileType.PPTX,
|
|
46
|
+
"application/pdf": FileType.PDF,
|
|
47
|
+
}
|
|
48
|
+
return mime_map.get(mime_type, FileType.UNKNOWN)
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def file_extension_to_enum(file_path: str) -> FileType:
|
|
52
|
+
"""
|
|
53
|
+
Converts a file path or extension to a FileType enum.
|
|
54
|
+
|
|
55
|
+
:param file_path: The file path or extension to convert.
|
|
56
|
+
:return: The corresponding FileType enum, or UNKNOWN if not recognized.
|
|
57
|
+
"""
|
|
58
|
+
if not file_path or not file_path.strip():
|
|
59
|
+
return FileType.UNKNOWN
|
|
60
|
+
|
|
61
|
+
# Extract the file extension, removing the leading dot and making it lowercase
|
|
62
|
+
file_extension = os.path.splitext(file_path)[1].lstrip('.').lower()
|
|
63
|
+
|
|
64
|
+
ext_map = {
|
|
65
|
+
"txt": FileType.TXT,
|
|
66
|
+
"md": FileType.MD,
|
|
67
|
+
"csv": FileType.CSV,
|
|
68
|
+
"doc": FileType.DOC,
|
|
69
|
+
"docx": FileType.DOCX,
|
|
70
|
+
"xls": FileType.XLS,
|
|
71
|
+
"xlsx": FileType.XLSX,
|
|
72
|
+
"ppt": FileType.PPT,
|
|
73
|
+
"pptx": FileType.PPTX,
|
|
74
|
+
"pdf": FileType.PDF,
|
|
75
|
+
}
|
|
76
|
+
return ext_map.get(file_extension, FileType.UNKNOWN)
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def parse_file(cls, file_type: FileType, file_bytes: bytes) -> str | None:
|
|
80
|
+
"""
|
|
81
|
+
Parses file bytes based on the FileType and returns the text content.
|
|
82
|
+
|
|
83
|
+
:param file_type: The type of the file to parse.
|
|
84
|
+
:param file_bytes: The raw bytes of the file to parse.
|
|
85
|
+
:return: The text content of the file, or None if the file type is not supported or parsing fails.
|
|
86
|
+
"""
|
|
87
|
+
if file_type is None or file_bytes is None:
|
|
88
|
+
return None
|
|
89
|
+
if not file_bytes:
|
|
90
|
+
return ""
|
|
91
|
+
|
|
92
|
+
# Dispatch to the correct parser method
|
|
93
|
+
parser_map = {
|
|
94
|
+
FileType.TXT: cls._parse_plain_text,
|
|
95
|
+
FileType.MD: cls._parse_plain_text,
|
|
96
|
+
FileType.CSV: cls._parse_plain_text,
|
|
97
|
+
FileType.DOC: cls._parse_doc,
|
|
98
|
+
FileType.DOCX: cls._parse_docx,
|
|
99
|
+
FileType.XLS: cls._parse_xls,
|
|
100
|
+
FileType.XLSX: cls._parse_xlsx,
|
|
101
|
+
FileType.PPT: cls._parse_ppt,
|
|
102
|
+
FileType.PPTX: cls._parse_pptx,
|
|
103
|
+
FileType.PDF: cls._parse_pdf,
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
parser_func = parser_map.get(file_type)
|
|
107
|
+
|
|
108
|
+
if parser_func:
|
|
109
|
+
return parser_func(file_bytes)
|
|
110
|
+
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
def _parse_plain_text(file_bytes: bytes) -> str:
|
|
115
|
+
return file_bytes.decode('utf-8')
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def _run_textract(file_bytes: bytes, extension: str) -> str:
|
|
119
|
+
"""
|
|
120
|
+
Helper to run textract on in-memory bytes by writing to a temp file.
|
|
121
|
+
Note: textract may require external system dependencies.
|
|
122
|
+
"""
|
|
123
|
+
import textract
|
|
124
|
+
with tempfile.NamedTemporaryFile(suffix=f".{extension}", delete=True) as temp_file:
|
|
125
|
+
temp_file.write(file_bytes)
|
|
126
|
+
temp_file.flush() # Ensure all bytes are written to disk
|
|
127
|
+
text = textract.process(temp_file.name).decode('utf-8')
|
|
128
|
+
return text
|
|
129
|
+
|
|
130
|
+
@classmethod
|
|
131
|
+
def _parse_doc(cls, file_bytes: bytes) -> str:
|
|
132
|
+
return cls._run_textract(file_bytes, 'doc')
|
|
133
|
+
|
|
134
|
+
@staticmethod
|
|
135
|
+
def _parse_docx(file_bytes: bytes) -> str:
|
|
136
|
+
import docx
|
|
137
|
+
with io.BytesIO(file_bytes) as stream:
|
|
138
|
+
document = docx.Document(stream)
|
|
139
|
+
return "\n".join(para.text for para in document.paragraphs if para.text.strip())
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def _parse_xls(file_bytes: bytes) -> str:
|
|
143
|
+
import xlrd
|
|
144
|
+
workbook = xlrd.open_workbook(file_contents=file_bytes)
|
|
145
|
+
text_parts = []
|
|
146
|
+
for sheet in workbook.sheets():
|
|
147
|
+
text_parts.append(f"Sheet: {sheet.name}\n")
|
|
148
|
+
for row_idx in range(sheet.nrows):
|
|
149
|
+
row_cells = []
|
|
150
|
+
for col_idx in range(sheet.ncols):
|
|
151
|
+
cell_text = str(sheet.cell_value(row_idx, col_idx))
|
|
152
|
+
if cell_text.strip():
|
|
153
|
+
row_cells.append(cell_text + "\t")
|
|
154
|
+
if row_cells:
|
|
155
|
+
text_parts.append("".join(row_cells))
|
|
156
|
+
text_parts.append("\n")
|
|
157
|
+
text_parts.append("\n")
|
|
158
|
+
return "".join(text_parts)
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def _parse_xlsx(file_bytes: bytes) -> str:
|
|
162
|
+
import openpyxl
|
|
163
|
+
with io.BytesIO(file_bytes) as stream:
|
|
164
|
+
workbook = openpyxl.load_workbook(stream, read_only=True)
|
|
165
|
+
text_parts = []
|
|
166
|
+
for sheet in workbook.worksheets:
|
|
167
|
+
text_parts.append(f"Sheet: {sheet.title}\n")
|
|
168
|
+
for row in sheet.iter_rows():
|
|
169
|
+
row_cells = []
|
|
170
|
+
for cell in row:
|
|
171
|
+
cell_text = str(cell.value) if cell.value is not None else ""
|
|
172
|
+
if cell_text.strip():
|
|
173
|
+
row_cells.append(cell_text + "\t")
|
|
174
|
+
if row_cells:
|
|
175
|
+
text_parts.append("".join(row_cells))
|
|
176
|
+
text_parts.append("\n")
|
|
177
|
+
text_parts.append("\n")
|
|
178
|
+
return "".join(text_parts)
|
|
179
|
+
|
|
180
|
+
@classmethod
|
|
181
|
+
def _parse_ppt(cls, file_bytes: bytes) -> str:
|
|
182
|
+
return cls._run_textract(file_bytes, 'ppt')
|
|
183
|
+
|
|
184
|
+
@staticmethod
|
|
185
|
+
def _parse_pptx(file_bytes: bytes) -> str:
|
|
186
|
+
import pptx
|
|
187
|
+
with io.BytesIO(file_bytes) as stream:
|
|
188
|
+
presentation = pptx.Presentation(stream)
|
|
189
|
+
text_parts = []
|
|
190
|
+
for slide in presentation.slides:
|
|
191
|
+
for shape in slide.shapes:
|
|
192
|
+
if shape.has_text_frame:
|
|
193
|
+
text = shape.text_frame.text
|
|
194
|
+
if text and text.strip():
|
|
195
|
+
text_parts.append(text)
|
|
196
|
+
return "\n".join(text_parts)
|
|
197
|
+
|
|
198
|
+
@staticmethod
|
|
199
|
+
def _parse_pdf(file_bytes: bytes) -> str:
|
|
200
|
+
"""Parses a PDF file's bytes and extracts text using PyMuPDF."""
|
|
201
|
+
import pymupdf
|
|
202
|
+
text_parts = []
|
|
203
|
+
with io.BytesIO(file_bytes) as stream:
|
|
204
|
+
with pymupdf.open(stream=stream) as doc:
|
|
205
|
+
for page in doc:
|
|
206
|
+
text_parts.append(page.get_text())
|
|
207
|
+
return "\n".join(text_parts)
|
|
@@ -4,38 +4,16 @@ from weakref import WeakValueDictionary
|
|
|
4
4
|
|
|
5
5
|
from databind.json import dumps
|
|
6
6
|
from sapiopylib.rest.User import SapioUser
|
|
7
|
+
from sapiopylib.rest.utils.singletons import SapioContextManager
|
|
7
8
|
|
|
8
9
|
from sapiopycommons.flowcyto.flowcyto_data import FlowJoWorkspaceInputJson, UploadFCSInputJson, \
|
|
9
10
|
ComputeFlowStatisticsInputJson
|
|
10
11
|
|
|
11
12
|
|
|
12
|
-
class FlowCytoManager:
|
|
13
|
+
class FlowCytoManager(SapioContextManager):
|
|
13
14
|
"""
|
|
14
15
|
This manager includes flow cytometry analysis tools that would require FlowCyto license to use.
|
|
15
16
|
"""
|
|
16
|
-
_user: SapioUser
|
|
17
|
-
|
|
18
|
-
__instances: WeakValueDictionary[SapioUser, FlowCytoManager] = WeakValueDictionary()
|
|
19
|
-
__initialized: bool
|
|
20
|
-
|
|
21
|
-
def __new__(cls, user: SapioUser):
|
|
22
|
-
"""
|
|
23
|
-
Observes singleton pattern per record model manager object.
|
|
24
|
-
|
|
25
|
-
:param user: The user that will make the webservice request to the application.
|
|
26
|
-
"""
|
|
27
|
-
obj = cls.__instances.get(user)
|
|
28
|
-
if not obj:
|
|
29
|
-
obj = object.__new__(cls)
|
|
30
|
-
obj.__initialized = False
|
|
31
|
-
cls.__instances[user] = obj
|
|
32
|
-
return obj
|
|
33
|
-
|
|
34
|
-
def __init__(self, user: SapioUser):
|
|
35
|
-
if self.__initialized:
|
|
36
|
-
return
|
|
37
|
-
self._user = user
|
|
38
|
-
self.__initialized = True
|
|
39
17
|
|
|
40
18
|
def create_flowjo_workspace(self, workspace_input: FlowJoWorkspaceInputJson) -> int:
|
|
41
19
|
"""
|
|
@@ -5,6 +5,7 @@ from typing import Any
|
|
|
5
5
|
from weakref import WeakValueDictionary
|
|
6
6
|
|
|
7
7
|
from sapiopylib.rest.User import SapioUser
|
|
8
|
+
from sapiopylib.rest.utils.singletons import SapioContextManager
|
|
8
9
|
|
|
9
10
|
_STR_JAVA_TYPE = "java.lang.String"
|
|
10
11
|
_INT_JAVA_TYPE = "java.lang.Integer"
|
|
@@ -274,37 +275,10 @@ class AccessionServiceDescriptor:
|
|
|
274
275
|
}
|
|
275
276
|
|
|
276
277
|
|
|
277
|
-
class AccessionService:
|
|
278
|
+
class AccessionService(SapioContextManager):
|
|
278
279
|
"""
|
|
279
280
|
Provides Sapio Foundations Accession Service functionalities.
|
|
280
281
|
"""
|
|
281
|
-
_user: SapioUser
|
|
282
|
-
|
|
283
|
-
__instances: WeakValueDictionary[SapioUser, AccessionService] = WeakValueDictionary()
|
|
284
|
-
__initialized: bool
|
|
285
|
-
|
|
286
|
-
@property
|
|
287
|
-
def user(self) -> SapioUser:
|
|
288
|
-
return self._user
|
|
289
|
-
|
|
290
|
-
def __new__(cls, user: SapioUser):
|
|
291
|
-
"""
|
|
292
|
-
Observes singleton pattern per record model manager object.
|
|
293
|
-
|
|
294
|
-
:param user: The user that will make the webservice request to the application.
|
|
295
|
-
"""
|
|
296
|
-
obj = cls.__instances.get(user)
|
|
297
|
-
if not obj:
|
|
298
|
-
obj = object.__new__(cls)
|
|
299
|
-
obj.__initialized = False
|
|
300
|
-
cls.__instances[user] = obj
|
|
301
|
-
return obj
|
|
302
|
-
|
|
303
|
-
def __init__(self, user: SapioUser):
|
|
304
|
-
if self.__initialized:
|
|
305
|
-
return
|
|
306
|
-
self._user = user
|
|
307
|
-
self.__initialized = True
|
|
308
282
|
|
|
309
283
|
def accession_with_config(self, data_type_name: str, data_field_name: str, num_ids: int) -> list[str]:
|
|
310
284
|
"""
|
|
@@ -7,35 +7,13 @@ from weakref import WeakValueDictionary
|
|
|
7
7
|
from databind.json import dumps, loads
|
|
8
8
|
from sapiopylib.rest.User import SapioUser
|
|
9
9
|
from sapiopylib.rest.pojo.DataRecord import DataRecord
|
|
10
|
+
from sapiopylib.rest.utils.singletons import SapioContextManager
|
|
10
11
|
|
|
11
12
|
from sapiopycommons.general.exceptions import SapioException
|
|
12
13
|
from sapiopycommons.multimodal.multimodal_data import *
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
class MultiModalManager:
|
|
16
|
-
_user: SapioUser
|
|
17
|
-
|
|
18
|
-
__instances: WeakValueDictionary[SapioUser, MultiModalManager] = WeakValueDictionary()
|
|
19
|
-
__initialized: bool
|
|
20
|
-
|
|
21
|
-
def __new__(cls, user: SapioUser):
|
|
22
|
-
"""
|
|
23
|
-
Observes singleton pattern per record model manager object.
|
|
24
|
-
|
|
25
|
-
:param user: The user that will make the webservice request to the application.
|
|
26
|
-
"""
|
|
27
|
-
obj = cls.__instances.get(user)
|
|
28
|
-
if not obj:
|
|
29
|
-
obj = object.__new__(cls)
|
|
30
|
-
obj.__initialized = False
|
|
31
|
-
cls.__instances[user] = obj
|
|
32
|
-
return obj
|
|
33
|
-
|
|
34
|
-
def __init__(self, user:SapioUser):
|
|
35
|
-
if self.__initialized:
|
|
36
|
-
return
|
|
37
|
-
self._user = user
|
|
38
|
-
self.__initialized = True
|
|
16
|
+
class MultiModalManager(SapioContextManager):
|
|
39
17
|
|
|
40
18
|
def load_image_data(self, request: ImageDataRequestPojo) -> list[str]:
|
|
41
19
|
"""
|
|
@@ -140,7 +140,7 @@ class AbstractWebserviceHandler(AbstractWebhookHandler):
|
|
|
140
140
|
# Get the login credentials from the headers.
|
|
141
141
|
auth: str = headers.get("Authorization")
|
|
142
142
|
if auth and auth.startswith("Basic "):
|
|
143
|
-
credentials: list[str] = b64decode(auth.split("Basic ")[1]).decode().split(":"
|
|
143
|
+
credentials: list[str] = b64decode(auth.split("Basic ")[1]).decode().split(":")
|
|
144
144
|
user = self.basic_auth(url, credentials[0], credentials[1])
|
|
145
145
|
elif auth and auth.startswith("Bearer "):
|
|
146
146
|
user = self.bearer_token_auth(url, auth.split("Bearer ")[1])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: sapiopycommons
|
|
3
|
-
Version: 2025.8.
|
|
3
|
+
Version: 2025.8.22a716
|
|
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>
|
|
@@ -17,7 +17,7 @@ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
|
17
17
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
18
|
Requires-Python: >=3.10
|
|
19
19
|
Requires-Dist: databind>=4.5
|
|
20
|
-
Requires-Dist: sapiopylib>=2025.
|
|
20
|
+
Requires-Dist: sapiopylib>=2025.7.31a279
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
|
|
23
23
|
|
|
@@ -1,46 +1,13 @@
|
|
|
1
1
|
sapiopycommons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
sapiopycommons/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
sapiopycommons/ai/
|
|
4
|
-
sapiopycommons/ai/protobuf_utils.py,sha256=cBjbxoFAwU02kNUxEce95WnMU2CMuDD-qFaeWgvQJMQ,24599
|
|
5
|
-
sapiopycommons/ai/server.py,sha256=w70KUENxrYoMyDRFunCAlxhhwXXWBNngCwG40Po7vTU,4212
|
|
6
|
-
sapiopycommons/ai/test_client.py,sha256=iPhn7cvKNLmDAXrjpmIkZpW2pDWlUhJZHDLHJbEoWsg,15673
|
|
7
|
-
sapiopycommons/ai/tool_service_base.py,sha256=oXxhhxeKiEq3QtlJEjsWnO8wrUPruz5M2Ti3ipyBf4E,43991
|
|
8
|
-
sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2.py,sha256=8tKXwLXcqFGdQHHSEBSi6Fd7dcaCFoOqmhjzqhenb_M,2372
|
|
9
|
-
sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2.pyi,sha256=FwtXmNAf7iYGEFm4kbqb04v77jNHbZg18ZmEDhle_bU,1444
|
|
10
|
-
sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2_grpc.py,sha256=uO25bcnfGqXpP4ggUur54Nr73Wj-DGWftExzLNcxdHI,931
|
|
11
|
-
sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2.py,sha256=in9iHiLPYcnLWoLeqy4nWSI0jZGHD_bMhEFRIRtJPuo,20864
|
|
12
|
-
sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2.pyi,sha256=U5zXrbBxsWilLTsRWJd1TqjdjLKFsr3enF9OJ8GfyWw,34028
|
|
13
|
-
sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2_grpc.py,sha256=Vj6qDKvsHgl25iBi3UjtTuGxihekgqCuufExvnJKzQI,940
|
|
14
|
-
sapiopycommons/ai/protoapi/plan/step_output_pb2.py,sha256=EBNCzLUDwwCqDCh35zSfFdfq0RP8WrmTMXEzEPu_1_E,2655
|
|
15
|
-
sapiopycommons/ai/protoapi/plan/step_output_pb2.pyi,sha256=yuxOYnDZ9DRuu-TLzaKOW_B4LUiYxTrNc2AbssXg4kE,2022
|
|
16
|
-
sapiopycommons/ai/protoapi/plan/step_output_pb2_grpc.py,sha256=ebWLIfOFeVE2WuUIThMBerVweH-1phviGX195UTwYyg,924
|
|
17
|
-
sapiopycommons/ai/protoapi/plan/step_pb2.py,sha256=mKTm_syaX99GzhWtGIPkxMTsfcsvW0QbRqjv06eHSM0,2433
|
|
18
|
-
sapiopycommons/ai/protoapi/plan/step_pb2.pyi,sha256=QPIcsjcUvEGQkdZMUMiVzFFNDl8yOUe_qJtf5XEp5Ck,2062
|
|
19
|
-
sapiopycommons/ai/protoapi/plan/step_pb2_grpc.py,sha256=1CBna5NBBxPwQhrkN8-Fim_j3FGmOfDo5C4c8sIBV8Q,917
|
|
20
|
-
sapiopycommons/ai/protoapi/plan/converter/converter_pb2.py,sha256=rZYBRfR0umwDYvBdYnzxR1VZSutRqunhd3QsdtQXiCM,3593
|
|
21
|
-
sapiopycommons/ai/protoapi/plan/converter/converter_pb2.pyi,sha256=_35yHfKTJH3SMdA5_c6qF6OZG6UwFWXpNh6dwRFDKkk,4250
|
|
22
|
-
sapiopycommons/ai/protoapi/plan/converter/converter_pb2_grpc.py,sha256=6T5FCmT_vEFSywUVlAlWWfBDPaILb0Dq8yCGuO_Q-BU,6448
|
|
23
|
-
sapiopycommons/ai/protoapi/plan/item/item_container_pb2.py,sha256=oAMgQ2R5iYjjPPfR_npySS2P0H9zB1mj-Unb0hhXNbE,3816
|
|
24
|
-
sapiopycommons/ai/protoapi/plan/item/item_container_pb2.pyi,sha256=K8Y0dvNY7V0MLOegC1ODXqc1g4lH1NY70EGH2UWN9vQ,4118
|
|
25
|
-
sapiopycommons/ai/protoapi/plan/item/item_container_pb2_grpc.py,sha256=1NzBWBUINC0Rk-NGYZ-97BVKvVUxcn_I44IjQO2h-nQ,932
|
|
26
|
-
sapiopycommons/ai/protoapi/plan/script/script_pb2.py,sha256=swyahlxM7fKm-RqtgMd1Czqd1JrbXRw3sYqFTkjbbyM,5144
|
|
27
|
-
sapiopycommons/ai/protoapi/plan/script/script_pb2.pyi,sha256=GqlqLf9UX_B5vCR9cm-VoYLHFJNmZjJafcPJ6EDa8Qw,6192
|
|
28
|
-
sapiopycommons/ai/protoapi/plan/script/script_pb2_grpc.py,sha256=ginPYpsiI6U6dB6gfWHJM8LWjSHUgcqz_gR7Dmdsyck,6864
|
|
29
|
-
sapiopycommons/ai/protoapi/plan/tool/entry_pb2.py,sha256=yNyyyVvz94ewjGaHw3t0pUP-KH7ACg5hLC0QzrsFPis,2130
|
|
30
|
-
sapiopycommons/ai/protoapi/plan/tool/entry_pb2.pyi,sha256=2vI0WSy0KGFHDewMSRyX5rUkmmmSaMBLvFO7txqA6Ug,2354
|
|
31
|
-
sapiopycommons/ai/protoapi/plan/tool/entry_pb2_grpc.py,sha256=i24BfJEt7LtyROxHVEyS9RLYqLZKPvyJMKRFZj6NUTg,923
|
|
32
|
-
sapiopycommons/ai/protoapi/plan/tool/tool_pb2.py,sha256=Gy6YjakkicKi4JthUFOxOyPLen-af7XI-ijinVs7EOY,8181
|
|
33
|
-
sapiopycommons/ai/protoapi/plan/tool/tool_pb2.pyi,sha256=x33Ko2KC5_lw8omA1hYRGoUWvDitxNdloxswTxaMaqc,16713
|
|
34
|
-
sapiopycommons/ai/protoapi/plan/tool/tool_pb2_grpc.py,sha256=CLSGEgSpN4D6wcE-P_5lzb2Nttjr4XjumxGUrZQSip0,6915
|
|
35
|
-
sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2.py,sha256=MhWzTyJz3xZgpdW8_LCVKuzKXT0cv6iHMRB-UNCUNzM,2094
|
|
36
|
-
sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2.pyi,sha256=vLYA8Tkzq2AwgVadoUp5vAg4HgGlgga0kzeS3e_XkCQ,1621
|
|
37
|
-
sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2_grpc.py,sha256=imcciy_kbmm7OV_W3jYZ53R6GQ6Yh-eUcVW0A9GkWdg,931
|
|
3
|
+
sapiopycommons/ai/tool_of_tools.py,sha256=zYmQ4rNX-qYQnc-vNDnYZjtv9JgmQAmVVuHfVOdBF3w,46984
|
|
38
4
|
sapiopycommons/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
39
|
-
sapiopycommons/callbacks/callback_util.py,sha256=
|
|
5
|
+
sapiopycommons/callbacks/callback_util.py,sha256=Z1LcXnRRjXyhmcSDUwh4NzcA6ICtcbFUMKcvAqQcS8E,153811
|
|
40
6
|
sapiopycommons/callbacks/field_builder.py,sha256=rnIP-RJafk3mZlAx1eJ8a0eSW9Ps_L6_WadCmusnENw,38772
|
|
41
|
-
sapiopycommons/chem/IndigoMolecules.py,sha256=
|
|
7
|
+
sapiopycommons/chem/IndigoMolecules.py,sha256=2oEbBTW5q4VtJFG_hRbHG7QjtTU6rJZL4lB_N-wWndo,5530
|
|
42
8
|
sapiopycommons/chem/Molecules.py,sha256=mVqPn32MPMjF0iZas-5MFkS-upIdoW5OB72KKZmJRJA,12523
|
|
43
9
|
sapiopycommons/chem/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
sapiopycommons/chem/ps_commons.py,sha256=V-g0v52cuwXejlKNH0W771Sl_HOuyMfmBzRy1b_ms9Q,36453
|
|
44
11
|
sapiopycommons/customreport/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
45
12
|
sapiopycommons/customreport/auto_pagers.py,sha256=89p-tik0MhsOplYje6LbAW4WClldpAmb8YXFDoXhIlY,17144
|
|
46
13
|
sapiopycommons/customreport/column_builder.py,sha256=0RO53e9rKPZ07C--KcepN6_tpRw_FxF3O9vdG0ilKG8,3014
|
|
@@ -59,17 +26,19 @@ sapiopycommons/eln/experiment_tags.py,sha256=7-fpOiSqrjbXmWIJhEhaxMgLsVCPAtKqH8x
|
|
|
59
26
|
sapiopycommons/eln/plate_designer.py,sha256=XFazSvhTbSy47t80-jc2tyx_-fQ_IUjKd18JQKEFcsY,13939
|
|
60
27
|
sapiopycommons/eln/step_creation.py,sha256=CFkGC-SxwAQpQlcs_obqLAVgmsNxKSGMqMtO_E6IVmw,10171
|
|
61
28
|
sapiopycommons/files/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
|
+
sapiopycommons/files/assay_plate_reader.py,sha256=3c2PQiiAbc2QJU9ZfNLzcTmvJrUwsbkIHO7R6R52xGU,3020
|
|
62
30
|
sapiopycommons/files/complex_data_loader.py,sha256=T39veNhvYl6j_uZjIIJ8Mk5Aa7otR5RB-g8XlAdkksA,1421
|
|
63
31
|
sapiopycommons/files/file_bridge.py,sha256=vKbqxPexi15epr_-_qLrEfYoxNxB031mXN92iVtOMqE,9511
|
|
64
32
|
sapiopycommons/files/file_bridge_handler.py,sha256=SEYDIQhSCmjI6qyLdDJE8JVKSd0WYvF7JvAq_Ahp9Do,25503
|
|
65
33
|
sapiopycommons/files/file_data_handler.py,sha256=f96MlkMuQhUCi4oLnzJK5AiuElCp5jLI8_sJkZVwpws,36779
|
|
34
|
+
sapiopycommons/files/file_text_converter.py,sha256=Gaj_divTiKXWd6flDOgrxNXpcn9fDWqxX6LUG0joePk,7516
|
|
66
35
|
sapiopycommons/files/file_util.py,sha256=djouyGjsYgWzjz2OBRnSeMDgj6NrsJUm1a2J93J8Wco,31915
|
|
67
36
|
sapiopycommons/files/file_validator.py,sha256=ryg22-93csmRO_Pv0ZpWphNkB74xWZnHyJ23K56qLj0,28761
|
|
68
37
|
sapiopycommons/files/file_writer.py,sha256=hACVl0duCjP28gJ1NPljkjagNCLod0ygUlPbvUmRDNM,17605
|
|
69
|
-
sapiopycommons/flowcyto/flow_cyto.py,sha256=
|
|
38
|
+
sapiopycommons/flowcyto/flow_cyto.py,sha256=B6DFquLi-gcWfJWyP4vYfwTXXJKl6O9W5-k8FzkM0Oo,2610
|
|
70
39
|
sapiopycommons/flowcyto/flowcyto_data.py,sha256=mYKFuLbtpJ-EsQxLGtu4tNHVlygTxKixgJxJqD68F58,2596
|
|
71
40
|
sapiopycommons/general/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
72
|
-
sapiopycommons/general/accession_service.py,sha256=
|
|
41
|
+
sapiopycommons/general/accession_service.py,sha256=ZvtvZg7d_siMJUedjrF14mcqo5ZqVA5IJxDa5enlB-8,12792
|
|
73
42
|
sapiopycommons/general/aliases.py,sha256=VwnWf_P803pcteoAIs0DkLScVChCS5XNgryTp8FzaNc,14696
|
|
74
43
|
sapiopycommons/general/audit_log.py,sha256=sQAMcJx0cNkgZm7nTZSaGPxWvHG0_x6dBtU0jESavb4,9131
|
|
75
44
|
sapiopycommons/general/custom_report_util.py,sha256=9elLEUSgfM0gli8nRPz1uYkhaXN4Vnx3piSiNHv5IBs,19156
|
|
@@ -81,7 +50,7 @@ sapiopycommons/general/popup_util.py,sha256=HKILegU1uCL_6abNlNL0Wn3xgX2JNa_kJeq7
|
|
|
81
50
|
sapiopycommons/general/sapio_links.py,sha256=YkcVKNLrSGoM7tCCXBAsIbIxylctwdcEyhePrRMODe0,2859
|
|
82
51
|
sapiopycommons/general/storage_util.py,sha256=ovmK_jN7v09BoX07XxwShpBUC5WYQOM7dbKV_VeLXJU,8892
|
|
83
52
|
sapiopycommons/general/time_util.py,sha256=jU1urPoZRv6evNucR0-288EyT4PrsDpCr-H1-7BKq9A,12363
|
|
84
|
-
sapiopycommons/multimodal/multimodal.py,sha256=
|
|
53
|
+
sapiopycommons/multimodal/multimodal.py,sha256=EP9WYzx1CvidmEBlvzO6tiF4HJwsPB1FgxpnbWzxnpA,6161
|
|
85
54
|
sapiopycommons/multimodal/multimodal_data.py,sha256=0BeVPr9HaC0hNTF1v1phTIKGruvNnwerHsD994qJKBg,15099
|
|
86
55
|
sapiopycommons/processtracking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
87
56
|
sapiopycommons/processtracking/custom_workflow_handler.py,sha256=eYKdYlwo8xx-6AkB_iPUBNV9yDoNvW2h_Sm3i8JpmRU,25844
|
|
@@ -97,8 +66,8 @@ sapiopycommons/sftpconnect/sftp_builder.py,sha256=lFK3FeXk-sFLefW0hqY8WGUQDeYiGa
|
|
|
97
66
|
sapiopycommons/webhook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
98
67
|
sapiopycommons/webhook/webhook_context.py,sha256=D793uLsb1691SalaPnBUk3rOSxn_hYLhdvkaIxjNXss,1909
|
|
99
68
|
sapiopycommons/webhook/webhook_handlers.py,sha256=7o_wXOruhT9auNh8OfhJAh4WhhiPKij67FMBSpGPICc,39939
|
|
100
|
-
sapiopycommons/webhook/webservice_handlers.py,sha256=
|
|
101
|
-
sapiopycommons-2025.8.
|
|
102
|
-
sapiopycommons-2025.8.
|
|
103
|
-
sapiopycommons-2025.8.
|
|
104
|
-
sapiopycommons-2025.8.
|
|
69
|
+
sapiopycommons/webhook/webservice_handlers.py,sha256=tyaYGG1-v_JJrJHZ6cy5mGCxX9z1foLw7pM4MDJlFxs,14297
|
|
70
|
+
sapiopycommons-2025.8.22a716.dist-info/METADATA,sha256=VcVFTZtBKXinxIoSA6DX_Pl7jFYdyw-YJp_1quWuVjI,3143
|
|
71
|
+
sapiopycommons-2025.8.22a716.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
72
|
+
sapiopycommons-2025.8.22a716.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
|
|
73
|
+
sapiopycommons-2025.8.22a716.dist-info/RECORD,,
|